HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🔒
Spring Boot — Spring Security Essentials
/
🔟
10. Spring Security OAuth2
10. Spring Security OAuth2
🔟

10. Spring Security OAuth2

github.com
https://github.com/prgrms-be-devcourse/w16-SpringBoot_Part_C/tree/class_10

OAuth2.0 이해하기

사용자가 가입된 서비스(구글, 페이스북, 카카오, 네이버 등)에서 제공하는 API를 이용하여 사용자 데이터에 접근하기 위해서는 사용자로부터 권한을 위임 받아야 한다. 이 때 사용자의 패스워드 없이도 권한을 위임 받을 수 있는 방법이 필요한데, OAuth2.0(Open Authorization, Open Authentication 2) 라는 표준 인증 프로토콜을 통해 처리한다.
  • 왜 API는 비밀번호만으로 인증하는 방법을 사용하지 않는가?
    • 신뢰 — 사용자는 애플리케이션에 비밀번호를 제공하기 꺼려함
    • 불필요하게 넓은 접근 범위 — 사용자가 애플리케이션에 비밀번호를 제공하면 애플리케이션에 필요한 데이터 뿐만 아니라 사용자 계정 안에 있는 모든 데이터에 접근할 수 있음
    • 사용성 — 사용자가 비밀번호를 바꾸면 애플리케이션은 더는 해당 데이터에 접근하지 못함
  • OAuth 주요 용어 4가지
    • Resource Owner — 서비스를 이용하는 사용자이자, 리소스 소유자
    • Client (어플리케이션) — 리소스 소유자를 대신하여 보호된 리소스에 액세스하는 응용 프로그램
    • Resource Server — 보호받는 리소스를 호스팅하고 액세스 토큰을 사용하는 클라이언트의 요청을 수락하고 응답할 수 있는 서버 (카카오, 네이버 등의 리소스 서버)
    • Authorization Server — 클라이언트 및 리소스 소유자를 성공적으로 인증한 후 액세스 토큰을 발급하는 서버 (카카오, 네이버 등의 인증 서버)
💡
OAuth2.0에서 Client는 Authorization server에게 4가지 방법으로 토큰 발생을 요청할 수 있다.
Authorization Code Grant
  • OAuth2.0에서 가장 중요하고, 널리 사용되는 인증 방법 — 이 방법에서 클라이언트는 써드파티 서비스의 백엔드 서버가 됨
    • 백엔드 서버가 존재하는 웹/모바일 서비스에 적합함
  • 사용자 인증 후 Callback을 통해 authorization code를 받고, 이를 client-id, client-secret과 함께 Access-Token으로 교환함
  • Callback 처리는 백엔드 서버에서 이루어지기 때문에, Access-Token이 외부에 노출되지 않음 (보안상 안전)
  • 4단계 처리 Flow
    • Authorization Request — 클라이언트는 사용자를 Authorization Server로 리다이렉션
      • response_type — code 고정
      • client_id — Authorization Server에서 클라이언트를 식별하기 위한 식별키
      • redirect_uri — Authorization Server에서 처리 완료 후 리다이렉션 하기 위한 URL
      • scope — 클라이언트가 요구하는 리소스를 정의
      • state — 클라이언트는 임의의 문자열을 생성하여 CSRF 공격을 방지함
      • https://kauth.kakao.com/oauth/authorize ?response_type=code &client_id=0492f15cb715d60526a3eb9e2323c559 &scope=profile_nickname%20profile_image &state=xI8tRNCSoeiAIw87NaUr5foPbhBhW2METzHDBK75jgo%3D &redirect_uri=http://localhost:8080/login/oauth2/code/kakao
    • Authorization Response — 클라이언트에서 요구하는 리소스에 대해 사용자 동의를 받고, 요청과 함께 전달된 redirect_uri로 리다이렉션
      • code — Access-Token 교환을 위한 승인 코드
      • state — 요청과 함께 전달 된 임의의 문자열
      • /login/oauth2/code/kakao ?code=jzcahTyqbAx4zs9pKfBDlGXmB36sPX2YJCNIIw0RKkW_ODsYTQpheSGABo17dHC5rXRD2Qopb9QAAAF76FELEg &state=xI8tRNCSoeiAIw87NaUr5foPbhBhW2METzHDBK75jgo%3D
    • Token Request — 승인 코드를 Access-Token으로 교환
      • grant_type — authorization_code 고정
      • code — 앞 단계에서 전달 받은 코드
      • client_id — Authorization Server에서 클라이언트를 식별하기 위한 식별키
      • client_secret — 클라이언트 비밀키
      • HTTP POST https://kauth.kakao.com/oauth/token Accept=[application/json, application/*+json] Writing [ {grant_type=[authorization_code], code=[jzcahTyqbAx4zs9pKfBDlGXmB36sPX2YJCNIIw0RKkW_ODsYTQpheSGABo17dHC5rXRD2Qopb9QAAAF76FELEg], redirect_uri=[http://localhost:8080/login/oauth2/code/kakao], client_id=[0492f15cb715d60526a3eb9e2323c559], client_secret=[oqoKOBecGMC45Uh7z7bmdtMJ0A4PSQ2l]} ] as "application/x-www-form-urlencoded;charset=UTF-8"
    • Token Response — Access-Token 및 부가정보 획득
      • access_token — 리소스 요청에 필요한 토큰 (보통 짧은 생명주기를 지니고 있음)
      • refresh_token — Access-Token을 갱신하기 위한 토큰
      https://docs.pivotal.io/p-identity/1-14/grant-types.html
