본문 바로가기

Back-End/Spring

Spring Security + OAuth2.0 + Jwt 활용 소셜 로그인 기능 만들기

의존성

  • Spring Security
  • OAuth2 Client
  • Lombok
  • Spring Data Jpa
//JJWT 의존성
dependencies {
    implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
    implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
    implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
}

구현 방식

  • 모든 채임을 백엔드에서 맡아(로그인 페이지 요청 -> 코드 발급 -> Access 토큰 -> 유저 정보 획득 -> JWT 발급)

동작 모식도

https://www.devyummi.com/page?id=669357f17c05b2f78ae4e7fa

모식도를 통한 소셜 로그인 동작 순서

1. 사용자의 소셜 로그인 시도

  • 클라이언트가 특정 소셜 서비스에 로그인을 시도
  • 사용자가 소셜 로그인 버튼을 클릭하면 /oauth2/authorization/{서비스명} 경로로 요청이 전달된다. spring Security에서 소셜 인증을 시자하는 엔드포인트

2. 외부 소셜 로그인 페이지로 리다이렉션

  • 서버는 사용자를 해당 소셜 서비스의 로그인 페이지로 리다이렉트 한다.
  • 사용자는 이 페이지에서 소셜 서비스 계정으로 로그인하 한다.

3. 로그인 성공 후 리다이렉트

  • 소셜 서비스는 사용자가 인증을 완료하면 redirect_uri로 설정된 URL로 사용자를 다시 리다이렉트 한다.
  • 이 URL은 일반적으로 /login/oauth2/code/{서비스명} 와 같은 형식(관습)
  • 이 과정에서 인증 제공자(Ex.카카오 등)가 Authrization Code를 서버로 반환.

4. Authorization Code로 Access Token 발급

  • 서버는 전달받은 Authorization Code를 사용해 소셜 서비스의 인증 서버와 통신하여 Access Token을 요청한다.
  • 이 작업은 Spring Security의 OAuth2LoginAuthenticationProvider가 담당한다.

5. AccessToken으로 사용자 정보 획득

  • Access Token을 발급 받은 후 서버는 해당 토큰을 이용해 소셜 리소스 서버에 사용자 정보를 요청한다.
  • 이 단계에서 OAuth2Service와 OAuth2User를 통해 사용자 정보가 처리된다.

6. 로그인 성공 핸들링

  • 사용자의 인증이 성공적으로 완료되면 LoginSuccessHandler가 실행된다.
  • 이 핸들러는 사용자 정보를 기반으로 세션을 생성 또는 JWT를 발급하는 등 작업을 수행

7. JWT 발급 및 요청 인증

  • JWT를 사용한다면 JWTFilter를 통해 인증이 이루어진다.
  • 클라이언트는 이후 요청마다 JWT를 포함해 서버와 통신하며 서버는 JWT를 검증해 사용자를 인증한다.
  • OAuth를 통해 인증했는데 또 인증하냐?
    > OAuth2 인증 과정은 AccessToken을 발급받기 위한 단발성 작업인데 사용자가 계속해서 요청을 보낼 따마다 OAtuh2 과정을 반복할 수 없다. 그래서 JWT를 통해 이후 요청을 처리하는 방식이 효율적이다.

우리가 OAuth2 클라이언트에서 구현해야 할 부분

  • OAuth2UserDetailsService
  • OAuth2UserDetails
  • LoginSuccessHandler

JWT에서 구현해야 할 부분

  • JwtFilter
  • JwtUtils

들어가기 전 UserEntity, UserDto, UserRepository 생성

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;
    private String name;
    private String email;
    private String role;

    public static UserEntity createUser(String username, String name, String email, String role) {
        return new UserEntity(
                null,
                username,
                name,
                email,
                role
        );
    }

    public void updateUser(String email, String name){
        this.email = email;
        this.name = name;
    }
}
@Getter
@AllArgsConstructor
public class UserDto {
    private String username;
    private String name;
    private String role;
}

OAuth2 소셜 로그인을 위한 변수 설정(application.properties or yaml, 해외 소셜 로그인들은 provider 안해두 됨)
client-id와 client-secret은 소설 서비스에서 API를 발급 받으면 받을 수 있음

