프로젝트

블로그 만들기 - Security config설정들

ernest45 2025. 4. 1. 01:19

 

package me.hanjun.config;

import lombok.RequiredArgsConstructor;
import me.hanjun.config.oauth.Oauth2UserCustomService;
import me.hanjun.repository.RefreshTokenRepository;
import me.hanjun.service.UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import static org.springframework.boot.autoconfigure.security.servlet.PathRequest.toH2Console;

@Configuration
@RequiredArgsConstructor
public class WebOauthSecurityConfig {

    private final Oauth2UserCustomService oauth2UserCustomService;
    private final TokenProvider tokenProvider;
    private final RefreshTokenRepository refreshTokenRepository;
    private final UserService userService;


    @Bean
    public WebSecurityCustomizer configure() {
        //스프링 시큐리티 기능 비활성화
        return (web -> web.ignoring()
                .requestMatchers(toH2Console())
                .requestMatchers("/img**", "/css/**", "js/**"));
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {


        //토큰 방식으로 인증을 하기 때문에 기존에 사용하던 폼로그인, 세션 비활성화
        http.csrf().disable()
                //CSRF(사이트 간 요청 위조) 보호 비활성화. 토큰 기반 인증(JWT)에서는 세션이 없으므로 CSRF 공격 위험이 낮아서 꺼둠.
                .httpBasic().disable() //HTTP Basic 인증(브라우저 팝업 로그인) 비활성화
                .formLogin().disable() //폼로그인 비활성화
                .logout().disable(); // 아래에서 커스터마이징.


        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        //SessionCreationPolicy.STATELESS: 세션을 사용하지 않음.
        //왜?: JWT 토큰으로 인증하니까 서버가 상태(세션)를 유지할 필요 없음. 클라이언트가 토큰을 헤더에 실어 보냄.

        //헤더를 확인할 커스텀 필드 추가
        http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //addFilterBefore(...UsernamePasswordAuthenticationFilter.class):
        // Spring Security의 기본 인증 필터(UsernamePasswordAuthenticationFilter) 전에 실행.

        // 토큰 재발급 URL는 인증 없이도 접근 가능하도록 설정, 나머지 API URL은 인증 필요
        http.authorizeHttpRequests()
                .requestMatchers("/api/token").permitAll()
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll();

        http.oauth2Login()
                .loginPage("/login")
                .authorizationEndpoint()
                // Authorization 요청과 관련된 상태 저장
                .authorizationRequestRepository(OAuth2AuthorizationREquestBasedOnCookieRepository())
                .and()
                .successHandler(oAuth2SuccessHander()) // 인증 성공 시 실행할 핸들러
                .userInfoEndpoint()
                .userService(oauth2UserCustomService);

        http.logout()
                .logoutSuccessUrl("/login");

        // api로 시작하는 uri인 경우 401 상태 코드를 반환하도록 예외처리
        http.exceptionHandling()
                .defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)
                        , new AntPathRequestMatcher("/api/**"));