Implicit Grant
  • Authorization Code Grant 방식과 비교했을 때, Authorization Response 단계에서 Access-Token이 전달되고 Token Request 단계가 생략됨
  • Access-Token이 URL에 노출되기 때문에 보안상 리스크가 있음
  • 백엔드 서버가 없는 제한적인 환경에서만 사용을 권장함
    • 브라우저에서 자바스크립트와 같은 스크립트 언어로 동작하는 클라이언트
    • https://docs.pivotal.io/p-identity/1-14/grant-types.html
Client Credentials Grant
  • client_id, client_secret 파리미터만 가지고 Access-Token을 발급할 수 있으며, 사용자는 전혀 관여하지 않음
  • 사용자의 직접적인 상호 작용 없이 백그라운드에서 실행해야 하는 서버 간 상호 작용에 사용
    • https://docs.pivotal.io/p-identity/1-14/grant-types.html
Resource Owner Password Credentials Grant
  • Client Credentials Grant 방식과 매유 유사하지만, client_id, client_secret 대신 사용자 비밀번호로 인증됨
  • 즉, 일반 로그인 아이디/비밀번호 인증
  • 클라이언트를 완전히 신뢰할 수 있을 때 사용
    • https://docs.pivotal.io/p-identity/1-14/grant-types.html

Spring Security OAuth2.0 Client (카카오 인증 연동)

Spring Security 인프라 스트럭처 위에서 Authorization Code Grant 타입 OAuth2.0 인증 처리 방법을 알아보자.
💡
10장의 베이스코드에서 User 모델과 테이블 정의가 변경되었다. 간단한 변경이므로 User 모델 정의를 참고한다
Spring Boot and OAuth2
this tutorial is designed to be completed in 2-3 hours, it provides deeper, in-context explorations of enterprise application development topics, leaving you ready to implement real-world solutions.
Spring Boot and OAuth2
https://spring.io/guides/tutorials/spring-boot-oauth2/
카카오 Application 생성
  • 카카오 개발자 사이트에서 어플리케이션을 하나 등록함
    • 요약 정보의 REST API 키 값을 OAuth2.0에서 client_id 값으로 사용됨
  • 카카오 로그인 설정을 활성화하고, Redirect URI 부분에 http://localhost:8080/login/oauth2/code/kakao 주소를 입력
    • 동의 항목 설정에서 profile_nickname, profile_image 필드를 필수 동의로 설정 — 해당 값은 scope 값으로 사용됨
    • 보안 설정에서 Client Secret을 활성화하고, 코드를 생성 — 해당 값은 client_secret 값으로 사용됨
