Skip to content

Auth module document

유동현 edited this page Jul 2, 2022 · 24 revisions

Introduction.


인증모듈은 IBAS 서비스에서 인증을 위한 구체적인 기능을 제공하는 것을 목적으로 한다.  
다른 모듈을 일체 의존하지 않는 순수 모듈이며, 크게 3가지로 구분되어 있다.  
(1) `OAuth 2.0 인증` (2) `토큰 발급 및 갱신, 인증` (3) `예외`  

1. OAuth 2.0 인증

OAuth2.0 프로토콜을 이용하여 소셜로그인을 지원하는 것을 목표로 한다. Spring Security 에서 제공하는 oauth2 기능을 이용한다.

엔티티는 두 가지가 존재한다.
(1) 발급한 리프레시 토큰을 저장하는 RefreshToken 과 (2) 로그인하는 소셜계정을 저장하는 SocialAccount


1.1. OAuth2.0 로그인 흐름

1.1.1. 인증 시작점

인증의 시작점은 https://www.inhabas.com/api/login/oauth2/authorization/{provider}?redirect_url={} 와 같은 형식이다.

IBAS 의 로그인 기능을 이용하기 위해서는 위의 endpoint 를 호출해야만 하고, 로그인 성공 여부를 알기 위해서 redirect_url을 명시해야한다.

프론트엔드 또는 모바일 어플리케이션에서는 기재한 redirect_url을 통해 결과를 받고, 적절히 로그인을 마무리 해야한다.


1.1.2. 백엔드 인증 흐름

  1. user agent 에서 인증시작 url 을 호출

  2. 인증 모듈에서 OAuth2.0 authorization code 방식의 인증을 시작. (authorization code 방식?)
    2-1. 소셜로그인 후 provider 측에서 사용자의 개인정보를 제공한다.
    2-2. 소셜 계정 정보를 db 에 저장한다. (로그인 로그 남기기 용도)
    2-3. 개인정보를 토대로 기존 회원인지 확인. -> 회원이 아니면 로그인 실패

  3. 인증 결과를 provider 로부터 받음.
    3-1. 성공하면 Oauth2AuthenticationSuccessHandler 호출

    • access token, refresh token 을 발급.
    • refresh token 은 db에 저장된다.
    • {redirect_url}?access_token={}&refresh_token={}&expires_in={}&image_url={} 로 응답한다.

    3-2. 실패하면 Oauth2AuthenticationFailureHandler 호출

    • {redirect_url}?error={errorCode} 의 형식으로 응답한다.

1.2. spring security 설정

1.2.1. 설정 코드

아래와 같이 코드를 설정한 이유는 후술하도록 하겠다.

public class AuthSecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;
    private final Oauth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
    private final Oauth2AuthenticationFailureHandler oauth2AuthenticationFailureHandler;
    private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

 @Override
 protected void configure(HttpSecurity http) throws Exception {

        http
                .antMatcher("/login/**")
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 세션 사용 안함.
                    .and()
                .cors().and()    // cors 활성화. 개발 서버를 위해서 따로 설정했음.
                .csrf().and()    // csrf 활성화
                .oauth2Login()   // oauth login 활성화
                    .authorizationEndpoint()
                        .baseUri("/login/oauth2/authorization")  // 인증 시작 url 을 설정. "/login/oauth2/authorization/{provider}" 의 형식이다.
                        .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)  // 요청을 request cookie에 저장.
                        .and()
                    .userInfoEndpoint()
                        .userService(customOAuth2UserService)  // 사용자 소셜계정 인증정보를 이용해서, db에 있는 기존 회원 정보를 불러오기 위함.
                        .and()
                    .failureHandler(oauth2AuthenticationFailureHandler)  // 소셜 로그인 실패 시 
                    .successHandler(oauth2AuthenticationSuccessHandler)  // 소셜 로그인 성공 시
                    .and()
                .authorizeRequests()
                    .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()  // cors preflight request를 허용
                    .anyRequest().permitAll();  // "/login/..." 으로 들어오는 요청은 모두 허용
    }
}

