Springboot - Security (8) 구Authentication객체가 가질수 있는 2가지 타입

Yuri Lee·2020년 9월 18일
1

Springboot - Security

목록 보기
8/8

[솔직히 여기 이해 제대로 안감 ... ;; ]

getClientRegistration : ClientRegistration{registrationId='google', clientId='774158704369-5ckg8m14n6p5oqqfa9o3ar29ednsuroo.apps.googleusercontent.com', clientSecret='Ii2YP2RY0aKLXNHhyCU9a8Tr', clientAuthenticationMethod=org.springframework.security.oauth2.core.ClientAuthenticationMethod@592d42e, authorizationGrantType=org.springframework.security.oauth2.core.AuthorizationGrantType@5da5e9f3, redirectUriTemplate='{baseUrl}/{action}/oauth2/code/{registrationId}', scopes=[email, profile], providerDetails=org.springframework.security.oauth2.client.registration.ClientRegistration$ProviderDetails@51f14ab0, clientName='Google'}
getAccessToken : ya29.a0AfH6SMAhBHtl64_9e65zEcknuudPjByCKbZOSAk5hScIHsO98KQE5wzqoBP3S_36s_n_8gv3ZROkQYHjPNDvip122ThUJl0PijecVxK2gGisbNN82z7YH23tQP4qxc4pgSnOqURRbtKlz8E2Oh_CLgNfgqGEXJvU1FU
getAttribute : {sub=108093779182714031698, name=yuri lee, given_name=yuri, family_name=lee, picture=https://lh3.googleusercontent.com/-wYB_5cdsgPY/AAAAAAAAAAI/AAAAAAAAACs/AMZuuckiu3V1IB3F_MHAmtcJnp1Ih_hq4w/photo.jpg, email=leyuri97@gmail.com, email_verified=true, locale=en}

getClientRegistration : 우리 서버의 기본적인 정보들이 들어가 있다.

결국은 ClientRegistration와 AccessToken 모두 userRequest 안에 들어있다. 결국은

구글 로그인 버튼 클릭 -> 구글 로그인 창 -> 로그인을 완료 -> code를 리턴(OAuth-Client라이브러리) -> code를 통해서 AccessToken을 요청한다. 그럼 그 AccessToken을 받을 것이다. 바로 여기까지가 userRequest이다.

userRequest 정보로 회원 프로필을 받아야 하는데 그때 사용되는 함수가 바로 loadUser 함수이다. 이 함수를 통해서 구글 회원 프로필을 받을 수 있다.

IndexController.java

	@GetMapping("/test/login")
	public @ResponseBody String testLogin(Authentication authentication) { //DI (의존성 주입)
		System.out.println("/test/login =========================");
		PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
		System.out.println(principalDetails.getUser());
		return "Check session info";
	}

위와 같은 방식으로 get요청을 하면 결과는 다음과 같다 (love라는 유저네임으로 로그인을 함)

User(id=1, username=love, password=$2a$10$jL9oLeI1Y9pu0GNeJlZaq.B1yb25hYCrGnVvFvx66aG5GQqx7S2qK, email=love@nate.com, role=ROLE_USER, createDate=2020-09-17 17:22:30.261)

DI 의존성 주입
authentication 객체 안에 getPrincipal이 있고 getPrincipal는 리턴하는 타입이 PrincipalDetails로 오브젝트이기 때문에 얘를 principalDetails로 받아서 getUser로 호출하면

getPrincipal 자체가

@Data
public class PrincipalDetails implements UserDetails{
	
	private User user; //콤포지션
	
	// 생성자 만들기 
	public PrincipalDetails(User user) {
		this.user = user;
	}

이것이다.

	@GetMapping("/test/login")
	public @ResponseBody String testLogin(Authentication authentication, 
			@AuthenticationPrincipal UserDetails userDetails) { //DI (의존성 주입)
		System.out.println("/test/login =========================");
		PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
		System.out.println("authentication :"+principalDetails.getUser());
		
		System.out.println("userDetails :"+userDetails.getUsername());
		return "Check session info";
	}
	
/test/login =========================
authentication :User(id=1, username=love, password=$2a$10$jL9oLeI1Y9pu0GNeJlZaq.B1yb25hYCrGnVvFvx66aG5GQqx7S2qK, email=love@nate.com, role=ROLE_USER, provider=null, providerId=null, createDate=2020-09-17 17:22:30.261)
userDetails :love

@AuthenticationPrincipal 어노테이션은 UserDetails 타입을 갖고 있다.

@Data
public class PrincipalDetails implements UserDetails{
	
	private User user; //콤포지션
	
	// 생성자 만들기 
	public PrincipalDetails(User user) {
		this.user = user;
	}

UserDetails타입을 implements 했기 때문에 UserDetails를 PrincipalDetails으로 대체할 수 있다.

/test/login =========================
authentication :User(id=1, username=love, password=$2a$10$jL9oLeI1Y9pu0GNeJlZaq.B1yb25hYCrGnVvFvx66aG5GQqx7S2qK, email=love@nate.com, role=ROLE_USER, provider=null, providerId=null, createDate=2020-09-17 17:22:30.261)
userDetails :User(id=1, username=love, password=$2a$10$jL9oLeI1Y9pu0GNeJlZaq.B1yb25hYCrGnVvFvx66aG5GQqx7S2qK, email=love@nate.com, role=ROLE_USER, provider=null, providerId=null, createDate=2020-09-17 17:22:30.261)

같은 정보를 갖고 있다. 하지만 여기서 구글 로그인을 진행하면 에러가 난다 ....

PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
여기서 오류 발생 ㅠㅠ

userDetails 타입으로 PrincipalDetails이 캐스팅이 안된다는 의미이다.

[정리]

스프링 시큐리티

세션이 있고, 이 세션 안에는 시큐리티가 관리하는 세션이 있다. 그리고 여기에 들어갈 수 있는 타입의 객체는 authentication 객체이다. 얘를 필요할때마다 컨트롤러에서 DI를 할 수 있다.

authentication안에 들어갈 수 있는 타입이 2개가 있다.
UserDetails, OAuth2User 타입이다.

정리하면 시큐리티가 들고 있는 세션에는 무조건 authentication 객체만 들어갈 수 있다. 얘가 딱 들어가는 순간 로그인이 된 것이다. authentication 객체 안에 들어갈 수 있는 타입은 딱 2개만 있는데 UserDetails, OAuth2User 타입이다.

그럼 언제 UserDetails이 만들어질까? 일반적인 로그인을 하면 UserDetails 타입으로 authentication 으로 들어온다. 페이스북 로그인, 구글 로그인과 같은 OAuth 로그인을 하게 되면 OAuth2User type이 authentication 객체 안으로 들어간다. 얘가 딱 들어가게 되면 세션이 생긴 것이니까 로그인이 된 것이다. 그럼 필요할 때 꺼내 써야 하는데 불편한 게 있다.

일반적인 로그인 세션으로 접근하려면 어떻게 해야 할까?

	@GetMapping("/user")
	public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails userDetails) {
		return "user";
	}

일반적인 로그인을 하였을 때는 이것을 사용하고,

	@GetMapping("/user")
	public @ResponseBody String user(@AuthenticationPrincipal OAuth2User oauth PrincipalDetails userDetails) {
		return "user";
	}

구글로그인을 하였을 때는 이것을 사용해야 하는데 무엇을 ... 사용해야 할까... (?) 처리가 굉장히 복잡해질 것....

authentication 객체 안에 들어갈 수 있는 타입은 UserDetails 아니면 OAuth2User 니까 뭘 하나 만들냐면..
X라는 클래스를 만들어서 UserDetails를 상속받고, 즉 implements 받고 x를 딱 부모로 두면

authentication 객체 자체가 UserDetails 아니면 OAuth2User 타입이기만 하면 담을 수 있다. 그래서 이 x를 authentication 객체에 담는 것이다.

	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		System.out.println("username:"+username);
		User userEntity = userRepository.findByUsername(username);
		if(userEntity != null) {
			return new PrincipalDetails(userEntity); // authentication 객체 안에 PrincipalDetails 이 들어간 것임 
		}
		return null;
	}

// authentication 객체 안에 PrincipalDetails 이 들어간 것임!!!

그러면 PrincipalDetails 타입을 부모로 해서 authentication 객체 안에 넣어버리면 우리가 언제 어디서나 필요할 때마다
oauth로 로그인 하든, google로 로그인을 하든 PrincipalDetails로 묶어서 받을 수 있다.


[Reference]

이 글은 유투버 데어 프로그래밍의 스프링 부트 시큐리티 강좌를 바탕으로 정리한 내용입니다.

profile
Step by step goes a long way ✨

0개의 댓글