Authorization Code Grant 방식 뿌시기
1 단계 임시 코드 발급
OAuth2AuthorizationRequestRedirectFilter
위 필터의 책임이=
OAuth2AuthorizationRequestResolver
을 통해 HttpServletRequest
를 OAuth2AuthorizationRequest
로 resolve 한 결과를 처리한다.→ resolving 이 성공했다면 redirect 를 코드와 함께 시키고 (response.sendRedirect())
→ resolving 중 예외가 발생했다면 response.sendError()
try { //"/oauth2/authorization/{registrationId}" 를 만족하는 요청 이라면 resolving 시도 //AuthorizationRequestResolver 를 활용해 HttpServletRequest 를 OAuth2AuthorizationRequest 로 resolving OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request); // resolveing 에 성공했다면 if (authorizationRequest != null) { // authorizationRequest 정보를 통해 response.sendRedirect 호출 // authorizationRequest 에는 redirectUri,scope, state, 등의 정보가 담긴다. // 이때 AuthorizationGrantType 이 Authorization_CODE 타입 이라면 // authorizationRequest 저장소에 요청정보를 저장한다. //authorizationRequest 의 기본 구현체는 HttpSessionOAuth2AuthorizationRequestRepository 이다. this.sendRedirectForAuthorization(request, response, authorizationRequest); return; } } catch (Exception ex) { this.unsuccessfulRedirectForAuthorization(request, response, ex); return; } try { filterChain.doFilter(request, response); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Check to see if we need to handle ClientAuthorizationRequiredException Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex); ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer .getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain); if (authzEx != null) { try { OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request, authzEx.getClientRegistrationId()); if (authorizationRequest == null) { throw authzEx; } this.sendRedirectForAuthorization(request, response, authorizationRequest); this.requestCache.saveRequest(request, response); } catch (Exception failed) { this.unsuccessfulRedirectForAuthorization(request, response, failed); } return; } if (ex instanceof ServletException) { throw (ServletException) ex; } if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new RuntimeException(ex); }
resolving 에성공한
OAuth2AuthorizationRequest

2. DefaultOAuth2AuthorizationRequestResolver
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { // registrationId 가 null 이면 fail if (registrationId == null) { return null; } // clientRegistration 저장소에서 registrationId 로 ClientRegistration 정보 조회 ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); if (clientRegistration == null) { throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId); } // OAuth2AuthorizationRequest 빌더를 통해 OAuth2AuthorizationRequest 생성 후 반환 OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration); String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction); // @formatter:off builder.clientId(clientRegistration.getClientId()) .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) .redirectUri(redirectUriStr) .scopes(clientRegistration.getScopes()) .state(DEFAULT_STATE_GENERATOR.generateKey()); // @formatter:on this.authorizationRequestCustomizer.accept(builder); return builder.build(); }

2 단계 Access Token 교환하기
OAuth2LoginAuthenticationFilter
- login/oauth2/code/{registrationId} 에 대한 요청을 처리한다.
- 이에 대한 처리는 AbstractAuthenticationProcessingFilter 에서 처리 된다.
- AbstractAuthenticationProcessingFilter 에서는 인증 적용 유무에 대한 필터링, 인증 결과의 성공, 실패 에 따른 후처리를 템플릿 메서드 패턴으로 제공한다.
- 실질적인 인증 프로세스는 아래의
OAuth2LoginAuthenticationFilter.attemptAuthenticate
메서드에 정의된다.

// 쿼리 파라미터를 추출한다. MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap()); if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) { OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } // 이전 단계 (1단계) 에서 저장한 인가 요청 정보를 제거한다. OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request, response); // 제거된 결과가 없으면 오류이다. if (authorizationRequest == null) { OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } //clientRegistrationRepository 에서 registrationId 를 통해 ClientRegistration를 조회한다. String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID); ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId); if (clientRegistration == null) { OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE, "Client Registration not found with Id: " + registrationId, null); throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()); } //redirectUri 를 생성하고 params(code, state 를 추가한 OAuth2AuthorizationResponse를 생성한다.) // @formatter:off String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) .replaceQuery(null) .build() .toUriString(); // @formatter:on OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,redirectUri); Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request); // clientRegistration, 인가요청, 인가 응답 정보를 가지는 OAuth2LoginAuthenticationToken를 생성한다. // 아직 인증 되지 않음 OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse)); authenticationRequest.setDetails(authenticationDetails); // authenticationManager ( = ProviderManager) 를 통해 인증 요청을 처리한다. // 이제 인증 됨 OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this .getAuthenticationManager().authenticate(authenticationRequest); // 인증된 토큰을 OAuth2AuthenticationToken 로 변환한다. OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter .convert(authenticationResult); Assert.notNull(oauth2Authentication, "authentication result cannot be null"); oauth2Authentication.setDetails(authenticationDetails); //OAuth2AuthorizedClient 에 인가된 클라이언트 정보를 저장한다. OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient( authenticationResult.getClientRegistration(), oauth2Authentication.getName(), authenticationResult.getAccessToken(), authenticationResult.getRefreshToken()); this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response); // 토큰을 반환하면 부모 (template 메서드) 에서 SecurityContextHolder 에 저장하는 등의 후처리를 수행한다. return oauth2Authentication;
OAuth2LoginAuthenticationProvider
OAuth2 로그인 인증 제공자
ProviderManager
가 사용하는 AuthenticationProvider
중 한 구현체내부에
OAuth2AuthorizationCodeAuthenticationProvider
를 가진다.scope 에 open_id 가 포함되어 있으면
OidcAuthorizationCodeAuthenticationProvider
가 처리하도록 한다.OAuth2AuthorizationCodeAuthenticationProvider
- 권한 코드 부여 흐름을 처리 하는 AuthenticationProvider
- 기본 구현 체 : DefaultOAuth2AuthorizationCodeAuthenticationProvider
- RestTemplate 을 통해 Authorization Code 와 AccessToken 의 교환을 담당한다.
- 이를 OAuth2AccessTokenResponse 에 저장하고 반환한다.
DefaultOAuth2UserService
- 받아온 AccessToken 을 이용해 ClientRegistration 내부의 UserInfo Uri 를 통해 실질적인 UserInfo 를 담아와 OAuth2User 를 생성한다.