1.2.2. AbstractAuthenticationProcessingFilter

당시 공부했던 이슈

SecurityFilterChain 에서 인증을 진행하는 핵심 필터는 AbstractAuthenticationProcessingFilter 이다. oauth2login 을 활성화하면, 이 필터는 OAuth2LoginAuthenticationFilter 에게 구체적인 인증을 위임한다. 이 때 직접 구현해야하는 사항은 다음과 같다.

  • SuccessHandler 및 FailureHandler : OAuth2.0 프로토콜에 따른 인증 결과를 적절히 처리할 수 있도록 한다.
  • OAuth2UserService : OAuth2.0 인증 결과를 바탕으로 기존 유저인지 데이터베이스를 통해 확인하는 작업을 한다. 상속받아서 CustomOauth2UserService 등의 이름으로 구현한다.

직접 구현해야하는 것은 아니지만 따로 Bean 으로 등록 해주어야하는 게 있다.

  • 사진 우측에 보면 AuthorizedClientRepository 가 보인다.
    spring 기본 설정으로 AuthenticatedPrincipalOAuth2AuthorizedClientRepository 가 설정되어 있는데, InMemoryOAuth2AuthorizedClientService 에게 위임한다.
    인메모리 방식이 권장되지 않기 때문에, HttpSessionOAuth2AuthorizedClientRepository로 설정하는 것이 좋다.
    (https://github.com/spring-projects/spring-boot/issues/24237)

1.2.3 AuthorizationRequestRepository

OAuth2.0 인증 프로토콜 과정에서는 리다이렉트가 많이 발생한다. 이 과정을 처리하기 위한 필터 OAuth2AuthorizationRequestRedirectFilter.
이미 정해진 프로토콜에 의해 api 서버 <-> Provider 통신에서 여러 url 파라미터들을 이용하는데,
이런 정보들을 중간에 제대로 저장하면서 처리하기 위해 springSecurity 는 AuthorizationRequestRepository를 사용한다.
(default는 HttpSessionOAuth2AuthorizationRequestRepository이다.)

로그인 로직이 완전히 종료되면, 처음 프론트엔드에서 로그인 요청할 때 파라미터로 기재했던 {redirect_url}로 리다이렉트 시켜주어야한다. 즉 {redirect_url}를 인증 과정(multiple redirects)동안 잘 유지하고 있어야한다. 따라서 Request cookie 에 저장하기 위해, AuthorizationRequestRepository를 상속받아서 HttpCookieOAuth2AuthorizationRequestRepository를 구현한다.


1.2.4. 구현 사항

잠깐 정리해보자면, spring security oauth2 를 사용하기 위해서는 아래의 요소를 구현해야한다.

1. SuccessHandler 및 FailureHandler 구현               : 로그인 결과를 토대로 적절한 처리          
2. OAuth2UserService 구현                              : 소셜 계정 정보로 기존 회원을 검사하기 위함.
3. HttpCookieOAuth2AuthorizationRequestRepository 구현 : request cookie 를 이용하여 {redirect_url}을 보존.
4. HttpSessionOAuth2AuthorizedClientRepository 빈 등록 (구현 x)

1.3. HttpCookieOAuth2AuthorizationRequestRepository 구현

1.3.1. CookieUtils

먼저 request, response 에 cookie 를 저장, 불러오기, 삭제 등의 작업을 할 수 있는 CookieUtils 를 구현했다.


1.3.2. 구현 상세

AuthorizationRequestRepository 을 상속받아서 4개의 메소드를 구현하면 된다.


1.3.2.1 save 함수

OAuth2AuthorizationRequest 는 OAuth2.0 표준 프로토콜에 따라서 api server <-> provider 간 통신할 때 SpringSecurity가 사용하는 객체이다.
이 객체는 OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME 이라는 key 값으로 저장한다.
우리가 보존할 {redirect_url} 은 프론트엔드 또는 모바일에서 보내준 값이므로, REDIRECT_URL_PARAM_COOKIE_NAME 라는 key 값으로 따로 구분하여 저장한다.
이것만 알면 다른 removeAuthorizationRequest, loadAuthorizationRequest 함수는 그냥 이해되므로 링크만 첨부하도록 하겠다.

    @Override
    public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
        if (authorizationRequest == null) {
            CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
            CookieUtils.deleteCookie(request, response, REDIRECT_URL_PARAM_COOKIE_NAME);
            return;
        }
        CookieUtils.setCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds);
        String redirectUrlAfterLogin = request.getParameter(REDIRECT_URL_PARAM_COOKIE_NAME);
        if (StringUtils.isNotBlank(redirectUrlAfterLogin)) {
            CookieUtils.setCookie(response, REDIRECT_URL_PARAM_COOKIE_NAME, redirectUrlAfterLogin, cookieExpireSeconds);
        }
    }

