[OAuth] Authentication객체가 가질 수 있는 2가지 타입
PrincipalOauth2UserService
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
// 로그인 후처리
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("userRequest : " + userRequest.getClientRegistration()); // registrationId로 어떤 OAuth로 로그인 했는지 확인 가능
System.out.println("userRequest : " + userRequest.getAccessToken().getTokenValue());
// 구글 로그인 버튼 클릭 -> 구글 로그인 창 -> 로그인 완료 -> code를 리턴(OAuth-Client라이브러리) -> AccessToken 요청
// userRequest 정보 -> loadUser함수 -> 구글로부터 회원프로필 받아준다.
System.out.println("getAttribute : " + super.loadUser(userRequest).getAttributes());
OAuth2User oAuth2User = super.loadUser(userRequest);
return super.loadUser(userRequest);
}
}
PrincipalDetails
getter 추가 (@Data)
@Data
@RequiredArgsConstructor
public class PrincipalDetails implements UserDetails {
IndexController
@GetMapping("/test/login")
@ResponseBody
public String testlogin(Authentication authentication) {
System.out.println("/test/login=================");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication : " + principalDetails.getUser());
return "세션 정보 확인하기";
}
일반 사용자로 로그인한 후 (ssar, 1234) /test/login을 호출하면
@GetMapping("/test/login")
@ResponseBody
public String testLogin(Authentication authentication, @AuthenticationPrincipal UserDetails userDetails) {
System.out.println("/test/login=================");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication : " + principalDetails.getUser());
System.out.println("userDetails : " + userDetails.getUsername());
return "세션 정보 확인하기";
}
@AuthenticationPrincipal을 통해서 세션정보를 가져올 수 있다.
PrincipalDetails를 보면
@Data
@RequiredArgsConstructor
public class PrincipalDetails implements UserDetails {
UserDetails를 상속받았기 때문에
@GetMapping("/test/login")
@ResponseBody
public String testLogin(Authentication authentication, @AuthenticationPrincipal PrincipalDetails userDetails) {
System.out.println("/test/login=================");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication : " + principalDetails.getUser());
System.out.println("userDetails : " + userDetails.getUser());
return "세션 정보 확인하기";
}
userDetails는 getUsername()이 아니라 getUser()를 사용할 수 있다.
이번에는 구글로 로그인한 후 /test/login을 호출하면
ClassCastException이 발생한다.
@GetMapping("/test/oauth/login")
@ResponseBody
public String testOauthLogin(Authentication authentication) {
System.out.println("/test/oauth/login=================");
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authentication : " + oAuth2User.getAttributes());
return "OAuth 세션 정보 확인하기";
}
이렇게 하면 에러가 발생하지 않는다.
위 정보는
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
// 로그인 후처리
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("userRequest : " + userRequest.getClientRegistration()); // registrationId로 어떤 OAuth로 로그인 했는지 확인 가능
System.out.println("userRequest : " + userRequest.getAccessToken().getTokenValue());
// 구글 로그인 버튼 클릭 -> 구글 로그인 창 -> 로그인 완료 -> code를 리턴(OAuth-Client라이브러리) -> AccessToken 요청
// userRequest 정보 -> loadUser함수 -> 구글로부터 회원프로필 받아준다.
System.out.println("getAttribute : " + super.loadUser(userRequest).getAttributes());
OAuth2User oAuth2User = super.loadUser(userRequest);
return super.loadUser(userRequest);
}
}
여기서 받은 정보와 같다.
@GetMapping("/test/oauth/login")
@ResponseBody
public String testOauthLogin(Authentication authentication, @AuthenticationPrincipal OAuth2User oauth) {
System.out.println("/test/oauth/login=================");
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authentication : " + oAuth2User.getAttributes());
System.out.println("oauth2User :" + oauth.getAttributes());
return "OAuth 세션 정보 확인하기";
}
@AuthenticationPrincipal 사용 시 타입은 OAuth2User이다.
세션 정보에 접근하려면 Authentication와 @AuthenticationPrincipal로 접근할 수 있다.
일반 사용자의 정보는 UserDetails 타입으로 형변환하여 값을 얻을 수 있고, OAuth를 통한 사용자는 OAuth2User 타입으로 형변환하여 값을 얻을 수 있다.
여기서 일반 사용자 정보를 받을 때 PrincipalDetails로 형변환 한 것은 PrincipalDetails가 UserDetails를 상속받았기 때문에 가능하다.
스프링 시큐리티는 자신만의 시큐리티 세션을 가지고 있다. 세션의 영역 안에 시큐리티가 관리하는 세션이 포함되어 있다.
시큐리티 세션에는 Authentication 객체만 있는데, 이 객체 안에는 UserDetails와 OAuth2User 타입만 들어갈 수 있다.
일반 로그인을 할 때 UserDetails이 Authentication안에 들어가고, OAuth 로그인을 하면 OAuth2User이 Authentication 안에 들어간다.
이제 필요할 때 꺼내서 사용해야 하는데 불편한 점이 있다. 로그인 방식에 따라 타입이 다르기 때문에 컨트롤러에서 코드를 다르게 작성해야 한다.
이런 불편함을 해소하기 위해서 UserDetails와 OAuth2User를 모두 상속받는(implements) 클래스를 만들면 된다.
이미 UserDetails를 상속받는 PrincipalDetails를 만들었기 때문에 여기에 OAuth2User를 추가하면 된다.
PrincipalDetails
package com.cos.security.config.auth;
import com.cos.security.model.User;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
// Security Session => Authentication => UserDetails(여기서는 PrincipalDeatils 가 된다.)
@Data
@RequiredArgsConstructor
public class PrincipalDetails implements UserDetails, OAuth2User {
private final User user; //콤포지션
// UserDetails 오버라이딩
// 해당 User의 권한을 리턴하는 곳!!
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return user.getRole();
}
});
return collection;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
// 우리 사이트에서 1년동안 회원이 로그인을 안하면, 휴면 계정으로 하기로 함.
// 현재시간 - 로그인시간 => 1년을 초과하면 return false
return true;
}
// OAuth2User 오버라이딩
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public String getName() {
return null;
}
}