Kakao Developers
Start with kakao platform +service api products Start with kakao platform + service api products 인간은 바로 사람과 사람이 서로 기대어 구성됩니다. 상생은 상대의 부정이 아닌 긍정을 통해 이를 수 있는 세계입니다. 카카오는 상생을 추구합니다. 카카오는 수많은 개발자분들과 함께, 개발자분들에 의해, 개발자분들을 위해 같이 성장하고 싶습니다. 개발자의 생산성 향상은 우리의 중요한 목표 중 하나입니다.
Kakao Developers
https://developers.kakao.com/
Kakao Developers
Spring Security OAuth2.0 의존성 추가 및 설정
  • spring-boot-starter-oauth2-client — 클라이언트 관점에서 OAuth2.0 인증 처리를 처리할 수 있도록 도와줌
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId> </dependency>
  • application.xml 파일에 kakao OAuth2.0 연동을 위한 정보를 입력함
    • 카카오 로그인 설정에서 입력한 Redirect URI 주소를 security.oauth2.client.registration.kakao.redirect-uri 부분에 입력함
      • 카카오 로그인 설정에서 입력한 Redirect URI 주소의 마지막 부분은 {registrationId} 변수로 처리함
spring: security: oauth2: client: registration: kakao: client-name: kakao client-id: 19f4f13148900bb3bedeb7c4eff31e31 client-secret: NwhWWnB5D62JcSFRzX3zUC2sRrgJ0iRd scope: profile_nickname, profile_image redirect-uri: "http://localhost:8080/login/oauth2/code/{registrationId}" authorization-grant-type: authorization_code client-authentication-method: POST provider: kakao: authorization-uri: https://kauth.kakao.com/oauth/authorize token-uri: https://kauth.kakao.com/oauth/token user-info-uri: https://kapi.kakao.com/v2/user/me user-name-attribute: id
  • 카카오 인증이 완료되었을 때 후처리를 담당할 AuthenticationSuccessHandler 인터페이스 구현체 추가
    • 카카오 인증이 완료된 사용자가 신규 사용자라면 사용자를 가입 시킴
    • 서비스 접근을 위한 JWT 토큰 생성 및 응답
      • 아래 코드에서는 단순히 JSON 포맷으로 응답을 생성하지만, 앱 연동을 위해 앱 전용 스킴을 설계하고 데이터를 전달할 수 있음