#registration
spring.security.oauth2.client.registration.서비스명.client-name=서비스명
spring.security.oauth2.client.registration.서비스명.client-id=서비스에서 발급 받은 아이디
spring.security.oauth2.client.registration.서비스명.client-secret=서비스에서 발급 받은 비밀번호
spring.security.oauth2.client.registration.서비스명.redirect-uri=서비스에 등록한 우리쪽 로그인 성공 URI
spring.security.oauth2.client.registration.서비스명.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.서비스명.scope=리소스 서버에서 가져올 데이터 범위

#provider
spring.security.oauth2.client.provider.서비스명.authorization-uri=서비스 로그인 창 주소
spring.security.oauth2.client.provider.서비스명.token-uri=토큰 발급 서버 주소
spring.security.oauth2.client.provider.서비스명.user-info-uri=사용자 정보 획득 주소
spring.security.oauth2.client.provider.서비스명.user-name-attribute=응답 데이터 변수

SecurityConfig 구현하기

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //로그인 과정 커스터마이징
        http.oauth2Login((oauth2) -> oauth2
                .userInfoEndpoint((userInfoEndpointConfig -> userInfoEndpointConfig
                        .userService(customOAuth2UserService))));

        //경로별 인가 작업
        http.authorizeHttpRequests((auth) -> auth
                .requestMatchers("/").permitAll()
                .anyRequest().authenticated());

        //Security 에서 세션 설정 stateless는 서벅가 클라이언트의 상태 정보를 저장하지 않는 방식
        http.sessionManagement((session) -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();
    }
}

코드 하나씩 뜯어보기

//로그인 과정 커스터마이징
http.oauth2Login((oauth2) -> oauth2
        .userInfoEndpoint((userInfoEndpointConfig -> userInfoEndpointConfig
              .userService(customOAuth2UserService))));

1. http.oauth2Login()

  • OAuth2.0 로그인 설정을 정의
  • 이 메서드를 통해 에플리케이션이 OAuth2 인증 제공자(Ex. 구글, 네이버, 카카오 등)를 사용해 로그인을 처리할 수 있다.

2. .userInfoEndPoint()

  • OAuth 2.0 로그인 과정에서 사용자 정보를 처리하는 부분을 설정
  • 로그인 성공 후 인증 제공자에서 반환된 사용자 정보를 로드하거나 커스터마이징할 때 사용한다.

3. .userService(customOAuth2UserService)

  • 사용자 정보를 처리하기 위한 커스텀 서비스를 등록한다.
  • customOAuth2UserService는 CustomOAuth2USerService 클래스의 인스턴스이며 사용자가 인증 제공자(Ex. 네이버, 카카오, 구글 등)로부터 가져온 정보를 적절히 파싱하고 처리하는 역할
//경로별 인가 작업
http.authorizeHttpRequests((auth) -> auth
         .requestMatchers("/").permitAll()
         .anyRequest().authenticated());

1. http.authorizeHttpRequest()

  • 이 메서드는 요청에 대한 인가 규칙을 정의하기 위해 사용된다.
  • 요청을 분석하고 특정 패턴에 따라 접근을 허용하거나 인증을 요구하는 등의 동작을 설정할 수 있다.

2. requestMatchers("/").permitAll()

  • request matcher는 어떤 URL 패턴에 대해 규칙을 적용할지 지정합니다.
  • 이 경우 /(루트경로)에 대해 모든 사용자에게 접근을 허용한다.
  • 인증된 사용자뿐만 아니라 비로그인 사용자도 접근이 가능하다.

3. .anyRequest()

  • requestMatchers()로 정의되지 않은 나머지 모든 요청에 대해 규칙을 설정

4. .authenticated()

  • 나머지 모든 요청은 인증된 사용자만 접근할 수 있다.
  • 여기서 인증된 사용자는 로그인한 사용자를 의미함.
    * .hasRole("ROLE_USER") : 특정 역할(권한)을 가진 사용자만 접근 허용
//Security 에서 세션 설정 stateless는 서벅가 클라이언트의 상태 정보를 저장하지 않는 방식
http.sessionManagement((session) -> session
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));
  • http.sessionManagement()를 사용해 애플리케이션에서 세션이 어떻게 생성되고 관리되는지를 정의
  • JWT는 클라이언트가 인증 상태를 스스로 관리하도록 돕는 방식이기 때문에 세션 관리가 불필요

