스프링 시큐리티는 인증된 정보를 Authentication으로 감싸서 Security Context Holder에 보관해 놓는다.
아래와 같이 메서드 파라미터로 Authentication을 받거나, @AuthenticationPrincipal 을 부착한
UserDetails 구현체를 받으면(PrincipalDetails implements UserDetails) 로그인한 유저의 세션 정보를 얻을 수 있다.
참고로 Authentication 객체의 getPrincipal()의 리턴 타입은 Object다.
@Controller
public class IndexController {
	@GetMapping("/test/login")
    public @ResponseBody
    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 "세션 정보 확인하기";
    }
}
로그를 보면, 결과가 똑같다.
authentication : User(id=1, username=ssar, password=$2a$10$2qissCjGbv8EBCpbp.bfyusrCakaSgeOLnzDTDuHTTpexp5FUJIxq, email=ssar@naver.com, role=ROLE_USER, provider=null, providerId=null, createDate=2021-12-30 11:11:59.0)
userDetails:User(id=1, username=ssar, password=$2a$10$2qissCjGbv8EBCpbp.bfyusrCakaSgeOLnzDTDuHTTpexp5FUJIxq, email=ssar@naver.com, role=ROLE_USER, provider=null, providerId=null, createDate=2021-12-30 11:11:59.0)
OAuth 로그인 유저의 세션 정보를 얻는 방법은 약간 다르다.
OAuth 로그인 유저의 경우 UserDetails 구현체로 다운 캐스팅하면 에러가 난다.
대신, OAuth2User로 다운 캐스팅해야 에러가 발생하지 않고 세션 정보를 얻을 수 있다.
이 역시 로그를 보면 결과가 같다. (보안상 이유로 생략)
@Controller
public class IndexController {
		@GetMapping("/test/oauth/login")
    public @ResponseBody
    String testOauthLogin(Authentication authentication, @AuthenticationPrincipal OAuth2User oauth) {
        System.out.println("/test/login======");
        OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); // Oauth 의 경우엔 OAuth2User 로 다운 캐스팅해야 에러 안난다.
        System.out.println("authentication : " + oAuth2User.getAttributes());
        System.out.println("oauth : "+oauth.getAttributes());
        return "oauth 세션 정보 확인하기";
    }
}
인증 정보를 담는 Authentication은 UserDetails 구현체 또는 OAuth2User 구현체를 가질 수 있다.
전자의 경우 일반 로그인 유저의 인증 정보를 담고, 후자의 경우는 OAuth 로그인 유저의 인증정보를 담는다.

하지만 이로 발생할 수 있는 문제가 있다.
로그인 방법마다 인증 정보를 담는 객체의 타입이 다르다면, 컨트롤러의 메서드마다 두 구현체를 파라미터로 받아야 할까?
다음과 같이 로직은 똑같은데, 로그인 방법에 따라 파라미터를 달리 해야한다면 유지보수 상에 엄청난 비용이 따른다.
public doSomething(@AuthenticationPrincipal UserDetials userDetails){}
public doSomething(@AuthenticationPrincipal OAuth2User oauth){}
해결책
UserDetials 와 OAuth2User를 모두 implements 한 클래스를 만들고, 이를 사용하면 된다.
public class PrincipalDetails implements UserDetails, OAuth2User {
    private final User user;//합성을 이용
    private Map<String,Object>attributes;
    //일반 로그인 시 사용되는 생성자
    public PrincipalDetails(User user) {
        this.user = user;
    }
    //Oauth 로그인 시 사용되는 생성자.
    public PrincipalDetails(User user, Map<String, Object> attributes) {
        this.user = user;
        this.attributes = attributes;
    }
    //해당 User 의 권한을 리턴하는 메서드
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> collect = new ArrayList<>();
        collect.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return user.getRole();
            }
        });
        return collect;
    }
    @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() {
        return true;
    }
    //아래부터 Oauth 관련 메서드. OAuth2User 오버라이드 메서드임.
    @Override
    public Map<String, Object> getAttributes() {
        return attributes;
    }
    //Oauth 의 기본키 getter 메서드. 잘 안씀.
    @Override
    public String getName() {
        return null;
    }
}
이렇게 하면, 로그인 방식에 따라 메소드를 여러개 만들 필요가 없어진다. 예를 들면 다음과 같다.
    @GetMapping("/user")
    public @ResponseBody
    String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
        return "user";
    }