Spring/Spring

[Spring] Spring Security + OAuth2.0 + Jwt 예제 구현

신민석 2024. 3. 29. 00:53

이전 포스팅의 구현 코드를 기반으로 예제를 구현했으니 참고해주세요

 

tps://comumu.tistory.com/76

 

[Spring] Spring Security 와 Jwt 를 이용한 회원가입, 로그인 구현

코드 구현 설계 이번에 제가 만들 프로젝트에서는 일반 로그인 (이메일과 비밀번호를 이용한 로그인) 과 소셜로그인 (구글 로그인, 애플 로그인) 각각 두가지 방식으로 로그인을 진행할 수 있게

comumu.tistory.com

 

프로젝트 설정

 

라이브러리 추가

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

 

OAuth2.0 관련 기능을 구현하기 위해 다음과 같이 라이브러리를 build.gradle 파일에 추가합니다.

 

이외에 Google Cloud 에서 client-id 와 client-secret 를 발급받아 application.yml 파일에 추가해 줘야 하는데 발급하는 과정은 생략하겠습니다. 

 

발급받은 key 추가

 

발급받은  client-id 와 client-secret 를 application.yml 파일에 추가합니다.

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: [발급받은 Client Id]
            client-secret: [발급 받은 Client Secret]
            scope: profile, email

 

 

OAuth 2.0 관련 클래스

 

파일 구조

 

oauth 패키지를 만들어 OAuth2.0 기능을 구현하는데 필요한 클래스들을 다음과 같이 설정했습니다. 코드에 대해 자세히 설명하기 전 각각의 클래스들이 어떤 역할을 가지고 있는지 간략하게 설명하겠습니다.

 

CustomOAuth2UserService : OAuth 로그인 로직을 담당하는 기능 구현


CustomOAuth2User : OAuth2UserService 에서 사용할 OAuth2 로그인용 클래스


OAuthAttributes : 각 소셜별로 받아오는 데이터가 다르므로 소셜별로 데이터를 처리하는 DTO 클래스


OAuth2UserInfo : 각 소셜 타입별로 유저 정보를 가지는 추상 클래스


GoogleOAuth2UserInfo : Google 에서 받아올 유저 정보를 정의한 DTO 


OAuth2LoginFailureHandler : OAuth 로그인이 실패했을때 작동하는 기능 구현


OAuth2LoginSuccessHandler : OAuth 로그인이 성공했을때 작동하는 기능 구현

 

🎯 CustomOAuth2User

package security.account.oauth;

import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import security.account.domain.Role;

import java.util.Collection;
import java.util.Map;


@Getter
public class CustomOAuth2User extends DefaultOAuth2User {

    private String email;
    private Role role;

    public CustomOAuth2User(Collection<? extends GrantedAuthority> authorities,
                            Map<String, Object> attributes, String nameAttributeKey,
                            String email, Role role) {
        super(authorities, attributes, nameAttributeKey);
        this.email = email;
        this.role = role;
    }
}

 

OAuth 로그인을 할때 사용자 인증에 사용되는 객체 입니다. OAuth 인증에 사용되는 객체를 구현할때는 기본적으로 DefaultOAuth2User 를 상속받아 구현합니다. email  role 필드를 추가해 커스텀했습니다.

 

만약 email 과 role 과 같은 추가 정보가 필요 없다면 DefaultOAuth2User 를 그대로 사용해도 괜찮습니다.

 

🎯 OAuthAttributes

package security.account.oauth;


import lombok.Builder;
import lombok.Getter;
import security.account.domain.Role;
import security.account.domain.SocialType;
import security.account.domain.User;
import security.account.oauth.userinfo.GoogleOAuth2UserInfo;
import security.account.oauth.userinfo.OAuth2UserInfo;

import java.util.Map;
import java.util.UUID;


@Getter
public class OAuthAttributes {

    private String nameAttributeKey; 
    private OAuth2UserInfo oauth2UserInfo; 

    @Builder
    private OAuthAttributes(String nameAttributeKey, OAuth2UserInfo oauth2UserInfo) {
        this.nameAttributeKey = nameAttributeKey;
        this.oauth2UserInfo = oauth2UserInfo;
    }


