cofig 설정들
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();
}
}
package me.hanjun.config;
import jakarta.servlet.FilterChain;
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.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
@RequiredArgsConstructor
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
private final static String HEADER_AUTHORIZATION = "Authorization";
private final static String TOKEN_PREFIX = "Bearer ";
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authorizationHeader = request.getHeader(HEADER_AUTHORIZATION);
String token = getAccessToken(authorizationHeader);
if (tokenProvider.validToken(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getAccessToken(String authorizationHeader) {
if (authorizationHeader != null && authorizationHeader.startsWith(TOKEN_PREFIX)) {
return authorizationHeader.substring(TOKEN_PREFIX.length());
}
return null;
}
}
usernamepassowordauthenticationfilter 전에 작동할 만든 tokenAuthenticationFilter
Spring Security 필터 체인 구성
Spring Security는 필터 체인(SecurityFilterChain) 을 통해 보안 로직을 실행함.

-
CsrfFilter: CSRF 토큰 검사.
-
UsernamePasswordAuthenticationFilter: 폼 로그인 처리.핵심이다! 이 전에 로그인이 되어야 한다.
-
BasicAuthenticationFilter: HTTP Basic 인증 처리.
-
ExceptionTranslationFilter: 예외 처리. 등등...
1. addFilterBefore란?

순차적으로 실행하는 필터에 먼저 사용할 내가 만든 커스텀를 끼워 넣기 가능!
tokenAuthenticationFilter(): 전에 만들 jwt토큰 확인 후 저장하는 커스텀 필터
(tokenAuthenticationFilter의 핵심 로직)

addFilterBefore의 동작
-
UsernamePasswordAuthenticationFilter.class: Spring Security의 기본 필터 중 하나로, 폼 로그인 요청(예: /login에 POST로 사용자 이름/비밀번호 전송)을 처리.
-
설정 결과:
-
tokenAuthenticationFilter가 UsernamePasswordAuthenticationFilter 전에 실행
-
JWT 인증을 먼저 처리하려면 순서를 이렇게 바꿔야함!
예를 들어, 요청이 오면
-
tokenAuthenticationFilter: 토큰 있는지 또는 유효한지 확인
-
UsernamePasswordAuthenticationFilter: 폼 로그인 체크 (여기선 비활성화됐지만)
이 순서가 뒤바뀌면 기본 로그인부터 시도하고 인증하고 넘겨버릴 수도 있음!
** 번외 **
사실 UsernamePasswordAuthenticationFilter는 form.login.disable(). 비활성화 시
UsernamePasswordAuthenticationFilter가 FilterChain에 등록되지 않는다!!!
근데 이럴 경우에도
http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); 쓰면
tokenAuthenticationFilter는 UsernamePasswordAuthenticationFilter 앞에
잘 등록된다.
그 이유가 뭘까?

간단히 말하자면, 필터의 존재에 고유한 각 순서 값을 가진다.!
(FilterOrderRegistration에서 관리함)
실제로 필터에 등록되지 않았더라도 그 순서 값을 여전히 참조 값으로 잡을 수 있음!!

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

jwt를 활용하기 위해서 security의 session 정책을 아예 사용하지 않는다는 뜻!
2-1. 내가 로그인을 시작한 위치는 어떻게 저장하지? STATELESS인데?

-
근데 STATELESS로 설정했으니까 세션이 없다 그래서 세션 대신 쿠키에 저장하도록 커스터마이징
그래서 쿠키 사용 시


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

--번외--
tokenAuthenticationFilter에서 doFilter 호출 시 메서드 파라미터는 2개지만,
UsernamePasswordAuthenticationFilter의 실제 구현된 doFilter는 3개로 받는다?

그중에 내가 넘긴 jwt에서는 dofilter의 메서드 파라미터 불일치가 있었는데 궁금했다.




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