1.4. Handler 구현

1.4.1. SuccessHandler

1.4.1.1 로직

아무런 오류도 발생하지 않고 Oauth2 인증이 끝나면, 실행된다.
{redirect_url}?access_token={}&refresh_token={}&expires_in={}&image_url={}처럼 응답하는 것을 목표로 한다.
성공 핸들러의 로직은 아래와 같다.

  1. request cookie 에서 {redirect_url} 을 꺼낸다.
  2. redirect_url 유효성 검증을 진행한다. (redirect_url forgery 방지 보안 로직)
    2-1. 유효하지 않으면 UnauthorizedRedirectUrlException을 발생시킴 -> FailureHandler로 이동한다.
  3. 로그인에 필요한 프로필 이미지와 토큰을 발급한다.
  4. request와 response 에 남아있을 cookie를 다 지운다.
  5. {redirect_url}?access_token={}&... 로 리다이렉트 시킨다.

1.4.1.2 redirect forgery 방지 보안 로직

미리 설정 파일에 기재해 둔 redirect_url 만 가능하다.
=> AuthProperities 확인

1.4.1.3 코드

@RequiredArgsConstructor
public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final TokenProvider tokenProvider;
    private final AuthProperities authProperties;  // @ConfigurationProperties
    private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        String targetUrl = this.determineTargetUrl(request, response, authentication);  // 1~3 번 과정 진행

        if (response.isCommitted()) {
            logger.debug("Response has already been committed. Unable to redirect to " + targetUrl);
            return;
        }

        this.clearAuthenticationAttributes(request);
        this.httpCookieOAuth2AuthorizationRequestRepository.clearCookies(request, response);
        this.getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }
determineTargetUrl(request, response, authentication)
    /**
     * @param authentication 인증 완료된 결과
     * @return 인증 결과를 사용해서 access 토큰을 발급하고, 쿠키에 저장되어 있던 redirect_uri(프론트에서 적어준 것)와 합쳐서 반환.
     * 명시되지 않으면 설정파일({@link AuthProperties})에 명시된 default redirect url 값 적용
     */
    @Override
    protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

        String targetUrl = CookieUtils.resolveCookie(request, REDIRECT_URL_PARAM_COOKIE_NAME)  // request 에서 쿠키를 꺼냄
                .map(Cookie::getValue)
                .orElse(authProperties.getOauth2().getDefaultRedirectUri()); // 없으면 default 로 설정파일에 기재해 둔 url 사용.

        if (notAuthorized(targetUrl)) {  // redirect forgery 검사 로직.
            /* 여기서 AuthenticationException 이 발생하면 예외는 AbstractAuthenticationProcessingFilter.doFilter 에서 처리된다.
             *   - AbstractAuthenticationProcessingFilter.doFilter 안에서 try~ catch~ 에서 잡힘.
             *   -    -> AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication()
             *   -    -> Oauth2AuthenticationFailureHandler().onAuthenticationFailure()
             * */
            throw new UnauthorizedRedirectUrlException();
        }

        String imageUrl = OAuth2UserInfoFactory.getOAuth2UserInfo((OAuth2AuthenticationToken) authentication)
                .getImageUrl();

        return UriComponentsBuilder.fromUriString(targetUrl)
                .queryParam("access_token", tokenProvider.createAccessToken(authentication))  // 토큰 발급
                .queryParam("refresh_token", tokenProvider.createRefreshToken(authentication))  // 리프레시 토큰 발급 및 저장
                .queryParam("expires_in", tokenProvider.getExpiration())
                .queryParam("image_url", imageUrl)
                .build().toUriString();
    }

    private boolean notAuthorized(String redirectUrl) {
        return !redirectUrl.isBlank() &&
                !authProperties.getOauth2().isAuthorizedRedirectUri(redirectUrl);  // 설정 파일에 적혀있는 redirect url 목록만 가능하다.
    }
}

