
개요
현재 프로젝트에 인증 & 인가 부분을 맡게 되면서, 요구사항으로 OAuth2.0을 이용한 로그인 기능을 구현하고 JWT 토큰 방식을 접목하게 되었다. Security OAuth2.0 기능이 추가되면 그 기능이 FilterChain 안에서 어떻게 동작하게 되고, 커스텀을 위해 어떤 부분을 어떻게 건드려야 되는지 간단히 알아보았다.
OAuth2.0 인증 방법

OAuth2.0은 여러 인증 방법이 있지만, 대게 Authorization Code Grant 방식을 사용한다고 한다.
코드 안에서는 여러 일이 일어나지만, Flow는 다음과 같다.
Flow 1. 사용자가 어플리케이션에 소셜 로그인 요청 -> 소셜 로그인 페이지로 리다이렉트
Flow 2. 소셜 인증 완료 -> 인증 서버에서 Authorization Code 발급 후 사용자에게 리다이렉트
Flow 3. Authorization Code을 Access Token으로 교환
Flow 4. 리소스 서버에서 Access Token으로 사용자 정보 획득
yaml 설정 파일
Security OAuth2.0에 대한 의존성 추가와 API 서비스 등록을 해준다. 언급한 준비 과정은 다른 좋은 글들이 있으니, 여기서 설명하지는 않겠다.
다 되었다면, yaml 설정 파일을 작성한다.
spring:
security:
oauth2:
client:
registration:
kakao: //-> registrationId
client-name: kakao
client-id: {id}
client-secret: {secret}
redirect-uri: "http://localhost:8080/login/oauth2/code/{registrationId}" //Flow 2 인증 서버가 리다이렉트 하는 주소, 사용자에게 임시 코드를 전달
authorization-grant-type: authorization_code
client-authentication-method: client_secret_post
scope:
- account_email
- profile_nickname
- gender
- birthday
- phone_number
- birthyear
provider: //-> provider
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize //Flow 1 소셜 로그인 페이지
token-uri: https://kauth.kakao.com/oauth/token //Flow 3 Access Token 교환 주소
user-info-uri: https://kapi.kakao.com/v2/user/me //Flow 4 유저 정보 조회 주소
user-name-attribute: id
OAuth2.0에 대한 yaml 설정 파일이다. 해당 정보들은 Auto Configuration을 통해 ClientRegistration 객체로 변환된다.
다 중요하지만, 특별히 provider에 리다이렉트 주소를 다 작성해주어야 한다는 것을 참고하면 좋겠다.
참고로 카카오와 네이버 같은 국내 기업들의 경우, 위 예시처럼 provider를 다 명시해주어야 하지만
구글, 페이스북 같은 글로벌 IT 기업들의 provider는 이미 내장되어 있어 명시하지 않아도 된다고 한다.
Security Config 클래스
위 과정이 다 되었다면, Config 클래스에 OAuth를 등록해준다.
@Component
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.
.
.
.oauth2Login() //Authorization Code Grant 방식 지원
.oauth2Client() //Client Credentials Grant 등 다른 방식
.
.
return http.build();
}
}
- oauth2Login() - Authorization Code Grant 방식 지원
oauth2Client() - 다른 방식 지원
Security OAuth2.0은 2가지 API를 제공하며, Authorization Code Grant 방식을 지원하는 oauth2Login() API를 이용하였다.
oauth2Login() API 등록 시, OAuth2LoginConfigurer가 작동되면서 두 개의 메서드가 실행된다.
메서드들의 역할은 다음과 같다.
- configure() - OAuth2AuthorizationRequestRedirectFilter 추가
- init() - OAuth2LoginAuthenticationFilter 추가
결과적으로 Security Filter에는 다음과 같은 순서로 Filter들이 추가 된다.

각 필터들의 역할을 Flow에 따라 알아보자
OAuth2AuthorizationRequestRedirectFilter
해당 필터는 Flow 1, Flow 2에 과정을 수행하는 필터이다.
Flow 1
1. 사용자가 소셜 로그인 요청을 보낸다.
'/oauth2/authorization/{registrationId}'
2. 로그인 요청이 들어온다면 해당 Filter가 캐치한다.

3. DefaultOAuth2AuthorizationReuqestResolver라는 녀석을 통해, 설정한 yaml 파일의 정보들(ClientRegistration)을 갖고와서 OAuth2AuthorizationRequest 객체에 넣어주고, 세션에 저장한다.

4. 실제 소셜 로그인 페이지로 리다이렉트 한다.

리다이렉트 페이지 주소는 yaml 파일에 명시한 주소가 OAuth2AuthorizationRequest를 통해 전달된다.
Flow 2
5. 소셜 로그인이 완료되면, 인증 서버에서 Authorization Code를 발급 후 사용자에게 리다이렉트 한다.
이 때, 리다이렉트 주소도 yaml 파일에 명시되어 있다.
'/login/oauth2/code/{registrationId}'
OAuth2LoginAuthenticationFilter
해당 필터는 Flow 3, Flow 4에 과정을 수행한다.
6. Flow 2 에서 리다이렉트한 요청을 캐치한다.