        return http.build();
    }

    @Bean
    public OAuth2SuccessHandler oAuth2SuccessHandler() {
        return new OAuth2SuccessHandler(tokenProvider,
                refreshTokenRepository, oAuth2AuthorizationREquestBasedOnCookieRepository(),
                userService);
    }

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() {
        return new TokenAuthenticationFilter(tokenProvider);
    }

    @Bean
    public OAuth2AuthorizationREquestBasedOnCookieRepository
    oAuth2AuthorizationREquestBasedOnCookieRepository() {

        return new OAuth2AuthorizationREquestBasedOnCookieRepository();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

Spring Security는  필터 체인(SecurityFilterChain) 을 통해 보안 로직을 실행함.
 
 
 

Spring Security 필터 체인 구성

 
말했듯이 Spring Security는 요청을 처리하기 위해 여러 필터를 순차적으로 실행

 

 
   
 

 

     예를 들어:
  1. CsrfFilter: CSRF 토큰 검사.
  2. UsernamePasswordAuthenticationFilter: 폼 로그인 처리.
  3. BasicAuthenticationFilter: HTTP Basic 인증 처리.
  4. ExceptionTranslationFilter: 예외 처리. 등등...

 

 

 

 

1. addFilterBefore란?

 

 

 

순차적으로 실행하는 필터먼저 사용할 내가 만든 커스텀를 끼워 넣는 것!

 

tokenAuthenticationFilter(): 전에 만들 jwt토큰 확인 후 저장하는 커스텀 필터

 

 

addFilterBefore의 동작

tokenAuthenticationFilter(): 우리가 만든 커스텀 필터(JWT 토큰을 확인하는 로직).
 
  • UsernamePasswordAuthenticationFilter.class: Spring Security의 기본 필터 중 하나로, 폼 로그인 요청(예: /login에 POST로 사용자 이름/비밀번호 전송)을 처리.
  • 설정 결과:
    • tokenAuthenticationFilterUsernamePasswordAuthenticationFilter 전에 실행

 

 

 

 

JWT 인증을 먼저 처리하려면 순서를 이렇게 바꿔야함!

 

예를 들어, 요청이 오면

  1.  tokenAuthenticationFilter: 토큰 있는지 또는 유효한지 확인
  2.  UsernamePasswordAuthenticationFilter:  폼 로그인 체크 (여기선 비활성화됐지만)
근데 왜 앞에 넣냐? JWT로 인증하면 세션이나 폼 로그인이 필요 없으니까, 커스텀한 필터로
제일 먼저 토큰을 보고 인증 확인 하려고!
 
 
 
 

이 순서가 뒤바뀌면 기본 로그인부터 시도하고 인증하고 넘겨버릴 수도 있음!

 
 
 
 
 
 

2. OAuth2AuthorizationRequestBasedOnCookieRepository?

 

 

Spring Security는 세션을 기반으로 인증을 관리함! but

 

 

 

  • STATELESS로 설정하면 Spring Security가 HTTP 요청 간의 세션을 생성하거나 유지x
  • 즉, 서버가 클라이언트의 세션을 저장하지 않으므로 세션 기반 인증(로그인)을 사용불가
  • 보통 JWT(JSON Web Token) 인증 방식에서 사용됨.
 
 
 
 

 

 

jwt를 활용하기 위해서 security의 session 정책을 아예 사용하지 않는다는 뜻!

 

 

2-1. 내가 로그인을 시작한 위치는 어떻게 저장하지? STATELESS인데?

 

 

로그인 시작   → 세션에 상태 저장 → 구글로 리다이렉트 → 돌아오면 세션에서 상태 꺼냄

 

 

기존 세션 사용 시 
 
 
*HttpSessionOAuth2AuthorizationRequestRepository를 써서 OAuth2AuthorizationRequest를 세션에 저장*
 
 
 
 
  로그인 시작 → 세션에 상태 저장 → 구글로 리다이렉트 → 돌아오면 세션에서 상태 꺼냄.
 
 
 
  • 근데  STATELESS로 설정했으니까 세션이 없다 그래서 세션 대신 쿠키에 저장하도록 커스터마이징

 

 

 

 

 

그래서 쿠키 사용 시

쿠키 저장소를 따로 만들고
쿠키에 저장하는 메서드!
 
  1. 구글로 로그인" 누르면 saveAuthorizationRequest가 쿠키에 상태 저장
  2. 구글에서 돌아오면 loadAuthorizationRequest로 쿠키에서 상태 읽음.

 

 

 

 

 

Q) 그럼 세션저장소를 완전히 못쓰는 건가?

 

A) NO !

 

코드에서 HttpSession을 직접 사용하여 값을 저장하는 것은 가능하지만, Spring Security가 관리하는 인증 관련 세션을 사용하지 않음.

 

 

 

 
 
 
 
(번외 - 세션 정책 옵션)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[Spring Security] 스프링시큐리티 설정값들의 역할과 설정방법(2)

스프링시큐리티의 여러가지 설정값들의 역할과 설정방법을 상세히 알아봅니다. Spring Security 커스텀 필터를 이용한 인증 구현 - 스프링시큐리티 설정(2) 본 포스팅은 스프링시큐리티의 전반적인

kimchanjung.github.io