google이나 naver에서 받아온 정보를 담고 있는 기본 OAuth2User 안에는 email, name, profile_image같은 정보만 들어있다.
그런데 사용자가 로그인을 성공한 직후, 서비스는 SuccessHandler에서 전용 AccessToken과 RefreshToken을 만들어서 브라우저로 내려주어야 한다. (그래야지만, 이후에 요청이 왔을 경우, 이 AccessToken으로 내 회원인지 판단을 할 수 있기 때문)
이때 AccessToken의 Payload에는 다음과 같은 정보가 들어간다.
member_idROLE_USER여기서 문제는 super.loadUser(userRequest)로 받아온 회원 정보(OAuth2User)를 그냥 리턴해버리면 내가 생성한 member_id나 Role 정보가 존재하지 않는다.
따라서 CustomOAuth2MemberService의 마지막 부분에서, 방금 DB에 저장한 Member객체로부터 id와 Role을 뽑아낸 뒤, 이름 담을 수 있는 바구니(CustomOAuth2User)를 만들어서 핸들러로 넘겨주는 것이다.
CustomOAuth2MemberService: 구글에서 온 로그인이 성공했네. DB에 저장하니까 이 회원 PK는 5번이고 권한은 USER야. 이 정보를 CustomOAuth2User 객체에 담아서 리턴할게.OAuth2LoginSuccessHandler: CustomOAuth2MemberService에서 넘겨준 객체를 받아보니 CustomOAuth2User네? 여기서 .getPrincipal()을 통해 PK 5번이랑 USER 정보만 빼서 AccessToken로 브라우저한테 줘야겠다.소셜 로그인 성공
↓
CustomOAuth2MemberService.loadUser()
→ DB 저장/조회
→ CustomOAuth2User 반환 (memberId, email, role 포함)
↓
OAuth2LoginSuccessHandler.onAuthenticationSuccess()
→ CustomOAuth2User에서 정보 추출
→ AccessToken 생성 → Body로 클라이언트 전달 (메모리 저장)
→ RefreshToken 생성 → HttpOnly 쿠키로 전달 + DB 저장
↓
이후 API 요청
→ 클라이언트가 Header에 "Authorization: Bearer {accessToken}" 첨부
→ JwtFilter가 토큰 파싱 → payload로 회원 식별 (DB 조회 없음)
↓
AccessToken 만료 시
→ 쿠키의 RefreshToken으로 재발급 요청
→ DB의 RefreshToken과 비교 검증 후 AccessToken 재발급
CustomOAuth2User - SuccessHandler가 JWT를 생성하기 위해 (id, Role) 정보를 여기서 꺼낸다.
@Getter
@RequiredArgsConstructor
public class CustomOAuth2User implements OAuth2User {
private final Long userId;
private final Role role;
private final String nickName;
@Override
public String getName() {
return nickName;
}
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collection = new ArrayList<>();
collection.add(new GrantedAuthority() {
@Override
public String getAuthority() {
return "ROLE_" + role.name();
}
});
return collection;
}
}
getAttributes()가 null을 리턴하는 이유
getAttributes()의 역할은 OAuth2Response에 담은 Resource Server가 제공한 회원 정보가 담겨있다. 세션 방식을 사용할 때는 유용하다.
하지만 JWT 방식을 사용할 때는 userId와 Role만 필요하기에, 딱히 필요하지 않다.
getAuthorities()
인증을 마치면 해당 collection에 담겨져 있는 권한들을 이용해 JWT로 만들고 이후 요청이 왔을 때 payload에 담긴 내용들을 통해 collection을 재조립하여 Authentication 객체에 담는다. 그리고 나서 @PreAuthrorize 어노테이션을 통해 권한이 있는 회원인지 판단한다.
※ @PreAuthorize를 사용하려면 SecurityConfig 클래스 상단에 @EnableMethodSecurity 어노테이션을 추가해야한다.
getAuthorities()에서 굳이 “ROLE_”을 붙이는 이유
나중에 컨트롤러에서 특정 API을 일반 유저만 쓸 수 있게 막으려면, @PreAuthorize어노테이션을 쓰게 된다.
@PreAuthorize("hasRole('USER')")
@GetMapping("/backtest")
public String backtest(){...}
이때, Spring Security는 hasRole('USER')를 보는 순간, “이 URL은 USER 권한을 갖고 있는 사람만이 사용 가능하구나. 회원의 신분증을 뒤져서 ROLE_USER라는 글자가 있는지 찾아봐야지”. 라고 판단한다. 만약 이때 getAuthorities()에서 그냥 "USER"라고만 넘겨주었다면 Security는 “ROLE_USER”가 없다라고 생각하여, 에러를 뱉어버린다. (오래된 관습. 꼭 지켜야할 규칙)
OAuth2SuccessHandler