    public static OAuthAttributes of(SocialType socialType,
                                     String userNameAttributeName, Map<String, Object> attributes) {

//        if (socialType == SocialType.GOOGLE) {
//            return ofNaver(userNameAttributeName, attributes);
//        }
//        if (socialType == SocialType.APPLE) {
//            return ofKakao(userNameAttributeName, attributes);
//        }
        return ofGoogle(userNameAttributeName, attributes);
    }


    public static OAuthAttributes ofGoogle(String userNameAttributeName, Map<String, Object> attributes) {
        return OAuthAttributes.builder()
                .nameAttributeKey(userNameAttributeName)
                .oauth2UserInfo(new GoogleOAuth2UserInfo(attributes))
                .build();
    }

    public User toEntity(SocialType socialType, OAuth2UserInfo oauth2UserInfo) {
        return User.builder()
                .socialType(socialType)
                .socialId(oauth2UserInfo.getId())
                .email(UUID.randomUUID() + "@socialUser.com")
                .nickName(oauth2UserInfo.getNickname())
                .role(Role.NORMAL)
                .build();
    }
}

 

 

nameAttributeKey : OAuth 로그인 진행 시 키가 되는 필드값입니다.

 

oauthUserInfo : 소셜타입별로 로그인 유저 정보를 가진 객체입니다. 

 

of 메서드 : 정적 팩토리 메서드 패턴으로 구현했고, SocialType 별로 필요한 데이터를 추출해 객체를 생성합니다. 하지만 여기선 Google OAuth2 로그인만 다루기 때문에 소셜타입별로 데이터를 추출하는 과정은 생략했습니다.

 

toEntity 메서드 : 자신의 DTO 인스턴스를 이전 포스팅에서 구현한 User 엔티티로 변환하는 기능입니다. 단 여기서 email 을 UUId 로 생성한 이유는 Jwt 토큰을 발급하기 위한 용도이므로 임의로 설정하기 위함입니다.

 

🎯 OAuth2UserInfo

package security.account.oauth.userinfo;

import java.util.Map;

public abstract class OAuth2UserInfo {

    protected Map<String, Object> attributes;

    public OAuth2UserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    public abstract String getId(); 

    public abstract String getNickname();

}

 

소셜 타입별로 유저 정보를 갖는 추상 클래스입니다.

 

 

🎯 GoogleOAuth2UserInfo

package security.account.oauth.userinfo;

import java.util.Map;

public class GoogleOAuth2UserInfo extends OAuth2UserInfo {

    public GoogleOAuth2UserInfo(Map<String, Object> attributes) {
        super(attributes);
    }

    @Override
    public String getId() {
        return (String) attributes.get("sub");
    }

    @Override
    public String getNickname() {
        return (String) attributes.get("name");
    }

}

 

🎯 CustomOAuth2UserService

package security.account.oauth.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import security.account.domain.SocialType;
import security.account.domain.User;
import security.account.oauth.CustomOAuth2User;
import security.account.oauth.OAuthAttributes;
import security.account.repository.UserRepository;

import java.util.Collections;
import java.util.Map;


//OAuth2 의 로그인 로직을 담당
@Slf4j
@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final UserRepository userRepository;


    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        log.info("OAuth2 로그인 요청 진입");

        /**
         * DefaultOAuth2UserService 객체를 생성해 .loadUser() 메서드 를 이용해 OAuth 서비스에서 사용자 정보를 가져온다(OAuth2User)
         */
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        /**
         * userRequest 는 oauth2/authorization/google 다음과 같은 요청이 들어온다.
         */
        String registrationId = userRequest.getClientRegistration().getRegistrationId(); // getRegistrationId() 를 이용하면 userRequest 에서 google 부분을 추출한다.
        SocialType socialType = getSocialType(registrationId);

        /**
         * userNameAttributeName 는 social 종류 별로 다르게 들어온다. 소셜 식별 값 : 구글 : "sub", 카카오 : "id", 네이버 : "id"
         */
        String userNameAttributeName = userRequest.getClientRegistration()
                .getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();

        // 소셜로그인 API 가 제공하는 사용자 Json 정보
        Map<String, Object> attributes = oAuth2User.getAttributes();


        OAuthAttributes extractAttributes = OAuthAttributes.of(socialType, userNameAttributeName, attributes);

        User createUSer = getUser(extractAttributes, socialType);

        // CustomOAuth2User 를 생성해서 반환한다. 만약 여기까지 코드가 정상 실행되면 OAuth2LoginSuccessHandler 가 실행되게 SecurityConfig 에서 설정할 예정이다.
        return new CustomOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(createUSer.getRole().getKey())),
                attributes,
                extractAttributes.getNameAttributeKey(),
                createUSer.getEmail(),
                createUSer.getRole()
        );
    }

    /**
     * 소셜 타입에 따라 생성한 OAuthAttributes 와 SocialType 을 이용해 DB 에서 사용자를 조회한다.
     * 만약 DB 에 사용자가 없으면 사용자를 DB 에 저장한다.
     */
    private User getUser(OAuthAttributes extractAttributes, SocialType socialType) {
        User findUser = userRepository.findBySocialTypeAndSocialId(socialType,
                extractAttributes.getOauth2UserInfo().getId()).orElse(null);

        if(findUser == null) {
            return saveUser(extractAttributes, socialType);
        }
        return findUser;
    }

    private User saveUser(OAuthAttributes extractAttributes, SocialType socialType) {
        User createdUser = extractAttributes.toEntity(socialType, extractAttributes.getOauth2UserInfo());
        return userRepository.save(createdUser);
    }

    private SocialType getSocialType(String registrationId) {
        // 만약 소셜 타입이 추가된다면 registrationId 을 이용한 소셜타입을 필터링 하는 로직 추가
        return SocialType.GOOGLE;
    }
}

 