1.4.2. FailureHandler

1.4.2.1 로직

OAuth2.0 인증 도중 오류가 하나라도 발생하면 호출된다.
{redirect_url}?error={}처럼 응답하는 것을 목표로 한다. 실패 핸들러의 로직은 아래와 같다.

  1. request 쿠키에서 {redirect_url} 을 꺼낸다.
  2. {redirect_url} 에 대한 유효성 검사를 진행한다. (redirect forgery 방지)
  3. 오류 메세지를 예외 인스턴스에서 꺼내서 url parameter 에 붙인다.
  4. request, response 에 남아있을 쿠키를 지운다.
  5. {redirect_url}?error={}형태의 주소로 리다이렉트한다.

1.4.2.2 코드

@RequiredArgsConstructor
public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    private final AuthProperties authProperties;
    private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
    private static final String ERROR_PARAM = "?error=";

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {

        String redirectUri = CookieUtils.resolveCookie(request, REDIRECT_URL_PARAM_COOKIE_NAME)
                .map(Cookie::getValue)
                .orElse(null);

        String targetUrl = getAuthorizedTargetUrl(exception, redirectUri);

        httpCookieOAuth2AuthorizationRequestRepository.clearCookies(request, response);
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }

    // ... 생략 ...

}
getAuthorizedTargetUrl(exception, redirectUri)
private String getAuthorizedTargetUrl(AuthenticationException exception, String redirectUri) {

    StringBuilder targetUrl = new StringBuilder();
    if (exception instanceof UnauthorizedRedirectUrlException || redirectUri.isBlank() || notAuthorized(redirectUri)) {
        targetUrl.append(authProperties.getOauth2().getDefaultRedirectUri());  // 유효하지 않으면 기본 주소로 리다이렉트
    }
    else {
        targetUrl.append(redirectUri);
    }
    targetUrl.append(ERROR_PARAM).append(getExceptionMessage(exception));  // 에러코드 붙이기

    return targetUrl.toString();
}

private boolean notAuthorized(String redirectUrl) {
    return !redirectUrl.isBlank() &&
            !authProperties.getOauth2().isAuthorizedRedirectUri(redirectUrl);  // 설정파일에 기재한 redirect_url 만 가능
}

1.5. OAuth2UserService 구현

DefaultOAuth2UserService를 상속받아서, loadUser를 구현해야한다.


1.5.1. 로직

  1. OAuth2 인증 결과를 이용하여 사용자 정보를 정제, 추출한다.
  2. 로그인 및 회원가입에 필요한 필수 값이 다 들어있는지 검사한다.
  3. 해당 소셜 계정을 db에 저장한다. (로그인 로그)
  4. 소셜 계정 정보로 기존 회원 권한을 조회하여 들고 온다.
  5. (소셜 게정 + 권한) 정보를 반환

1.5.2. CustomOAuth2UserService