OAuth2UserService

  • CustomOAuth2UserService
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        
        // 1. 부모 클래스의 loadUser 메서드를 호출하여 OAuth2.0 사용자 정보 로드
        OAuth2User oAuth2User = super.loadUser(userRequest);

        // 2. 어떤 인증 제공자로부터 온 요청인지 확인
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        
        // 3. 인증 제공자에 따라 사용자 정보를 처리
        OAuth2Response oAuth2Response = null;
        if (registrationId.equals("naver")) {
            oAuth2Response = new NaverResponse(oAuth2User.getAttributes());

        } else if (registrationId.equals("google")) {
            oAuth2Response = new GoogleResponse(oAuth2User.getAttributes());
        } else {
            return null;
        }

    }
}

코드 하나씩 뜯어보기

1. DefaultOAuth2UserService

  • SpringSecurity에서 기본적으로 제공하는 OAuth 2.0 사용자 정보 처리 클래스
  • 이 클래스를 상속 받아 loadUser 메서드를 오버라이드하여 사용자 정보를 커스터마이징한다.

2. OAuth2UserRequest

  • OAuth 2.0 인증 요청에 대한 정보를 담고 있는 객체이다.
  • 이 객체를 통해서
    • getClientRegistration() : 어떤 인증 제공자가 요청했는지 확인
    • getAccessToken() : 요청에 사용된 엑세스 토큰을 가져올 수 있다.

3. OAuth2User

  • OAuth 2.0 인증 제공자에서 반환한 사용자 정보를 나타내는 객체
  • oAuth2User.getAttribute() 를 통해 사용자 정보를 조회할 수 있다
    Ex. 네이버 : response.id, response.email 등

4. registrationId

  • 인증 제공자를 식별하는 문자열, 이 값은 Spring Security의 application.yml 또는 application.properties에서 정의된 클라이언트 등록 정보와 매칭된다.

5. NaverResponse와 GoogleResponse

  • 인증 제공자마다 반환하는 사용자 정보의 구조가 다르다.
  • NaverResponse와 GoogleResponse 클래스는 이를 적절히 파싱하거나 가공하기 위한 클래스

OAuth2Response

public class NaverResponse implements OAuth2Response{

    private final Map<String, Object> attribute;

    /**
     * 네이버 데이터가 JSON 형태로 전달해줌
     *{ resultcode=00, message=success, response={id=123123123, name=밥병천}}
     */
    public NaverResponse(Map<String, Object> attribute) {
        this.attribute = (Map<String, Object>) attribute.get("response");
    }


    @Override
    public String getProvider() {
        return "naver";
    }

    @Override
    public String getProviderId() {
        return attribute.get("id").toString();
    }

    @Override
    public String getEmail() {
        return attribute.get("email").toString();
    }

    @Override
    public String getName() {
        return attribute.get("name").toString();
    }
}

 

네이버 OAuth 2.0 인증 제공자로부터 받은 사용자 정보를 처리하는 클래스.네이버가 반환하는 사용자 정보(JSON 데이터)를 파싱하고 이를 이용해 필요한 정보를 뽑아낼 수 있다.

코드 하나씩 뜯어보기

private final Map<String, Object> attribute;
  • 네이버에서 제공한 사용자 정보를 저장하는 필드
  • 네이버 API의 응답에서 response 키 안의 데이터를 추출해 저장한다.
public NaverResponse(Map<String, Object> attribute) {
    this.attribute = (Map<String, Object>) attribute.get("response");
}
  • 네이버 API는 사용자 정보를 JSON 형태로 반환한다.
  • 이 중에서 response 키에 포함된 데이터만 추출하여 attribute 필드에 저장한다.
{
    "resultcode": "00",
    "message": "success",
    "response": {
        "id": "123123123",
        "email": "user@example.com",
        "name": "밥병천"
    }
}

여기서 response의 내용만 attribute에 저장된다.

{
    "id": "123123123",
    "email": "user@example.com",
    "name": "밥병천"
}

흐름 설명

