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는 요청을 처리하기 위해 여러 필터를 순차적으로 실행

예를 들어:
-
CsrfFilter: CSRF 토큰 검사.
-
UsernamePasswordAuthenticationFilter: 폼 로그인 처리.
-
BasicAuthenticationFilter: HTTP Basic 인증 처리.
-
ExceptionTranslationFilter: 예외 처리. 등등...
1. addFilterBefore란?

순차적으로 실행하는 필터에 먼저 사용할 내가 만든 커스텀를 끼워 넣는 것!
tokenAuthenticationFilter(): 전에 만들 jwt토큰 확인 후 저장하는 커스텀 필터
addFilterBefore의 동작
tokenAuthenticationFilter(): 우리가 만든 커스텀 필터(JWT 토큰을 확인하는 로직).
-
UsernamePasswordAuthenticationFilter.class: Spring Security의 기본 필터 중 하나로, 폼 로그인 요청(예: /login에 POST로 사용자 이름/비밀번호 전송)을 처리.
-
설정 결과:
-
tokenAuthenticationFilter가 UsernamePasswordAuthenticationFilter 전에 실행
-
JWT 인증을 먼저 처리하려면 순서를 이렇게 바꿔야함!
예를 들어, 요청이 오면
-
tokenAuthenticationFilter: 토큰 있는지 또는 유효한지 확인
-
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로 설정했으니까 세션이 없다 그래서 세션 대신 쿠키에 저장하도록 커스터마이징
그래서 쿠키 사용 시


- 구글로 로그인" 누르면 saveAuthorizationRequest가 쿠키에 상태 저장
- 구글에서 돌아오면 loadAuthorizationRequest로 쿠키에서 상태 읽음.
Q) 그럼 세션저장소를 완전히 못쓰는 건가?
A) NO !
코드에서 HttpSession을 직접 사용하여 값을 저장하는 것은 가능하지만, Spring Security가 관리하는 인증 관련 세션을 사용하지 않음.
(번외 - 세션 정책 옵션)

[Spring Security] 스프링시큐리티 설정값들의 역할과 설정방법(2)
스프링시큐리티의 여러가지 설정값들의 역할과 설정방법을 상세히 알아봅니다. Spring Security 커스텀 필터를 이용한 인증 구현 - 스프링시큐리티 설정(2) 본 포스팅은 스프링시큐리티의 전반적인
kimchanjung.github.io
'프로젝트' 카테고리의 다른 글
블로그 만들기 - 배포 중 build 실패 대처 (0) | 2025.04.08 |
---|---|
블로그 만들기 - OAuth2 handler가 동작하지 않는 에러 (0) | 2025.04.03 |
블로그 만들기 -Oauth2구글에 요청이 자동으로 간다고? (0) | 2025.03.31 |
블로그 만들기 - cookie와 util 클래스를 static으로? (0) | 2025.03.30 |
블로그 만들기 - 환경변수 dotenv으로 관리 (0) | 2025.03.30 |