사이드 프로젝트 중 회원 쪽을 맡아서 진행중 OAuth2 를 구현하고 있습니다. 잊어버리기 전에 SpringSecurity Filter 중 하나인 OAuth2UserService 에서 처리 방법을 정리 해보려 합니다. 자세한 내용은 추후에 올릴 예정입니다. 여기서는 간략히!
여기서는
Google 에서 Access Token 을 받을 때 까지를 기록하고 있습니다.
Front 와 Back 을 완전히 분리하기 위함입니다.
구글 OAuth2 를 예시로 들 예정입니다. 구글 서비스에 OAuth 등록을 완료하고
생성한 구글 서비스에서 필요한 값들을 서버에 완전히 등록한 이후 입니다.
<a href="/oauth2/authorization/google">구글</a>
SecurityFilterChain 에서 다음과 같이 설정하여 소셜 로그인을 사용 시
사용자가 지정한 Service 가 작동되도록 합니다
@Bean
public SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
return http....
.oauth2Login()
.userInfoEndpoint().userService(oAuthService).and().permitAll()
.successHandler(oAuth2SuccessHandler)
.failureHandler(oAuth2FailureHandler)
.and()
....
OAuth2UserService를 구현하며 SpringSecurityFilterChain 에 OAuth2 요청 시
작동하도록 사용자가 등록한 서비스 입니다.
@Service
@RequiredArgsConstructor
@Log4j2
public class OAuthService implements OAuth2UserService<OAuth2UserRequest, OAuth2User>
{
private final AuthRepository authRepository;
private final AuthTokenService authTokenService;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException
{
log.info("OAuthService Operation");
OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService();
OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest);
log.info("oAuth2User : {}",oAuth2User.toString());
2023-11-23 08:43:56.750 ...OAuthService : OAuthService Operation
2023-11-23 08:43:56.863 ...OAuthService : oAuth2User : Name: [**],
Granted Authorities: [[OAUTH2_USER, SCOPE_https://www.googleapis.com/auth/userinfo.email,
SCOPE_https://www.googleapis.com/auth/userinfo.profile, SCOPE_openid]],
User Attributes: [{sub=****, name==****, given_name==****, family_name==****, picture==****, email==****,
email_verified=true, locale=ko}] Hibernate:
OAuthService( SpringSecurity Filter Chain 에 추가한 class ) 로
OAuth 로그인 이후의 결과 값을 얻을 수 있다는 것이 확인 되었습니다.
public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
...
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
if (!StringUtils
.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
"Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
if (!StringUtils.hasText(userNameAttributeName)) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
+ userRequest.getClientRegistration().getRegistrationId(),
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
SpringSecurity 에서 제공하는 DefaultOAuth2UserService 의 loadUser 에서 이미 accessToken 을 사용하는 것을 확인할 수 있습니다.
자체적으로 행하는 다음 로직을 내부적으로 실행 중임으로 조금 더 분석해 보면 Front 와 Back 의 분리를 할 수 있을 것 같습니다