조립하기
https://dev-dx2d2y-log.tistory.com/219
[골든리포트!] 7) JWT토큰 인증과정을 Spring Security 필터체인에 태워보기
저번에 인증과정에서 SecurityFilterChain을 어느정도 알아보았다. 이번에는 저번에 만들어본 JWTAuthorizationFilter를 수정해보도록한다. https://dev-dx2d2y-log.tistory.com/216 [골든리포트!] 6) 필터체인에서 JWT필
dev-dx2d2y-log.tistory.com
이전에 JwtAuthorizationFilter까지 만들었고, SecurityConfig에 연결해주어야했는데, 오늘은 그걸 해보려한다.
필터체인 내부에서 생성해야하는 Authentication 객체와 같은 특별한 경우가 아니라면 new 생성자는 가급적 사용하지 말고, 되도록이면 스프링의 빈을 사용해야한다고한다.
public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final JWTProvider jwtProvider;
private final AuthenticationFailureHandler failureHandler;
private final AuthenticationManager authenticationManager;
public JwtAuthorizationFilter(AuthenticationFailureHandler failureHandler,
JWTProvider jwtProvider,
AuthenticationManager authenticationManager) {
this.failureHandler = failureHandler;
this.jwtProvider = jwtProvider;
this.authenticationManager = authenticationManager;
}
}
public class SecurityConfig{
private final OAuth2SuccessHandler oauth2SuccessHandler;
private final JWTProvider jwtProvider;
private final MemberRepository memberRepository;
public SecurityConfig(OAuth2SuccessHandler oauth2SuccessHandler,
JWTProvider jwtProvider,
MemberRepository memberRepository) {
this.oauth2SuccessHandler = oauth2SuccessHandler;
this.jwtProvider = jwtProvider;
this.memberRepository = memberRepository;
}
}
클래스가 스프링 빈에 속한 다른 클래스에 의존 중이라면, 이렇게 생성자를 통해 주입받는 것이 가장 좋다.