CustomOAuth2UserService 클래스는 OAuth2 로그인 로직을 담당합니다.

 

OAuth2UserService<OAuth2UserRequest, OAuth2User> 를 상속받아 loadUser 를 구현하면 loadUser 안에 OAuth2 로직을 구현하면 됩니다. 

 

loadUser 안에 구현한 로그인 로직이 모두 정상 작동하면 CustomOAuth2User 를 만들어 반환합니다. 이때 CustomOAuth2User 클래스는 OAuth2User 를 상속받아 구현한 클래스이기 때문에 반환할 수 있는겁니다.

 

loadUser 로직이 끝나면 OAuth2LoginSuccessHandler 클래스를 실행해야 하는데 이는 SecurityConfig 에 추가하면됩니다(아래에서 다시한번 설명드리겠습니다.)

 

🎯 OAuth2LoginSuccessHandler

package security.account.oauth.handler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.parameters.P;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import security.account.domain.Role;
import security.account.filter.JwtAuthenticationFilter;
import security.account.oauth.CustomOAuth2User;
import security.account.repository.UserRepository;
import security.account.service.JwtService;
import org.springframework.security.core.Authentication;

import java.io.IOException;

@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {

    private final JwtService jwtService;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("OAuth2 Login 성공");

        try {
            //CustomOAuth2UserService 에서 반환한 CustomOAuth2User 를 가져온다.
            CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();

            if (oAuth2User.getRole() == Role.NORMAL) {
                String accessToken = jwtService.createAccessToken(oAuth2User.getEmail());
                response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken);

                response.sendRedirect("oauth2/sign-up"); // 프론트쪽의 회원가입 추가 정보 입력 폼으로 리다리렉트

                jwtService.sendAccessAndRefreshToken(response, accessToken, null);
                return;
            } else {
                loginSuccess(request, response, oAuth2User);
            }
        } catch (Exception e) {
            throw e;
        }

    }

    private void loginSuccess(HttpServletRequest request, HttpServletResponse response, CustomOAuth2User oAuth2User) {

        String refreshToken = extractRefreshToken(request);

        if (refreshToken != null) {
            createOnlyAccessToken(oAuth2User, response);
            return;
        }

        if (refreshToken == null) {
            createAccessTokenAndRefreshToken(oAuth2User, response);
            return;
        }
    }

    private String extractRefreshToken(HttpServletRequest request) {
        return jwtService.extractRefreshToken(request)
                .filter(jwtService::validateToken)
                .orElse(null);
    }

    private void createOnlyAccessToken(CustomOAuth2User oAuth2User, HttpServletResponse response) {
        String accessToken = jwtService.createAccessToken(oAuth2User.getEmail());
        response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken);
        jwtService.sendAccessToken(response, accessToken);
        log.info("AccessToken 만 발급 = " + accessToken);
    }

    private void createAccessTokenAndRefreshToken(CustomOAuth2User oAuth2User, HttpServletResponse response) {
        String accessToken = jwtService.createAccessToken(oAuth2User.getEmail());
        String updateRefreshToken = jwtService.createRefreshToken();
        response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken);
        response.addHeader(jwtService.getRefreshHeader(), "Bearer " + updateRefreshToken);
        jwtService.sendAccessAndRefreshToken(response, accessToken, updateRefreshToken);
        jwtService.updateRefreshToken(oAuth2User.getEmail(), updateRefreshToken);
        log.info("AccessToken 발급 = " + accessToken);
        log.info("RefreshToken 발급 = " + updateRefreshToken);
    }


}

 

