1. 서론
OAuth(Open Authorization)은 2006년 최초 출시된 인증을 위한 개방형 표준 프로토콜이다. 최초 출시 시기인 2006년 트위터와 Ma.gnolia가 주도하여 1.0버전을 개발하였으며, 이후 더욱 개선된 1.0a버전도 출시되었지만 일부 어플리케이션에서 보안성을 확보하지 못하는 사례가 존재했다. 기존보다 보안성을 강화하고 단순화시킨 2.0버전이 2012년에 등장하게 되었고, 지금까지 여러 어플리케이션에서 아주 유용하게 사용되고 있는 프로토콜이라 할 수 있겠다.
OAuth가 등장하기 이전에는, 특정 서비스에서 다른 서비스의 기능을 이용할 수 있게 하기 위해 다음과 같이 아주 위험한 방법을 사용하고 있었다. 어떤 회원이 현재 이용하고 있는 서비스를 A, A를 통해 이용하고 싶은 서비스를 B라고 하자.
- A 서비스에게 B 서비스의 회원 정보를 "직접" 제공
- A 서비스는 제공받은 회원 정보를 토대로 B 서비스에 접근
- B 서비스는 이에 따라 A 서비스에게 기능 제공
- A 서비스는 이를 이어 받아 회원에게 기능 제공
일단 이는 합리적이지 못하다. 먼저 B 서비스가 기존에 사용자의 정보를 관리하기 위해 가지고 있던 부담감이 고스란히 A 서비스에게도 전달된다. 만약 이러한 시스템을 다른 여러 서비스가 채택할수록 굉장히 비효율적인 체계로 변질된다. 또한 이 말은, 한 쪽 서비스에서 정보가 유출될 경우 보안적으로 굉장히 취약해질 수 밖에 없는 구조를 가지고 있다. 아울러 B 서비스는 A 서비스에게 자신들의 회원 정보를 일방적으로 맡기게 되는 것이기 때문에 불안할 수 밖에 없다.
이를 해결하기 위해 나타난 개념이자 프로토콜이 OAuth이다. OAuth는 타사 플랫폼의 회원 데이터에 접근하기 위해 Client(우리의 서버)에게 데이터에 대한 접근 권한을 위임해줄 수 있게 한다. 따라서 개발자는 이를 활용하여 우리의 서비스가 타 플랫폼으로부터 권한을 위임받아 다양한 서비스 제공을 할 수 있도록 하는 기능을 실현시킬 수 있다.
본 프로젝트는 Google 플랫폼에 대한 OAuth 서비스를 구현하였으며, 이는 플랫폼 별로 다소 상이할 수 있다. Spring boot로 구현한 OAuth 로그인 기능을 코드와 함께 살펴보자.
2. Spring boot Application OAuth 2.0 설정
우리는 Spring boot 어플리케이션에 대한 기본적인 설정을 application.yml 또는 .properties 환경 파일에 기재하여 설정한다. Spring security의 OAuth 2.0에 대한 설정도 마찬가지인데, 그렇다면 Spring security는 이러한 기본적인 설정을 가지고 어떻게 OAuth 2.0 프로토콜에 대한 프로세스를 진행시키는걸까?
...
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@Conditional(ClientsConfiguredCondition.class)
class OAuth2ClientRegistrationRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean(ClientRegistrationRepository.class)
InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
new OAuth2ClientPropertiesMapper(properties).asClientRegistrations().values());
return new InMemoryClientRegistrationRepository(registrations);
}
}
...
@ConfigurationProperties(prefix = "spring.security.oauth2.client")
public class OAuth2ClientProperties implements InitializingBean {
private final Map<String, Provider> provider = new HashMap<>();
private final Map<String, Registration> registration = new HashMap<>();
public Map<String, Provider> getProvider() {
return this.provider;
}
public Map<String, Registration> getRegistration() {
return this.registration;
}
...
public static class Registration {
private String provider;
private String clientId;
private String clientSecret;
...
spring:
...
security:
oauth2:
client:
registration:
google:
client-id: ...
client-secret: ...
scope:
- email
- profile
OAuth2ClientRegistrationRepositoryConfiguration.class에는 @EnableConfigurationProperties(OAuth2ClientProperties.class) 어노테이션이 존재하는데, 이 어노테이션으로 .yml 설정 파일의 데이터가 (OAuth2ClientProperties.class)에 명시된 class에 바인딩된다. 따라서 OAuth2ClientProperties.class 파일에 @ConfigurationProperties를 사용할 수 있으며, prefix에 적혀 있는 spring.security.oauth2.client이 .yml 설정 파일과 대응된다. 이로 인해 OAuth2ClientProperties의 필드 변수인 provider와 registration의 key 값으로 플랫폼(google, naver 등) 명이, 각각의 value 값으로는 Provider 객체와 Registration 객체가 놓인다. 그리고 Regisration 객체의 필드 변수인 clientId, clientSecret에 우리가 설정한 client-id, client-secret 데이터가 바인딩되는 것이다.
또한 OAuth2ClientRegistrationRepositoryConfiguration.class는 InMemoryClientRegistrationRepository를 bean 주입시킨다. InMemoryClientRegistrationRepository에는 각 OAuth 2.0 서버 별로 OAuth2ClientProperties 설정 값이 반영된 ClientRegistration 객체들(registrations)이 저장된다.
...
public final class OAuth2ClientPropertiesMapper {
private final OAuth2ClientProperties properties;
public OAuth2ClientPropertiesMapper(OAuth2ClientProperties properties) {
this.properties = properties;
}
public Map<String, ClientRegistration> asClientRegistrations() {
Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
this.properties.getRegistration()
.forEach((key, value) -> clientRegistrations.put(key,
getClientRegistration(key, value, this.properties.getProvider())));
return clientRegistrations;
}
...
...
public final class InMemoryClientRegistrationRepository
implements ClientRegistrationRepository, Iterable<ClientRegistration> {
private final Map<String, ClientRegistration> registrations;
...
registrations는 OAuth2ClientPropertiesMapper의 asClientRegistrations() 메서드는 Map 객체를 반환하고 그 value들을 가져와 리스트에 담아 만들어진다. 이후 registrations는 InMemoryClientRegistrationRepository의 필드 변수에 Map 객체로 저장이 되고, 이 객체가 bean 등록이 되는 것이다.
즉, Spring boot 어플리케이션이 실행되면 .yml 설정 파일로부터 OAuth2ClientProperties가 생성되고, 이를 기반으로 OAuth2ClientPropertiesMapper를 통해 각 OAuth 서버마다의 ClientRegistration를 생성하여 List에 담는다. 이후 이를 Map 객체로 InMemoryClientRegistrationRepository에 저장한다. 따라서 Bean 등록된 InMemoryClientRegistrationRepository에 저장된 ClientRegistration 정보로 OAuth 로그인 요청 시 ClientRegistration를 가져와 로그인 프로세스에 사용한다.
3. 로그인 요청 시
Spring boot 어플리케이션에 대해 Google OAuth 설정을 마치면, "/oauth2/authorization/{registrationId}"로 해당 Spring boot API 서버에서 Endpoint가 생성된다. 이 Endpoint는 어디서 생성 내지는 처리되는 것인가?
SecurityConfig.java
...
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenProvider jwtTokenProvider;
private final JwtCookieProvider jwtCookieProvider;
private final UrcarcherOAuth2Service urcarcherOAuth2Service;
private final OAuth2SuccessHandler oAuth2SuccessHandler;
private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
private static final String[] BLACK_LIST = {"/api/paymentPlace/**, /api/exchange/**, /api/reserve/**"};
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
...
// OAuth 로그인에 대한 흐름 설정
.oauth2Login(oauth2Config -> {
oauth2Config
// Backend가 다중 서버로 구성될 경우 필요한 설정, OAuth 인증 과정에서 사용되는 여러 파라미터를 쿠키로 저장하여 진행시킴
.authorizationEndpoint(authEndPoint -> {
authEndPoint.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository);
})
// 로그인 성공 시 urcarcherOAuth2Service에서 제공받은 유저 정보를 처리함
.userInfoEndpoint(load->load.userService(urcarcherOAuth2Service))
// 로그인 성공 시 수행해야할 필요한 로직을 처리함
.successHandler(oAuth2SuccessHandler);
})
// UsernamePasswordAuthenticationFilter보다 앞 순위에 필터를 배치한다는 뜻, 우리는 이 필터가 비활성화 돼 있으므로
// 실질적으로 사용자는 JwtAuthenticationFilter를 거치게 됨.
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, jwtCookieProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
...
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
...
public HttpSecurity oauth2Login(Customizer<OAuth2LoginConfigurer<HttpSecurity>> oauth2LoginCustomizer)
throws Exception {
oauth2LoginCustomizer.customize(getOrApply(new OAuth2LoginConfigurer<>()));
return HttpSecurity.this;
}
...
@SuppressWarnings("unchecked")
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
throws Exception {
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
return apply(configurer);
}
...
우리는 filterChain을 bean 등록하며 HttpSecurity를 설정하게 된다. oauth2Login()이 활성화되면, OAuth2LoginConfigurer가 생성된다. HttpSecurity는 AbstractConfiguredSecurityBuilder를 상속받으며, 여기서 LinkedHashMap으로 configurers를 필드 변수로 갖는다. getOrApply() 메서드는 해당 configurer가 configurers에 존재하지 않을 때에만 configurers에 추가한다. apply() 메서드는 add() 메서드로 연결되어 configurers에 해당 configurer를 추가한다(두 메서드 전부 AbstractConfiguredSecurityBuilder에 있음).
...
public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
extends AbstractAuthenticationFilterConfigurer<B, OAuth2LoginConfigurer<B>, OAuth2LoginAuthenticationFilter> {
...
@Override
public void init(B http) throws Exception {
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
this.setAuthenticationFilter(authenticationFilter);
...
@Override
public void configure(B http) throws Exception {
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter;
if (this.authorizationEndpointConfig.authorizationRequestResolver != null) {
authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
this.authorizationEndpointConfig.authorizationRequestResolver);
}
...
http.addFilter(this.postProcess(authorizationRequestFilter));
...
super.configure(http);
}
...
SecurityFilterChain bean이 주입될 때(SecurityConfig.java), http.build()가 실행되면서 doBuild()가 실행된다.(5. AbstractConfiguredSecurityBuilder & AbstractSecurityBuilder 참고) 이 과정에서 http가 가지고 있는 configurers가 모두 자신들만의 초기화(init())와 구성(configure()) 과정을 실행한다. configurers 중 하나인 OAuth2LoginConfigurer도 마찬가지인데, OAuth2LoginConfigurer의 configure() 메서드가 실행되며 OAuth2AuthorizationRequestRedirectFilter 객체가 필터 체인에 추가된다. 바로 이 OAuth2AuthorizationRequestRedirectFilter가 "/oauth2/authorization/{registrationId}"로 온 요청을 처리한다.
...
public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilter {
public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization";
...
private OAuth2AuthorizationRequestResolver authorizationRequestResolver;
...
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
if (authorizationRequest != null) {
this.sendRedirectForAuthorization(request, response, authorizationRequest);
return;
}
}
...
}
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
OAuth2AuthorizationRequest authorizationRequest) throws IOException {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
}
this.authorizationRedirectStrategy.sendRedirect(request, response,
authorizationRequest.getAuthorizationRequestUri());
}
...
...
public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
...
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
String registrationId = resolveRegistrationId(request);
if (registrationId == null) {
return null;
}
String redirectUriAction = getAction(request, "login");
return resolve(request, registrationId, redirectUriAction);
}
...
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId,
String redirectUriAction) {
if (registrationId == null) {
return null;
}
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
throw new InvalidClientRegistrationIdException("Invalid Client Registration with Id: " + registrationId);
}
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();
}
...
OAuth2AuthorizationRequestRedirectFilter의 필드에는 생성자의 매개 변수로 받은 OAuth2AuthorizationRequestResolver가 set되어 있다. OAuth2AuthorizationRequestRedirectFilter의 필터링을 거치면(doFilterInternal()), OAuth2AuthorizationRequestResolver의 구현체인(개발자가 따로 커스터마이징하지 않았을 시에) DefaultOAuth2AuthorizationRequestResolver resolve() 메서드로 "/oauth2/authorization/{registrationId}"로 온 요청에서 registrationId를 추출한 후, 이를 통해 InMemoryClientRegistrationRepository의 findByRegistrationId() 메서드로 2. Spring boot Application OAuth 2.0 설정에서 set up된 ClientRegistration 객체를 가져온다. 여기서 OAuth2AuthorizationRequest가 반환되며, OAuth2AuthorizationRequestRedirectFilter는 sendRedirectForAuthorization() 메서드에 이 OAuth2AuthorizationRequest 객체를 전달하고 호출하여 User를 redirect 시키게 된다. 즉, 사용자는 위 과정을 통해 clientId와 redirectUri 등을 가지고, 플랫폼 별 아이디, 비밀번호를 입력할 수 있는 웹 페이지로 이동되는 것이다.
따라서 개발자는 이를 기반으로 두고 프론트(React) 단에서 해당하는 URL 주소로 구글 로그인을 위한 page로 redirect 시킬 수 있다.
Login.jsx
...
const googleOauth2Handler = () => {
window.location.href = process.env.REACT_APP_GOOGLE_OAUTH2_HANDLING;
};
...
<button className="btn btn-primary btn-lg oauth" onClick={googleOauth2Handler}>
<img className="logo" src={googleLogo} alt="Google Logo"/>
Google {t('Login2')}
</button>
...
그러면 우리가 다른 서비스에서 흔히 접할 수 있던 구글 로그인 page를 볼 수 있다.
4. 소셜 로그인 처리
API 서버가 구글 리소스 서버로부터 유저 정보를 제공 받는 과정은 다음과 같다.
OAuth2AuthorizationRequestRedirectFilter로 인증 대행 서버에 redirect되고 난 뒤 아이디 및 비밀번호 입력 후 정상 인증이 되면,(위 그림 2. 인증 대행 서버에 인증 요청 과정) 인증 대행 서버는 로그인 요청 시 url에 담겨 있던 redirect-uri로 Authorization code를 발급하여 API 서버(Spring boot)에 request를 보낸다.(위 그림 3. code 발급 과정) 즉, request url에 Authorization code가 담겨 있다. 바로 이 과정에서 발생하는 request를 OAuth2LoginAuthenticationFilter가 처리한다.
이 필터 역시 마찬가지로 상기한 OAuth2LoginConfigurer에 의해 생성되고 필터 체인에 등록된다. OAuth2LoginConfigurer의 초기화(init()) 과정을 보면 OAuth2LoginConfigurer가 상속하는 AbstractAuthenticationFilterConfigurer의 필드 변수인 authFilter에 OAuth2LoginAuthenticationFilter가 담기게 되고, configure() 메서드가 실행되면서 HttpSecurity의 filter 목록에 OAuth2LoginAuthenticationFilter가 추가된다.
...
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter>
extends AbstractHttpConfigurer<T, B> {
...
@Override
public void configure(B http) throws Exception {
...
F filter = postProcess(this.authFilter);
http.addFilter(filter);
}
...
protected final void setAuthenticationFilter(F authFilter) {
this.authFilter = authFilter;
}
...
...
public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String DEFAULT_FILTER_PROCESSES_URI = "/login/oauth2/code/*";
...
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
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());
}
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());
}
// registrationId를 통해 현재 OAuth 서버에 대한 설정을 가져오기(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());
}
// @formatter:off
String redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.build()
.toUriString();
// @formatter:on
// Access Token 요청을 위한 Authorization code를 담아서 OAuth2AuthorizationResponse를 만듦.
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
redirectUri);
Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
// OAuth2AuthorizationResponse를 이용하여 인증 대행 서버에게 Access token 요청을 위한 authenticationRequest를 만듦.
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(authenticationDetails);
// ProviderManager의 authenticate() 메서드로 인증 대행 서버의 Access token을 발급받고, 유저 정보를 가져옴.
OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
.getAuthenticationManager()
.authenticate(authenticationRequest);
OAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter
.convert(authenticationResult);
Assert.notNull(oauth2Authentication, "authentication result cannot be null");
oauth2Authentication.setDetails(authenticationDetails);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
return oauth2Authentication;
}
...
...
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
...
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
Authentication authenticationResult = attemptAuthentication(request, response);
...
}
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (this.requiresAuthenticationRequestMatcher.matches(request)) {
return true;
}
if (this.logger.isTraceEnabled()) {
this.logger
.trace(LogMessage.format("Did not match request to %s", this.requiresAuthenticationRequestMatcher));
}
return false;
}
...
OAuth2LoginAuthenticationFilter는 AbstractAuthenticationProcessingFilter를 상속받고, doFilter() 메서드는 AbstractAuthenticationProcessingFilter에 존재한다. 따라서 OAuth 서버가 code와 함께 redirect-uri로 request를 Spring boot 서버에 보내면, OAuth2LoginAuthenticationFilter를 거치면서 해당 필터 내부에 구현된 attemptAuthentication() 메서드로 OAuth 인증 과정(위 OAuth 2.0 프로토콜 그림의 4~7번 과정)이 진행된다.
doFilter() try 문 진입 전 if 문에서 requiresAuthentication() 메서드로 로그인 프로세스를 진행할 것인지 결정한다. 이 메서드에서 처음에 진입하는 조건문 if (this.requiresAuthenticationRequestMatcher.matches(request)) { 을 보자. this.requiresAuthenticationRequestMatcher에는 따로 설정하지 않았기에 OAuth2LoginAuthenticationFilter의 정적 변수인 DEFAULT_FILTER_PROCESSES_URI가 담겼으며, 이는 default 값인 "/login/oauth2/code/*"이다. 즉, Spring boot 어플리케이션에 설정된 redirect-uri와 this.requiresAuthenticationRequestMatcher에 담긴 uri가 일치되는 패턴이어야 로그인 프로세스가 진행됨을 의미한다.(언급한 4~7번 과정) 만약 application.yml 환경 설정 파일에서 redirect-uri을 따로 설정했다면, Security Configuration에서의 oauth2Login() 설정에서 loginProcessingUrl()에 개발자가 따로 정한 redirect-uri를 명시하여 설정해야한다.(Google cloud console에서 설정한 승인된 리디렉션 URI에 Spring boot 어플리케이션 OAuth 설정의 redirect-uri가 포함돼야 함에 항상 유의. 명시하지 않으면 "/login/oauth2/code/*"의 패턴을 가지게 되는 것이다.)
5. 요약
- OAuth2ClientRegistrationRepositoryConfiguration.class에 의해 application.yml or .properties 환경 파일로부터 oauth 설정을 Application 내부에 객체로 저장함. 이후 필터 동작 시 플랫폼 별 바인딩된 데이터를 해당 객체에서 가져옴.
- OAuth2AuthorizationRequestRedirectFilter가 "/oauth2/authorization/{registrationId}" Endpoint를 가지는 request를 가로채서 사용자가 인증 대행 서버에 로그인할 수 있도록 플랫폼에 대한 로그인 페이지로 redirect 시킴.
- 해당 소셜(인증 대행 서버) 로그인이 성공하면, 인증 대행 서버는 설정된 redirect-uri로 Authorization code와 함께 redirect 시킴.
- OAuth2LoginAuthenticationFilter는 유효한 redirect-uri 패턴인지 검사 후에, 발급받은 Authorization code로 위 그림의 4. Access Token 요청 ~ 7. 유저 정보 제공 과정을 처리.
Project Github Repo.
https://github.com/1nthebleakmidwinter/Urcarcher_Card-Service_ShinhanDS-Academy
References
- https://hudi.blog/oauth-2.0/
- https://velog.io/@zz1996zz/Spring-Security-Oauth-2.0
- https://hoons-dev.tistory.com/140
- https://velog.io/@gd5177/Oauth2LoginConfigurer-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0
- https://velog.io/@max9106/OAuth3
'Spring boot > Spring security' 카테고리의 다른 글
[Spring boot] OAuth(Google) & JWT Login (6) - JWT + 쿠키 (0) | 2024.11.11 |
---|---|
[Spring boot] OAuth(Google) & JWT Login (5) - OAuth 동작 ② (14) | 2024.11.06 |
[Spring boot] OAuth(Google) & JWT Login (3) - Security 구성 및 동작 (5) | 2024.10.31 |
[Spring boot] OAuth(Google) & JWT Login (2) - 자체 로그인 동작 구조 (6) | 2024.10.28 |
[Spring boot] OAuth(Google) & JWT Login (1) - 프로젝트 기본 구조 (7) | 2024.10.22 |