1. OAuth 2.0 인증 요청

  • 사용자가 로그인 버튼을 누르면 Spring Security가 OAuth 2.0 인증 제공자(구글, 네이버, 카카오 등)로 요청을 보낸다.

2. OAuth2UserService 호출

  • 인증 제공자가 사용자 정보를 반환하면 DefaultOAuth2UserService가 이를 처리하기 위해 호출된다.
  • 커스터마이징된 CustomOAuth2UserService가 이 요청을 가로채서 실행된다.

3. 사용자 정보 로드 및 제공자 구분

  • super.loadUser(userRequest)로 기본 사용자 정보를 가져온다.
  • registrationId로 요청이 구글, 네이버, 카카오 중 어디에서 왔는지 확인한다.

4. 사용자 정보 파싱 및 커스터마이징

  • 제공자에 따라 적절한 클래스(NaverResponse, KakaoResponse 등)로 사용자 정보를 처리한다.

5. 결과 반환

  • 가공된 사용자 정보를 반환하여, 이후 인증 과정에 활용하거나 데이터베이스에 저장한다.

CustomOAuth2UserService 유저 정보 DB 저장하기

@Service
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
    //DefaultOAuth2UserService : security에서 기본적으로 제공하는 OAuth2.0 사용자 정보 처리 클래스.
    //이 클래스를 상속 받아 loadUSer메서드를 오버라이드 하여 사용자 정보를 커스터마이징한다.

    private final UserRepository userRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        					
                            ... 생략
                            
        String username = oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId();
        UserEntity existData = userRepository.findByUsername(username);

        if (existData == null) {
            UserEntity createUser = UserEntity.createUser(username, oAuth2Response.getEmail(), oAuth2Response.getName(), "ROLE_USER");

            userRepository.save(createUser);

            UserDto userDto = new UserDto(username, oAuth2Response.getName(), "ROLE_USER");

            return new CustomOAuth2User(userDto);
        } else {
            existData.updateUser(oAuth2Response.getEmail(), oAuth2Response.getName());

            UserDto userDto = new UserDto(existData.getUsername(),oAuth2Response.getName(), existData.getRole());
            return new CustomOAuth2User(userDto);
        }
    }
}

추가된 코드 하나씩 살펴보기

String username = oAuth2Response.getProvider() + " " + oAuth2Response.getProviderId();
  • 사용자 고유 식별자 생성
    • getProvider와 getProviderId를 결합하여 고유 식별할 수 있는 username을 생성한다.
      Ex. naver 123123123
if (existData == null) {
    UserEntity createUser = UserEntity.createUser(username, oAuth2Response.getEmail(), oAuth2Response.getName(), "ROLE_USER");
    userRepository.save(createUser);
    UserDto userDto = new UserDto(username, oAuth2Response.getName(), "ROLE_USER");
    return new CustomOAuth2User(userDto);
}
  • 기존 사용자가 없을 경우
    • 새로운 사용자 엔티티를 생성
    • 생성한 사용자를 DB에 저장
    • UserDto를 생성하고 이를 기반으로 CustomOAuth2User 객체를 반환
  • 궁금한점. DB에 저장하면 됐지 "UserDto를 생성하고 이를 기반으로 CustomOAuth2User 객체를 반환" 이거 왜 하는거지?
    • CustomOAuth2User반환의 목적 SpringSecurity는 인증 과정에서 사용자 정보를 OAuth2User 객체로 관리한다.
      CustomOAuth2User는 SpringSecurity의 기본 OAuth2USer를 확장하거나 대체한 사용자 정의 클래스이다.
      그래서 이 작업이 필요한 이유!
      > SpringSecurity는 인증이 완료된 후 SecurityContext에 인증 객체를 저장한다.
      이 인증 객체에 사용자 정보를 포함하려면 SpringSecurity가 이해할 수 있는 객체(OAuth2User)를 반환해야 한다.
      그래서 CustomOAuth2User는 이후 인증된 사용자 정보를 SpringSecurity가 쉽게 접근할 수 있도록 제공

그럼 일단 요까지 OAuth2.0 소셜 로그인 구현은 끝..! 이제 JWT로 넘어가자......