7. 발급 받은 Authorization Code가 들어있는 OAuthorizationResponse 객체를 OAuth2LoginAuthenticationToken에 담는다.

8. OAuth2LoginAuthenticationToken을 처리하는 알맞는 Provider를 찾아 authenticate() 메서드를 호출한다.

authenticate() 메서드 실제로 사용자 정보를 획득하는 로직을 수행하며,
Provider는 OAuth2LoginAuthenticationProvider가 매칭된다.
OAuth2LoginAuthenticationProvider
서드 파티와 직접적으로 연결되는 부분이기에, 실제 Flow 3, Flow 4 를 처리하는 객체만 보면 될 것 같다.
Flow 3
9. 전달 받은 OAuth2LoginAuthenticationToken의 Authorization Code를 통해 서드 파티와 Access Token으로 교환한다.

이 때, 서드 파티와 연결되는 Provider는 OAuth2AuthorizationCodeAuthenticationProvider이다.
Flow 4
10. OAuth2UserService 객체에서 Access Token을 통해 사용자의 정보를 갖고온다.

11. 모든 로직이 수행되면, SuccessHandler가 동작하여 추가 로직을 수행한다.

그래서 커스텀은 어떻게?
길고 긴 디버깅 지옥여행이 끝났다..! 사실은 Spring Security의 OAuth2.0 기능을 사용한다면, Authorization Code Grant 방식으로 필터를 추가하기 때문에 실제로 구현할 건 없다.
하지만 JWT 토큰 기반의 로그인을 접목하여, 로그인 후 토큰을 프론트 쪽에 전달하고자 하였다.
그렇다면 리소스 서버로부터 사용자 정보를 갖고 온 이후 시점인 Flow 4의 다음 부분들을 우리가 건드려볼 수 있을 것이다.
- OAuth2UserService
- SuccessHandler
필자는 SuccessHandler를 커스텀하여 로직을 구성하였다.
SuccessHandler를 커스텀 한 이유는 다음의 글을 참고하면 좋겠다.
참고 - https://velog.io/@namhm23/스프링-시큐리티-소셜로그인-인증-구현-방법
스프링 시큐리티 + 소셜로그인 인증 구현 방법
이번 포스팅에서는 스프링 부트 3.1.5 버전에서 Spring Security 를 통해 소셜로그인을 직접 적용해보며, 부트 최 신 버전에서 소셜로그인을 적용하는 방법과 내가 직접 적용해본 과정을 포스팅 해보
velog.io
@Component
@RequiredArgsConstructor
public class CustomOAuth2SuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private final MemberService memberService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException {
if (authentication instanceof OAuth2AuthenticationToken) {
OAuth2User oauth2User = ((OAuth2AuthenticationToken) authentication).getPrincipal();
String provider = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId();
OAuthAttribute oAuthAttribute = OAuthAttribute.of(oauth2User, provider);
Token token = memberService.oauthLogin(oAuthAttribute);
sendTokenJson(response, tokenToJson(token));
}
}
public String tokenToJson(Token token) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(token);
}
private void sendTokenJson(HttpServletResponse response, String tokenJson) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setContentLength(tokenJson.getBytes().length);
response.getWriter().write(tokenJson);
}
}
Authentication 내 principal의 소셜 사용자의 정보가 OAuth2User 객체로 저장되어 있다. 필요에 따라 사용자의 정보를 추출하여 사용하면 될 것이다.
참고 - https://yelimkim98.tistory.com/50
소셜로그인 7. OAuth2AuthenticationSuccessHandler
이전글에서 구현했던 OAuth2UserService # loadUser() 가 실행이 완료되면 세션 방식 로그인에서는 시큐리티 세션(의 Authentication 객체)에 사용자 정보가 들어가게 됩니다. (JWT 방식에서는... 별 일 없는 것
yelimkim98.tistory.com
정리
Spring Security라는 프레임워크가 워낙 거대하다보니, 지레 겁을 먹었던 부분이 있었다. 막상 뜯어보니 Spring Security가 OAuth2.0에 대한 부분을 거의 다 처리해주고 있어 놀라웠다.
또 과정을 뜯어볼 때는 상당히 복잡했지만, 뜯어보면서 공부하고 이해하게 되어 더욱 잊혀지지 않을 것 갖고, 필요에 따른 커스텀 역시 별거 아니였다는 것을 깨달았다. 역시 기술을 써먹어도 이해를 하고 써먹어야 되나보다.
'Spring > Security' 카테고리의 다른 글
| [Spring Security] 가리지 않고 예외 먹는 AuthenticationEntryPoint (0) | 2024.03.20 |
|---|