@Component
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    private final SocialAccountService socialAccountService;  // 소셜 계정 정보 저장하기 위함.
    private final UserAuthorityProvider userAuthorityProvider;  // 계정 정보로 기존회원 권한을 검색하고 들고오기 위함.

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        DefaultOAuth2User oAuth2User = (DefaultOAuth2User) super.loadUser(userRequest);  

        OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(  // factory 패턴을 이용, provider 종류에 상관없이 일관성있게 사용하도록 OAuth2UserInfo 객체로 변환한다.
                userRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes());

        // 필수값 받아왔는지 확인
        if(!oAuth2UserInfo.validateNecessaryFields()) {
            throw new InvalidUserInfoException();
        }
        // db 에 소셜 계정 정보 update
        socialAccountService.updateSocialAccountInfo(oAuth2UserInfo);

        // 현재 로그인하려는 유저에 맞는 권한을 들고옴.
        Collection<SimpleGrantedAuthority> authorities = userAuthorityProvider.determineAuthorities(oAuth2UserInfo);

        String nameAttributeKey = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
                .getUserNameAttributeName();  // provider 자체 회원 table id 컬럼에 해당하는 field. (구글은 sub, 네이버는 uid, 등..)

        return new DefaultOAuth2User(authorities, oAuth2UserInfo.getAttributes(), nameAttributeKey);
    }
}

1.5.3. 소셜계정 정보

구글, 네이버, 카카오 등 provider 마다 개인정보를 반환하는 json 형태가 다 다르다.
다 다른 정보들을 일관성 있는 인터페이스로 사용하기 위해 OAuth2UserInfo 클래스를 생성했다.
Map<String, Object> -> OAuth2UserInfo 로의 변환을 위해 아래와 같은 팩토리 패턴을 사용한다.

1.5.3.1 OAuth2UserInfoFactory

public interface OAuth2UserInfoFactory {
    static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map<String, Object> attributes) {

        OAuth2UserInfo userInfo = null;
        OAuth2Provider oAuth2Provider = OAuth2Provider.convert(registrationId);
        switch (oAuth2Provider) {
            case GOOGLE:
                userInfo = new GoogleOAuth2UserInfo(attributes);
                break;
            case NAVER:
                userInfo = new NaverOAuth2UserInfo(attributes);
                break;
            case KAKAO:
                userInfo = new KakaoOAuth2UserInfo(attributes);
                break;
            default:
                throw new UnsupportedOAuth2ProviderException();
        }

        return userInfo;
    }

    // ...
}

1.5.3.2 OAuth2UserInfo

public abstract class OAuth2UserInfo {

    protected Map<String, Object> attributes;
    protected OAuth2Provider provider;
    public OAuth2UserInfo(OAuth2Provider provider, Map<String, Object> attributes) {
        this.provider = provider;
        this.attributes = attributes;
    }
    public OAuth2Provider getProvider() {
        return provider;
    }
    public Map<String, Object> getAttributes() {
        return Collections.unmodifiableMap(attributes);
    }
    public abstract String getId();
    public abstract String getName();
    public abstract String getEmail();
    public abstract String getImageUrl();
    public abstract Map<String, Object>  getExtraData();

    public boolean validateNecessaryFields() {
        return StringUtils.hasText(this.getEmail())
                && StringUtils.hasText(this.getId())
                && StringUtils.hasText(this.getName())
                && StringUtils.hasText(this.getImageUrl());
    }
}

변환에 관련된 코드는 여기서 더 확인할 수 있다.


1.5.4. 기존회원 권한 불러오기

1.5.4.1 로직 (제어의 역전)

이 부분에서 조금 고민을 했었다.
인증 모듈은 순수한 모듈이어야 하지, 회원 서비스를 의존해서는 안된다고 생각했다.
하지만 회원 서비스를 통해야만, 회원 권한 정보를 불러올 수 있었다.
그래서 UserAuthorityProvider 라는 인터페이스를 만들어서, 회원 서비스가 determineAuthorities 메소드를 구현하도록 강제했다. (제어의 역전)

1.5.4.2 인터페이스

public interface UserAuthorityProvider {

    Collection<SimpleGrantedAuthority> determineAuthorities(OAuth2UserInfo oAuth2UserInfo);
}