OAuth2.0 소셜 로그인 이후 인증 토큰을 발급하는 기능을 구현하자!

일단 기본 흐름

  1. 사용자가 OAuth2.0 소셜 로그인으로 인증
  2. SpringSecurity가 인증 완료 후 CustomSuccessHandler를 호출
  3. CustomSuccessHandler에서 JWT를 생성
  4. 생성된 JWT를 HTTP 응답 헤더에 추가하여 클라이언트에 전달
  5. 클라이언트는 이후 요청에 JWT를 헤더로 포함하여 인증 상태를 유지
@Component
@RequiredArgsConstructor
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    private final JwtUtil jwtUtil;

    @Override
    public void onAuthenticationSuccess (HttpServletRequest request,
                                         HttpServletResponse response,
                                         Authentication authentication) throws IOException, ServletException {
        //OAuth2.0User
        CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal();

        String username = customUserDetails.getUsername();

        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
        GrantedAuthority auth = iterator.next();
        String role = auth.getAuthority();

        String token = jwtUtil.generateToken(username, role);

        response.addHeader("Authorization", "Bearer " + token);
    }
}

CustomSuccessHandler 클래스 코드 하나씩 분석

핵심 역할 : OAuth2.0 인증 후, 사용자 정보를 바탕으로 JWT를 생성하고 응답에 추가

1. 사용자 정보 가져오기

  • 인증 객체에서 사용자 정보를 가져온다
CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal();
  • customUserDetails는 사용자의 주요 정보를 담고 있다.

2. 사용자 이름과 권한 가져오기

  • 사용자의 username과 role을 가져온다
String username = customUserDetails.getUsername();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
GrantedAuthority auth = authorities.iterator().next();
String role = auth.getAuthority();
  • Ex. username = "naver 123123123", role = "ROLE_USER"
  • authentication.getAuthoities()
    • authentication 객체는 인증된 사용자의 인증 정보를 담고 있다.
    • getAuthorities() 메서드는 사용자의 권한 목록을 반환한다.
      Ex. ["ROLE_USER"]
  • authorities.iterator().next()
    • authorities는 Collection 타입이므로 반복자를 사용하여 첫 번째 권한을 가져온다.
      Ex. ROLE_USER
  • auth.getAuthority()
    • auth 객체에서 실제 역할 문자열을 추출한다.
      Ex."ROLE_USER"

3. JWT 생성

  • JWT 유틸리티 클래스를 사용하여 토큰을 생성한다.
String token = jwtUtil.generateToken(username, role);
  • JwtUtil의 generateToken 메서드는 사용자 이름과 권한을 기반으로 JWT를 생성

4. 응답에 JWT 추가

  • 생성된 JWT를 HTTP 응답 헤더에 추가한다.
response.addHeader("Authorization", "Bearer " + token);
  • 클라이언트는 이 헤더를 사용하여 이후 요청에 인증 정보를 포함한다.

5. SecurityConfig 설정

  • SecurityFilterChain에서 성공 핸들러 추가
http.oauth2Login((oauth2) -> oauth2
        .userInfoEndpoint((userInfoEndpointConfig -> userInfoEndpointConfig
                .userService(customOAuth2UserService)))
        .successHandler(customSuccessHandler)); // 이 부분 추가
  • OAuth2 로그인 성공 시 CustomSuccessHandler가 실행된다.
  • 사용자의 인증 정보가 검증된 후 JWT가 생성되고 응답에 추가된다.

마지막!! JwtFilter를 추가해서 클라이언트가 요청에 포함한 JWT 토큰을 확인하ㅗ 인증을 처리하기 위한 SpringSecurity의 커스텀 필터를 구현. 이 필터는 모든 요청에서 JWT의 유효성을 검사하고 인증 정보가 올바른 경우 SpringSecurity 컨텍스트에 인증 정보를 설정

기본 동작 흐름

  1. 클라이언트가 요청을 보낼 때 Authorization 헤더에 JWT를 포함
    • Ex. Authorization: Beaer <JWT_TOKEN>
  2. JwtFilter는 요청이 처리되기 전에 실행되며 JWT를 검증
  3. JWT가 유효하면 사용자 정보를 기반으로 Spring Security 인증 객체를 생성하고, SecurityContext에 저장
  4. 이후 컨트롤러 등에서 인증 정보에 접근 가능