OAuth 로그인이 성공했을때 실행시킬 클래스입니다. AuthenticationSuccessHandler 를 상속받아 onAuthenticationSuccess 메서드안에 성공했을때 어떤 처리를 진행할지 구현한 코드입니다.

 

Role  NORMAL 일때는 추가적인 회원 정보(나이, 휴대폰 번호) 등록이 필요합니다. 때문에 리다이렉트 URL 을 클라이언트에게 보내고 클라이언트는 응답받은 URL 로 이동하게끔 구현하면 됩니다.

 

만약 이미 추가 정보를 등록한 사용자가 로그인을 한다면 loginSuccess 메서드가 실행되는데 RefreshToken 여부를 확인해 AccessToken 만 발행할지 아니면 RefreshToken 도 함께 발행할지를 확인합니다.

 

🎯 OAuth2LoginFailureHandler

package security.account.oauth.handler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.getWriter().write("소셜 로그인 실패");
        log.info("소셜 로그인에 실패했습니다. 에러 메시지 : {}", exception.getMessage());
    }
}

 

OAuth 로그인을 실패했을때 실패 로그와 함께 에러메시지를 확인합니다.

 

🎯 SecurityConfig 

package security.account.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import security.account.filter.CustomLoginAuthenticationFilter;
import security.account.filter.JwtAuthenticationFilter;
import security.account.handler.LoginFailureHandler;
import security.account.handler.LoginSuccessHandler;
import security.account.oauth.handler.OAuth2LoginFailureHandler;
import security.account.oauth.handler.OAuth2LoginSuccessHandler;
import security.account.oauth.service.CustomOAuth2UserService;
import security.account.repository.UserRepository;
import security.account.service.JwtService;
import security.account.service.LoginService;

/**
 * 인증은 CustomJsonUsernamePasswordAuthenticationFilter에서 authenticate()로 인증된 사용자로 처리
 * JwtAuthenticationProcessingFilter는 AccessToken, RefreshToken 재발급
 */
@Configuration
@EnableWebSecurity // @EnableWebSecurity 어노테이션을 붙여야 Spring Security 기능을 사용할 수 있다.
@RequiredArgsConstructor
public class SecurityConfig {

    private final LoginService loginService;
    private final JwtService jwtService;
    private final UserRepository userRepository;
    private final ObjectMapper objectMapper;
    private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
    private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler;
    private final CustomOAuth2UserService customOAuth2UserService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .formLogin((formLogin) -> formLogin.disable()) // Spring Security 에서는 아무 설정을 하지 않으면 기본 FormLogin 형식의 로그인을 제공한다
                .httpBasic((httpBasic) -> httpBasic.disable()) // JWT 토큰을 사용한 로그인 방식이기 때문에 httpBasic disable 로 설정
                .csrf((csrfConfig) -> csrfConfig.disable()) // 서버에 인증 정보를 저장하지 않고, 요청 시 인증 정보를 담아서 요청 하므로 보안 기능인 csrf 불필요
                .httpBasic((httpBasic) -> httpBasic.disable())
                .headers((headerConfig) ->
                        headerConfig.frameOptions(frameOptionsConfig ->
                                frameOptionsConfig.disable()
                        )
                )
                // 세션 사용 x
                .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/sign-up").permitAll()
                        .requestMatchers("/admin/**").hasRole("ADMIN")