1.5.4.3 기본 구현체

아무런 구현체도 없으면 실행 시 오류가 발생한다.
따라서 기본 구현체를 만들어두고 @ConditionalOnMissingBean으로 빈으로 등록했다.
회원 서비스에서 따로 구현을 하면, 이 구현체는 사용되지 않는다.
따로 구현을 하지 않으면, 이 구현체가 Bean으로 등록되어 모든 사용자는 anonymous 라는 권한을 갖는다.

public class DefaultUserAuthorityProvider implements UserAuthorityProvider {

    @Override
    public Collection<SimpleGrantedAuthority> determineAuthorities(OAuth2UserInfo oAuth2UserInfo) {

        return Collections.singleton(new SimpleGrantedAuthority("ROLE_anonymous"));
    }
}


2. 토큰

OAuth2 인증이 성공적으로 끝나고 기존 회원이라는 사실이 최종적으로 확인되면, successHanlder 가 호출된다.  
핸들러에서는 해당 회원에게 토큰을 발급하고, api 요청을 할때마다 요청 헤더에 `Authorization : Bearer {access_token}` 형식으로 넣어주어야 한다.  
그러면 api server 에서는 securityFilterChain 에서 해당 토큰을 인증하고 인증 결과를 `securityContext` 에 담도록 해야한다.  
`securityContext` 에 담긴 인증결과를 기반으로 접근 제어를 한다.  

따라서 구현한 사항은 다음과 같다.  
1. 토큰 생성
2. 토큰 재발급
3. 토큰을 인증하는 Filter

추가적으로 여기서는 JWT 토큰을 사용하지만, 다른 형태의 토큰을 사용할 수 있으므로  
Token에 대한 추상화 수준을 높이고 JWT 토큰을 구현체로 사용하도록 했다.

2.1. 토큰 생성

2.1.1 TokenProvider

(1) 액세스 토큰 및 리프레시 토큰 생성 (2) 토큰 유효성 검증 (3) 토큰 decode : 토큰 정보를 해석해서 securityContext 에 담을 수 있는 AbstractAuthenticationToken 의 subclass type 으로 반환한다.

public interface TokenProvider {

    boolean validate(String token);

    TokenAuthenticationResult decode(String token);

    TokenDto reissueAccessTokenUsing(String refreshToken) throws JwtException;

    /**
     * @param authentication the result of OAuth 2.0 authentication
     * @return jwt token string
     */
    String createAccessToken(Authentication authentication);

    /**
     * Some transactions may occur here whenever need to save refresh tokens.
     * @param authentication the result of OAuth 2.0 authentication
     * @return jwt token string
     */
    String createRefreshToken(Authentication authentication);

    /**
     * @return get {@code expires_in} response parameter value of the access token in seconds
     */
    Long getExpiration();
}

2.1.1.1 TokenAuthenticationResult

image

토큰 정보를 해석해서 securityContext 에 담을 수 있는 AbstractAuthenticationToken 의 subclass type 인 TokenAuthenticationResult으로 반환한다.

이 모듈에서 사용하는 토큰은 OAuth2 인증결과를 바탕으로 만들기 때문에, OAuth2UserInfoAuthentication 클래스를 만들어서 OAuth2 사용자 정보를 담는다.

토큰 도메인의 구현체 도메인인 JwtToken 은 최종적으로 OAuth2UserInfoAuthentication를 상속받아서 JwtAuthenticationResult 를 인증객체로 사용한다.

2.1.1.2 TokenDto

@Getter
public class TokenDto {

    private final String grantType;
    private final String accessToken;
    private final String refreshToken;
    private final Long expiresIn;

    @Builder
    public TokenDto(String grantType, String accessToken, String refreshToken, Long accessTokenExpireDate) {
        this.grantType = grantType;
        this.accessToken = accessToken;
        this.refreshToken = refreshToken;
        this.expiresIn = accessTokenExpireDate;
    }
}


3. 예외

Clone this wiki locally