public class OAuth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private final Logger log = LoggerFactory.getLogger(getClass()); private final Jwt jwt; private final UserService userService; public OAuth2AuthenticationSuccessHandler(Jwt jwt, UserService userService) { this.jwt = jwt; this.userService = userService; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauth2Token = (OAuth2AuthenticationToken) authentication; OAuth2User principal = oauth2Token.getPrincipal(); String registrationId = oauth2Token.getAuthorizedClientRegistrationId(); User user = processUserOAuth2UserJoin(principal, registrationId); String loginSuccessJson = generateLoginSuccessJson(user); response.setContentType("application/json;charset=UTF-8"); response.setContentLength(loginSuccessJson.getBytes(StandardCharsets.UTF_8).length); response.getWriter().write(loginSuccessJson); } else { super.onAuthenticationSuccess(request, response, authentication); } } private User processUserOAuth2UserJoin(OAuth2User oAuth2User, String registrationId) { return userService.join(oAuth2User, registrationId); } private String generateLoginSuccessJson(User user) { String token = generateToken(user); log.debug("Jwt({}) created for oauth2 login user {}", token, user.getUsername()); return "{\"token\":\"" + token + "\", \"username\":\"" + user.getUsername() + "\", \"group\":\"" + user.getGroup().getName() + "\"}"; } private String generateToken(User user) { return jwt.sign(Jwt.Claims.of(user.getUsername(), new String[]{"ROLE_USER"})); } } @Service public class UserService { // ... 생략 ... @Transactional public User join(OAuth2User oauth2User, String provider) { checkArgument(oauth2User != null, "oauth2User must be provided."); checkArgument(isNotEmpty(provider), "authorizedClientRegistrationId must be provided."); String providerId = oauth2User.getName(); return findByProviderAndProviderId(provider, providerId) .map(user -> { log.warn("Already exists: {} for (provider: {}, providerId: {})", user, provider, providerId); return user; }) .orElseGet(() -> { Map<String, Object> attributes = oauth2User.getAttributes(); @SuppressWarnings("unchecked") Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); checkArgument(properties != null, "OAuth2User properties is empty"); String nickname = (String) properties.get("nickname"); String profileImage = (String) properties.get("profile_image"); Group group = groupRepository.findByName("USER_GROUP") .orElseThrow(() -> new IllegalStateException("Could not found group for USER_GROUP")); return userRepository.save( new User(nickname, provider, providerId, profileImage, group) ); }); } }
주요 구현 코드
  • Spring Security 설정
    • OAuth2AuthenticationSuccessHandler Bean 추가 및 설정
@Bean public OAuth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler(Jwt jwt, UserService userService) { return new OAuth2AuthenticationSuccessHandler(jwt, userService); } @Override protected void configure(HttpSecurity http) throws Exception { http // ... 생략 ... .oauth2Login() .successHandler(getApplicationContext().getBean(OAuth2AuthenticationSuccessHandler.class)) .and() .addFilterAfter( getApplicationContext().getBean(JwtAuthenticationFilter.class), SecurityContextPersistenceFilter.class ) ; }
어떤 일들이 벌어진 것일까?
  • filterChainProxy 살펴보기 — 3개의 필터가 추가됨
    • notion image
    • DefaultLoginPageGeneratingFilter — 로그인 페이지 생성 필터
      • 로그인 전략에 따라 Form 로그인 페이지, OAuth2.0 로그인 페이지 등이 생성됨
      • /oauth2/authorization/kakao — 카카오 OAuth 인증 요청 링크
        • OAuth2AuthorizationRequestRedirectFilter 에서 해당 요청을 처리하게됨
        <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Please sign in</title> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/> </head> <body> <div class="container"> <h2 class="form-signin-heading">Login with OAuth 2.0</h2> <table class="table table-striped"> <tr><td><a href="/oauth2/authorization/kakao">kakao</a></td></tr> </table> </div> </body> </html>
        DefaultLoginPageGeneratingFilter 에서 생성된 OAUTH 인증 페이지 HTML 코드
    • OAuth2AuthorizationRequestRedirectFilter — 카카오 인증서버로 사용자를 리다이렉트 시킴
      • /oauth2/authorization/{registrationId} 패턴의 URL 요청을 처리함 (기본값)
        • {registrationId} 부분에는 인증 Provider 식별키(kakako 같은)가 입력됨
      • AuthorizationRequestRepository 인터페이스 구현체에는 application.yml 파일에 설정한 OAuth 연동 정보가 저장되어 있음
      • 인증 Provider 식별키로 AuthorizationRequestRepository 인터페이스에서 OAuth 연동 정보를 가져옴
      • authorization-uri 주소로 사용자를 리다이렉트 시킴
      • 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()); }
        OAuth2AuthorizationRequestRedirectFilter 구현 일부 발췌
    • OAuth2LoginAuthenticationFilter — Authorization Response를 수신하고, Token Request를 인증 서버로 요청
      • 흥미로운 점은 AuthenticationManager, AuthenticationProvider 같은 Spring Security 기본 인프라 스트럭처가 그대로 활용됨
      • OAuth2LoginAuthenticationToken
        • OAuth2.0 인증 처리를 명시적으로 나타내는 Authentication 인터페이스 구현체
      • OAuth2LoginAuthenticationProvider
        • OAuth2LoginAuthenticationToken 타입 인증 요청을 처리할 수 있는 AuthenticationProvider 인터페이스 구현체
        • Authorization Server에서 Access-Token 및 Refresh-Token을 가져옴
        • 발급 받은 Access-Token 을 이용해, 사용자 데이터를 조회해옴 — OAuth2User 객체로 표현됨
        @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // ... 생략 ... 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()); } // ... 생략 ... OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken( clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse) ); authenticationRequest.setDetails(authenticationDetails); OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest); OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken( authenticationResult.getPrincipal(), authenticationResult.getAuthorities(), authenticationResult.getClientRegistration().getRegistrationId() ); oauth2Authentication.setDetails(authenticationDetails); OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient( authenticationResult.getClientRegistration(), oauth2Authentication.getName(), authenticationResult.getAccessToken(), authenticationResult.getRefreshToken() ); this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response); return oauth2Authentication; }
        OAuth2LoginAuthenticationFilter 구현 일부 발췌
