
OAuth 2.0 Authorization Code Flow 과정 중 아래 과정 살피기
- Client Server 는 인증된 사용자의 Access Token 을 사용하여 Resource Server 에 요청을 보낸다. Resource Server 는 전송된 Access Token 을 확인하고, 유효한 경우 Resoure Request 에 대한 응답을 반환한다.
- Client Server 는 Resource Server 로부터 받은 Resoure Response 를 이용하여 Client Server 내에서 인증 작업을 수행한다.
- 이러한 과정을 통해 Client Application 은 OAuth 2.0 Authorization Server 로부터 얻은 Access Token 을 사용하여 Resource Server 에 접근하고, 인증된 사용자의 정보나 기타 필요한 리소스를 획득할 수 있다.
지난 포스팅 때 살펴본 OAuth2LoginAuthenticationProvider 클래스의 authenticate 메서드에서 OAuth2UserService 의 loadUser 메서드를 통해 위의 작업이 수행된다.
여기서 우리는
1. OAuth2UserService 인터페이스를 구현하는 CustomOAuth2UserService 클래스를 직접 작성한다.
2. OAuth2AuthenticationSuccessHandler 클래스의 oAuth2AuthenticationSuccessHandler 메서드를 직접 작성한다.
- Resource Server 로부터 획득한 정보를 이용하여 회원을 생성하거나 업데이트하는 작업을 수행한다. Authentication 의 Principal 를 구성하는 OAuth2User 객체에 대해 직접 설정하는 데에 목적이 있다.
- 모든 인증이 성공적으로 끝마친 후에 최종적으로 JWT 발급을 위한 목적이 있다.
@Getter
@Builder
public class PrincipalDetails implements UserDetails, OAuth2User {
private final String username;
private final String password;
private final String authority;
private Map<String, Object> attributes;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton((GrantedAuthority) () -> authority);
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
/* OAuth2User 인터페이스의 메서드들 */
@Override
public Map<String, Object> getAttributes() { return attributes; }
@Override
public String getName() { return username; }
}
✨ PrincipalDetails Class
- PrincipalDetails 클래스는 Spring Security 의 UserDetails 및 OAuth2User 인터페이스를 구현한 클래스이다.
- 기본 로그인 방식으로 사용자를 인증하면 UserDetails 를 사용한다.
- OAuth 2.0 로그인 방식으로 사용자를 인증하면 OAuth2User 를 사용한다.
- OAuth2LoginAuthenticationFilter 에 의해 SecurityContextHolder 의 SecurityContext 의 Principal 에 저장되어 전역적으로 접근 가능하다. 추후 인증된 사용자의 정보를 얻거나 수정하는 데 사용된다.
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final UserRepository userRepository;
private final CartRepository cartRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = new DefaultOAuth2UserService().loadUser(userRequest);
OAuth2Attribute attributes = OAuth2Attribute.of(
userRequest.getClientRegistration().getRegistrationId(),
oAuth2User.getAttributes()
);
User user = updateOrCreateUser(attributes);
return createOAuth2User(user, attributes);
}
private User updateOrCreateUser(OAuth2Attribute attributes) {
Optional<User> optionalUser = userRepository.findByEmail(attributes.getEmail());
if (optionalUser.isPresent()) {
User existingUser = optionalUser.get();
existingUser.setName(attributes.getName());
existingUser.setPicture(attributes.getPicture());
return userRepository.save(existingUser);
}
User newUser = userRepository.save(attributes.toEntity());
newUser.setCart(cartRepository.save(
Cart.builder()
.user(newUser)
.build()));
return newUser;
}
private OAuth2User createOAuth2User(User user, OAuth2Attribute attributes) {
return PrincipalDetails.builder()
.username(String.valueOf(user.getId()))
.authority(user.getAuthority().toString())
.attributes(attributes.getAttributes())
.build();
}
}
✨ CustomOAuth2UserService Class
💡 loadUser Method
- OAuth 2.0 플로우에서 사용자 정보를 로드하는 데 사용되는 메서드이다.
- OAuth 2.0 Authorization Server 에서 얻은 Access Token 을 기반으로 사용자 정보를 가져오는 역할을 담당한다.
- 중요한 파라미터는 OAuth2UserRequest 객체이다. OAuth 2.0 Resource Request 를 위한 필요한 정보를 포함한다.
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest( loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
- DefaultOAuth2UserService 클래스의 loadUser 메서드에 Access Token 을 이용하여 OAuth2User 정보를 가져오는 로직이 구현되어 있다.
OAuth2User oAuth2User = new DefaultOAuth2UserService().loadUser(userRequest);
- OAuth2Attribute 클래스의 of 메서드를 사용하여 OAuth2User 객체의 Attributes 에 관한 정보를 새롭게 생성한 OAuth2Attribute 객체로 재구성한다. 필요한 속성 정보를 쉽게 가져오기 위한 목적이 있다.
OAuth2Attribute attributes = OAuth2Attribute.of( userRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes() );
- 주어진 OAuth2Attribute 에서 사용자를 찾아 업데이트하거나 새로 만든다.
- 먼저 주어진 이메일로 사용자를 데이터베이스에서 찾는다. 사용자가 이미 존재하는 경우 해당 사용자를 업데이트하고, 존재하지 않는 경우 새로운 사용자를 생성한다.
User user = updateOrCreateUser(attributes);
- OAuth2User 를 구현하는 PrincipalDetails 객체를 생성하고 반환한다.
return createOAuth2User(user, attributes);따라서 OAuth2UserService의 loadUser 메서드는 이러한 정보를 기반으로 실제 사용자 정보를 로드하고, 그 결과로 OAuth2User 객체를 반환한다.
@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication
) throws IOException, ServletException {
TokenInfo tokenInfo = jwtTokenProvider.generateTokenInfo(authentication, response);
String targetUrl = UriComponentsBuilder.fromUriString("http://www.tr1ll1on.site/oauth2/redirect")
.queryParam("accessToken", tokenInfo.getAccessToken())
.build()
.encode(StandardCharsets.UTF_8)
.toUriString();
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
}
✨ OAuth2AuthenticationSuccessHandler Class
- OAuth 2.0 인증 성공 시 최종적으로 호출되는 핸들러이다. 사용자의 인증이 성공하면 onAuthenticationSuccess 메서드가 실행되고, JWT 토큰을 생성하고 응답으로 반환한다.
- JwtTokenProvider 를 사용하여 사용자의 인증 정보로부터 JWT 토큰을 생성한다.
- UriComponentsBuilder 를 사용하여 리다이렉트할 URL을 생성한다. accessToken 을 쿼리 파라미터로 추가한다.
- 최종적으로 RedirectStrategy 를 사용하여 생성된 URL로 리다이렉트한다.
✨ 필수 작업 ✨
SecurityFilterChain 에서 oauth2Login 메서드를 사용하여 OAuth 2.0 로그인 설정을 해야한다.