                        .anyRequest().authenticated()
                )

                .oauth2Login((oAuth2LoginConfigurer) -> oAuth2LoginConfigurer
                        .successHandler(oAuth2LoginSuccessHandler)
                        .failureHandler(oAuth2LoginFailureHandler)
                        .userInfoEndpoint((userInfoEndpointConfig -> userInfoEndpointConfig.userService(customOAuth2UserService))));

        http.addFilterAfter(customLoginAuthenticationFilter(), LogoutFilter.class);
        http.addFilterBefore(jwtAuthenticationFilter(), CustomLoginAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public CustomLoginAuthenticationFilter customLoginAuthenticationFilter() {
        //CustomJsonUsernamePasswordAuthenticationFilter 에서 인증할 객체(Authentication) 생성
        CustomLoginAuthenticationFilter customJsonUsernamePasswordLoginFilter
                = new CustomLoginAuthenticationFilter(objectMapper);

        //일반 로그인 인증 로직
        customJsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager());

        customJsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessHandler());
        customJsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler());
        return customJsonUsernamePasswordLoginFilter;
    }

    @Bean
    public AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        //비밀번호 인코딩
        provider.setPasswordEncoder(passwordEncoder());
        //loginService loadUserByUsername 호출 이때 DaoAuthenticationProvider 가 username 을 꺼내서 loadUserByUsername 을 호출
        provider.setUserDetailsService(loginService);
        // loadUserByUsername 에서 전달받은 UserDetails 에서 password를 추출해 내부의 PasswordEncoder 에서 password 가 일치하는지 검증을 수행
        return new ProviderManager(provider);
    }

    // jwt 인증필터 빈 등록
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(jwtService, userRepository);
        return jwtAuthenticationFilter;
    }

    // 로그인 성공 시 호출되는 LoginSuccessHandler 빈 등록

    @Bean
    public LoginSuccessHandler loginSuccessHandler() {
        return new LoginSuccessHandler(jwtService, userRepository);
    }

    // 로그인 실패 시 호출되는 LoginFailureHandler 빈 등록
    @Bean
    public LoginFailureHandler loginFailureHandler() {
        return new LoginFailureHandler();
    }

    // 패스워드 인코딩을 위한 기능 빈 등록
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

 

Spring Security Config 설정 클래스입니다. (이전 포스팅 참고)

 

.oauth2Login((oAuth2LoginConfigurer) -> oAuth2LoginConfigurer
        .successHandler(oAuth2LoginSuccessHandler)
        .failureHandler(oAuth2LoginFailureHandler)
        .userInfoEndpoint((userInfoEndpointConfig -> userInfoEndpointConfig.userService(customOAuth2UserService))));

 

OAuth2.0 기능이 실행되기 위해 추가한 코드고 성공했을때, 실패했을때 Handler를 각각 넣어줬고 userInfoEndpoint() 부분은 로그인 로직을 담당할 CustomOAuth2UserService 를 넣어줬습니다!

 

그러면 자연스럽게 CustomOAuth2UserService 안에 있는 loadUser 메서드가 실행되고 만약 정상적으로 모든 로그인 로직이 동작한다면 OAuth2User 객체를 만들어 반환해 OAuth2LoginSuccessHandler 안에 있는 onAuthecticationSuccess 메서드 파라미터의 Authectication 에 담겨 보내지고 (CustomOAuth2User) 형변환을 통해 로그인이 성공했을때의 로직을 실행할 수 있게됩니다!

 

테스트 

 

(1) http://localhost:8080/login 접속

 

http://localhost:8080/login 주소로 접속하면 Spring Security 가 기본으로 제공하는 로그인 페이지가 제공됩니다. Google 버튼을 클릭하면 /oauth2/authorization/google 이 호출되며 Google Login 폼이 나옵니다.

 

(2) DB 확인

 

 

제공된 구글 로그인 폼을 모두 통과하면 DB에 다음과 같이 데이터가 들어가는걸 확인할 수 있습니다.

 

(3) AccessToken 유효성 확인

 

 

@GetMapping("/admin/test")
public String testAdmin(){

    System.out.println("ADMIN API CALL");
    return "ok";
}

 

UserController 클래스에 다음과 같이 테스트용 api 를 추가합니다.

 

/admin 으로 시작하는 요청은 Role 이 Admin 일때 허용되게끔 SecurityConfig 클래스에 설정해 줬기때문에 DB 에 사용자 권한을 ADMIN 으로 변경합니다.

 

이후 다시한번 로그인을 진행하고 Network Response Header 의 Authorization 에 AccessToken 이 잘 들어온걸 확인할 수 있습니다.

 

 

 

발급받은 AccessToken 을 Header 에 담아 요청을 보내 테스트해봅니다. (이때 Bearer 를 토큰 앞에 꼭 추가해줘야 합니다.) 정상적으로 동작하면 ok 라는 응답을 받게 됩니다!