추가적인 개선
OAuth2AuthorizationRequestRedirectFilter, OAuth2LoginAuthenticationFilter 구현을 자세히 보면 서로 연결되는 부분이 있음
  • 이것은 CSRF 공격 방지를 위한 임의의 문자열 state를 확인하는 절차
  • OAuth2AuthorizationRequestRedirectFilter — authorizationRequestRepository를 통해 authorizationRequest 저장
  • OAuth2LoginAuthenticationFilter — authorizationRequestRepository를 통해 authorizationRequest 조회
    • authorizationRequest 조회가 안되면 오류 처리
  • 그런데, authorizationRequestRepository 인터페이스 기본 구현체가 HttpSessionOAuth2AuthorizationRequestRepository 클래스로 Session을 사용함
  • API 서버는 Session을 사용하지 않기 때문에 HttpCookieOAuth2AuthorizationRequestRepository 구현을 추가하여, Session 대신 Cookie을 사용하도록함
@Override public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { return getCookie(request) .map(this::getOAuth2AuthorizationRequest) .orElse(null); } @Override public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { if (authorizationRequest == null) { getCookie(request).ifPresent(cookie -> clear(cookie, response)); } else { String value = Base64.getUrlEncoder().encodeToString(SerializationUtils.serialize(authorizationRequest)); Cookie cookie = new Cookie(cookieName, value); cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(cookieExpireSeconds); response.addCookie(cookie); } } @Override public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) { return loadAuthorizationRequest(request); } @Override public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { return getCookie(request) .map(cookie -> { OAuth2AuthorizationRequest oauth2Request = getOAuth2AuthorizationRequest(cookie); clear(cookie, response); return oauth2Request; }) .orElse(null); } private Optional<Cookie> getCookie(HttpServletRequest request) { return ofNullable(WebUtils.getCookie(request, cookieName)); } private void clear(Cookie cookie, HttpServletResponse response) { cookie.setValue(""); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } private OAuth2AuthorizationRequest getOAuth2AuthorizationRequest(Cookie cookie) { return SerializationUtils.deserialize( Base64.getUrlDecoder().decode(cookie.getValue()) ); }
HttpCookieOAuth2AuthorizationRequestRepository 구현 일부 발췌
OAuth2LoginAuthenticationFilter 구현의 마지막 부분 — OAuth2AuthorizedClient 저장
  • authorizedClientRepository를 통해 OAuth2AuthorizedClient 객체를 저장함 — 즉, OAuth2.0 인증이 완료된 사용자 정보를 저장
    • 그런데, authorizedClientRepository 기본 구현체가 AuthenticatedPrincipalOAuth2AuthorizedClientRepository 클래스이며 내부적으로 InMemoryOAuth2AuthorizedClientService 클래스를 사용해 OAuth2AuthorizedClient 객체를 저장함
      • 따라서, OAuth2.0 으로 인증되는 클라이언트가 많아지면 OOME 발생 가능성이 있음
      • 또한 인증된 사용자 정보가 특정 서버 메모리에만 저장되고 있기 때문에 특정 서버 장애 발생 시 사이드 이펙트가 발생할 수 있음
      • 다행히 InMemoryOAuth2AuthorizedClientService 클래스는 OAuth2AuthorizedClientService 인터페이스 구현체이며, InMemoryOAuth2AuthorizedClientService 를 대체할 수 있는 JdbcOAuth2AuthorizedClientService 클래스가 있음
public final class AuthenticatedPrincipalOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository { private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); private final OAuth2AuthorizedClientService authorizedClientService; private OAuth2AuthorizedClientRepository anonymousAuthorizedClientRepository = new HttpSessionOAuth2AuthorizedClientRepository(); public AuthenticatedPrincipalOAuth2AuthorizedClientRepository( OAuth2AuthorizedClientService authorizedClientService) { Assert.notNull(authorizedClientService, "authorizedClientService cannot be null"); this.authorizedClientService = authorizedClientService; } // ... 생략 ... @Override public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal, HttpServletRequest request, HttpServletResponse response) { if (this.isPrincipalAuthenticated(principal)) { this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal); } else { this.anonymousAuthorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, request, response); } } // ... 생략 ... private boolean isPrincipalAuthenticated(Authentication authentication) { return authentication != null && !this.authenticationTrustResolver.isAnonymous(authentication) && authentication.isAuthenticated(); } }
AuthenticatedPrincipalOAuth2AuthorizedClientRepository 구현 일부 발췌
💡
AuthenticatedPrincipalOAuth2AuthorizedClientRepository 클래스 구현에서 Session을 사용하는 HttpSessionOAuth2AuthorizedClientRepository 클래스가 확인되지만, 논리적으로 해당 클래스가 사용되는 부분은 실행되지 않는다. 왜냐하면 OAuth2.0 인증된 사용자의 authentication은 null이 아니며 anonymous 상태도 아니기 때문이다.
Java Configuration 수정 및 확인
  • 설정 변경 부분
    • HttpCookieOAuth2AuthorizationRequestRepository — HttpSessionOAuth2AuthorizationRequestRepository Bean을 대체함
    • JdbcOAuth2AuthorizedClientService — InMemoryOAuth2AuthorizedClientService Bean을 대체함
    • AuthenticatedPrincipalOAuth2AuthorizedClientRepository — JdbcOAuth2AuthorizedClientService 의존성 주입
public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() { return new HttpCookieOAuth2AuthorizationRequestRepository(); } @Bean public OAuth2AuthorizedClientService authorizedClientService( JdbcOperations jdbcOperations, ClientRegistrationRepository clientRegistrationRepository ) { return new JdbcOAuth2AuthorizedClientService(jdbcOperations, clientRegistrationRepository); } @Bean public OAuth2AuthorizedClientRepository authorizedClientRepository(OAuth2AuthorizedClientService authorizedClientService) { return new AuthenticatedPrincipalOAuth2AuthorizedClientRepository(authorizedClientService); } @Override protected void configure(HttpSecurity http) throws Exception { http // ... 생략 ... .oauth2Login() .authorizationEndpoint() .authorizationRequestRepository(authorizationRequestRepository()) .and() .successHandler(getApplicationContext().getBean(OAuth2AuthenticationSuccessHandler.class)) .authorizedClientRepository(getApplicationContext().getBean(AuthenticatedPrincipalOAuth2AuthorizedClientRepository.class)) .and() .addFilterAfter(jwtAuthenticationFilter(), SecurityContextPersistenceFilter.class) ; }
  • 설정 완료 후 카카오 인증을 진행
    • OAuth2LoginAuthenticationFilter에서 authorizationRequestRepository를 통해 OAuth2AuthorizationRequest를 조회하는 부분을 확인
      • Session 기반 구현체 대신 HttpCookieOAuth2AuthorizationRequestRepository 클래스가 사용됨
      notion image
    • 모든 처리 완료 후 OAUTH2_AUTHORIZED_CLIENT 테이블에 데이터가 들어간 것을 확인 할 수 있음
      • 해당 테이블에는 Access-Token 외에 Refresh-Token 같은 정보도 포함되 있음
      notion image