@RequiredArgsConstructor
@Slf4j
public class JwtFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String authorizationHeader = request.getHeader("Authorization");

        //Authorization 헤더 검증
        if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
            log.info("JWT 토큰이 필요 합니다.");
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT 토큰이 필요 합니다.");
        }

        String token = authorizationHeader.substring(7);
        // Secret Key 는 내가 만든게 맞는지 검증 만료 기간 지났는지 검증
        // Key , 만료기간이 지났는지 지나지 않았는지
        if (!jwtUtil.validateToken(token)) {
            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
            response.getWriter().write("{\"error\": \"Unauthorized\"}");
        }

        //토큰에서 username과 role 추출
        String username = jwtUtil.extractUsername(token);
        String role = jwtUtil.extractRoles(token);

        UserDto userDto = new UserDto(username, null, role);

        //UserDetails에 회원 정보 객체 담기
        CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDto);

        //스프링 시큐리티 인증 토큰 생성
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
        // 세션에 사용자 등록
        SecurityContextHolder.getContext().setAuthentication(authToken);

        filterChain.doFilter(request, response);
    }
}

JwtFilter 코드 분석

1. Authorization 헤더 검증
2. JWT 토큰 검증
3. 사용자 정보 추출

String username = jwtUtil.extractUsername(token);
String role = jwtUtil.extractRoles(token);

UserDto userDto = new UserDto(username, null, role);
  • JWT에서 사용자 정보를 추출
    • extractUsername(token) > JWT의 subject 필드를 반환
    • extractRoles(token) > JWT의 role 필드를 반환
  • 추출한 정보를 바탕으로 UserDto 생성

4. 사용자 인증 객체 생성

CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDto);

//스프링 시큐리티 인증 토큰 생성
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
    customOAuth2User,
    null,
    customOAuth2User.getAuthorities()
);
  • 사용자 정보를 CustomOAuth2User 객체로 감싼다.
    • CustomOAuth2User는 SpringSecurity에서 사용자의 권한 정보를 제공
  • UsernamePasswordAuthenticationToken 생성 : SpringSecurity가 사용자의 인증 상태를 ㅘㄴ리할 수 있도록 설정하는 부분. 결과적으로 이 객체는 "이 사용자는 인증되었으며, 다음과 같은 권한이 있다" 라는 정보를 표현
    • CustomOAuth2USer : 인증된 사용자 정보
    • null : 비밀번호(여기서는 필요없다)
    • customOAuth2User.getAuthorities() : 권한 정보

5. Spring Security 컨텍스트에 인증 정보 저장

SecurityContextHolder.getContext().setAuthentication(authToken);
  • Spring Security의 SecurityContextHolder를 사용하여 현재 요청의 인증 정보를 관리한다.
  • SecurityContextHolder는 현재 인증 정보를 저장하고 관리하는 저장소
    > 역할
    1. 현재 요청과 연결된 인증 상태를 유지
    2. 인증된 사용자 정보를 저장
    3. 인증된 사용자 정보는 애플리케이션 어느 곳에서나 가져올 수 있음
  • setAuthentication(authToken)을 호출하며 현재 요청이 인증된 상태로 설정
  • 이후 컨트롤러나 서비스 계층에서 SecurityContextHolder를 통해 인증 정보를 가져올 수 있다.

6. 다음 필터로 요청 전달

7. Security 설정에 JwtFilter 추가

http.addFilterBefore(new JwtFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
  • JwtFilter를 Spring Security 필터 체인에 추가
    • UsernamePasswordAuthenticationFilter 앞에 실행되도록 설정
    • 이는 JWT 인증이 SpringSecurity의 기본 인증 처리 전에 실행됨을 의미

'Back-End > Spring' 카테고리의 다른 글

SpringSecurity Authentication 객체와 Principal 객체  (0) 2025.01.24
SpringSecurity + JWT를 활용한 로그인  (0) 2025.01.23
스프링 트랜잭션의 이해(feat. 전파)  (0) 2025.01.20
Spring Valid  (1) 2024.12.08
Spring Bean 등록  (0) 2024.12.08