이런 방식의 장점은 생성자를 통해 객체를 생성할 때 생성자 매개변수를 굳이 신경쓰지 않아도 스프링이 빈에 등록된 객체를 보고 자동으로 의존성을 주입시켜준다는 것이다. 위의 예시는 내가 이전에 순수JDK로 백엔드서버를 만들 때 main에서 DB 커넥션을 만들고 생성자를 타고타고 MemberRepository로 DB 커넥션을 넘겨준 것.
그런데 우선 JwtAuthorizationFilter는 Component 어노테이션을 사용하는 클래스도 아니기 때문에, SpringSecurity에서 위와 같은 방식으로는 불러올 수 없다.
public JwtAuthorizationFilter(AuthenticationFailureHandler failureHandler,
JWTProvider jwtProvider,
AuthenticationManager authenticationManager) {
this.failureHandler = failureHandler;
this.jwtProvider = jwtProvider;
this.authenticationManager = authenticationManager;
}
JwtAuthorizationFilter는 AuthenticationFliter, JwtProvider, AuthenticationManager 클래스를 생성자 매개변수로 받는데, JWTProvider를 제외하고는 스프링 빈에 등록되어있지 않아서 스프링이 자동으로 생성자에 객체를 넣어줄 수도 없다.
따라서 빈이 아닌 외부 클래스나 라이브러리를 사용하려면 @Bean 어노테이션을 사용해야한다.
public class SecurityConfig{
private final OAuth2SuccessHandler oauth2SuccessHandler;
private final JWTProvider jwtProvider;
public SecurityConfig(OAuth2SuccessHandler oauth2SuccessHandler,
JWTProvider jwtProvider) {
this.oauth2SuccessHandler = oauth2SuccessHandler;
this.jwtProvider = jwtProvider;
}
@Bean
public JwtAuthenticationProvider JwtAuthenticationProvider() {
return new JwtAuthenticationProvider(jwtProvider);
}
@Bean
public AuthenticationManager authenticationManagerBean(
JwtAuthenticationProvider authenticationProvider
) throws Exception {
return new ProviderManager(Arrays.asList(authenticationProvider));
}
@Bean
public AuthenticationProvider authenticationProviderBean() throws Exception {
return new JwtAuthenticationProvider(jwtProvider);
}
@Bean
public AuthenticationFailureHandler failureHandler(){
return new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
};
}
@Bean
public JwtAuthorizationFilter jwtAuthorizationFilter(
JWTProvider jwtProvider,
AuthenticationFailureHandler failureHandler,
AuthenticationManager authenticationManager
){
return new JwtAuthorizationFilter(failureHandler, jwtProvider, authenticationManager);
}
}
이렇게..
빈으로 등록된 OAuth2SuccessHandler와 JWTProvider는 생성자를 통해 주입받고,
빈이 아닌 JwtAuthorizationFilter는 @Bean 어노테이션을 등록해서 해당 메서드가 반환하는 객체를 스프링 빈으로 등록한다. 나중에 객체가 필요하면 빈에서 들고오던가하는 식으로 동작한다.
@Bean
public SecurityFilterChain filterChain (HttpSecurity http,
JwtAuthorizationFilter jwtAuthorizationFilter) throws Exception {
http
.addFilterBefore(jwtAuthorizationFilter,
UsernamePasswordAuthenticationFilter.class)
}
이렇게 나중에 filterChain메서드에서 JwtAuthorizationFilter를 생성자로 받으면, 스프링에서 빈에 있던 JwtAuthorizationFilter 객체를 찾아 생성자로 넣어준다.
그리고 @Bean 어노테이션의 메서드에 들어가는 객체들인 AuthenticationHandler와 AuthenticationManager, 그리고 AuthenticationManager를 생성하기 위한 JwtAuthenticationProvider도 빈으로 등록한다.
클래스가 스프링 빈으로 등록된 클래스에 의존하는 경우에는 생성자를 통해 의존하는 객체를 자동으로 주입받는 방식을 사용할 수 있다.
하지만 의존하는 객체가 스프링 빈으로 등록되지 않은 클래스의 경우에는, 직접 해당 객체를 반환하는 메서드를 만들고, @Bean 어노테이션을 사용해 이를 빈으로 등록해야한다. @Bean 어노테이션은 메서드가 반환하는 객체를 빈으로 등록한다. 따라서 이 메서드가 실행되면 반환하는 객체가 빈으로 등록되어 마치 스프링 빈으로 등록된 클래스인 것처럼 객체를 사용할 수 있다. 생성자를 통해 자동으로 객체를 주입받는 등...

암튼 그래서 잘 동작한다.
이 방식은 Spring SecurityFilterChain이 어떻게 구성되어있는지 알아보기 위해서 한 것이고.. 실제로는 이렇게 AuthenticationProvider까지 들어갈 필요없이 한 개의 필터에서 JWT토큰을 처리한다. JWT토큰 방식은 그냥 토큰확인만하면 되니까
나중에 폼로그인 / 세션인증 방식으로 확장시킨다면 필요할 것이다..
https://github.com/kimdoes/GoldenReports/commits/main/src/main
GitHub - kimdoes/GoldenReports
Contribute to kimdoes/GoldenReports development by creating an account on GitHub.
github.com
여기에 등록해놨으니 필요할 때마다 보면 된다. 커밋이름은 85f613c
현실적인 JwtAuthorizationFilter 적용하기
package com.example.GoldenReport.filters;
import com.example.GoldenReport.Service.JWT.JWTProvider;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.coyote.BadRequestException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.*;
public class JWTAuthorizationFilter extends OncePerRequestFilter {
private final JWTProvider jwtProvider;
public JWTAuthorizationFilter(JWTProvider JWTprovider) {
this.jwtProvider = JWTprovider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String path = request.getRequestURI();
System.out.println(path);
Cookie[] cookies = request.getCookies();
if (cookies == null) {
filterChain.doFilter(request, response);
return;
}
String accessToken = Arrays.stream(cookies)
.filter(cookie -> "Authentication".equals(cookie.getName()))
.map(Cookie::getValue)
.findFirst()
.orElse(null);
if (accessToken == null) {
filterChain.doFilter(request, response);
return;
}
try {
Optional<Map<String, Object>> optionalUser = jwtProvider.decodeJWT(accessToken, List.of("position"));
if (optionalUser.isEmpty()) {
filterChain.doFilter(request, response);
return;
}
Map<String, Object> user = optionalUser.get();
Authentication authentication = new UsernamePasswordAuthenticationToken(
user.get("subject"),
null,
AuthorityUtils.createAuthorityList(user.get("position").toString()));
SecurityContextHolder
.getContext()
.setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JwtException e) {
e.printStackTrace();
throw new BadRequestException("Invalid JWT Token", e);
}
}
}
그래서 실제로는 원래의 필터를 가져와서 넣었다.
쿠키에 있는 JWT토큰을 찾아서, 이를 해석하고, 중간에 오류가 생기거나 값에 이상이 있으면 다음필터로 인증을 넘기고 (직접 예외처리한 JWT토큰 오류를 제외하면 다음필터에서 인증오류를 잡아낼 수 있다) 아니면 Authentication 객체를 만들어 SecurityContextHolder에 등록한다.
지금까지 다뤘던 방식은 세션인증과 폼로그인 방식에서의 정석이긴하지만, 폼로그인이 없고 JWT토큰을 사용하는 프로그램에서는 좀 과한 추상화라고.
package com.example.GoldenReport.Config;
import com.example.GoldenReport.Service.JWT.JWTProvider;
import com.example.GoldenReport.Service.LogInAndSignUp.OAuth2SuccessHandler;
import com.example.GoldenReport.filters.JWTAuthorizationFilter;
import com.example.GoldenReport.filters.JwtAuthorizationEntryPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig{
private final OAuth2SuccessHandler oauth2SuccessHandler;
private final JWTProvider jwtProvider;
public SecurityConfig(OAuth2SuccessHandler oauth2SuccessHandler,
JWTProvider jwtProvider) {
this.oauth2SuccessHandler = oauth2SuccessHandler;
this.jwtProvider = jwtProvider;
}
@Bean
public JWTAuthorizationFilter jwtAuthorizationFilter(
JWTProvider jwtProvider
){
return new JWTAuthorizationFilter(jwtProvider);
}
@Bean
public SecurityFilterChain filterChain (HttpSecurity http,
JWTAuthorizationFilter jwtAuthorizationFilter) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/",
"/css/**",
"/js/**",
"/images/**",
"/favicon.ico/**",
"/h2-console/**",
"/signup/**",
"/oauth2/**").permitAll()
.requestMatchers(HttpMethod.GET, "/review/**").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.defaultSuccessUrl("/")
.successHandler(oauth2SuccessHandler)
.failureUrl("/login?error=true")
)
.addFilterBefore(jwtAuthorizationFilter,
UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(ex -> ex
.authenticationEntryPoint(new JwtAuthorizationEntryPoint()))
.sessionManagement((session)-> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.csrf(AbstractHttpConfigurer::disable)
.headers(headers -> headers
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
)
.logout(logout -> logout
.logoutSuccessUrl("/")
);
return http.build();
}
}
SecurityConfig도 다음과 같이 수정
JwtAuthorizationFilter 역시 @Bean 어노테이션으로 스프링 빈에 등록해서 사용했다. 이전에는 SecurityConfig에 new 연산자를 사용해서 JwtAuthorizationFilter로 등록했지만 이제는 @Bean 어노테이션을 사용한다.
'개인 프로젝트 > [2026] 골든리포트!' 카테고리의 다른 글
| [골든리포트!] 7) JWT토큰 인증과정을 Spring Security 필터체인에 태워보기 (0) | 2026.02.18 |
|---|---|
| [골든리포트!] 6) 필터체인에서 JWT필터 인증 처리하기 (0) | 2026.02.13 |
| [골든리포트!] 5) JWT 토큰 후속기능 처리하기 (0) | 2026.02.12 |
| [골든리포트!] 4) 스프링 세션 설정 및 JWT토큰 발급 (0) | 2026.02.10 |
| [골든리포트!] 3) Spring Security Config로 스프링 OAuth 다루기 (0) | 2026.02.10 |