전 velog글 [Spring] 9. Spring Security -1 이후의 추가 글.
https://velog.io/@kimybeom/9.-Spring-Security
//Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
OncePerRequestFilter
의 설계 목적OncePerRequestFilter
는 "한 요청당 필터가 한 번만 실행되도록 보장"하는 Spring의 특수 필터이다.
이 클래스를 상속받으면 doFilterInternal
메서드를 오버라이드해야 한다.
doFilter
메서드는 이미 OncePerRequestFilter
내부에서 구현되어 있으며, doFilterInternal
을 호출하는 구조로 설계되었다.
doFilter
vs doFilterInternal
doFilter
Filter
인터페이스의 메서드.OncePerRequestFilter
에서는 final로 선언되어 오버라이드가 불가능.doFilterInternal
OncePerRequestFilter
에서 제공하는 추상 메서드OncePerRequestFilter
가 요청 당 한 번만 실행되도록 관리한다.doFilter
를 사용할 수 없나?OncePerRequestFilter
의 doFilter
메서드는 final로 선언되어 있어 오버라이드가 금지된다.public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) { ... }
doFilterInternal
을 구현하도록 강제하여, 개발자가 실수로 필터가 여러 번 실행되지 않도록 방지한다.@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
String url = request.getRequestURI();
if (url.startsWith("/auth")) {
filterChain.doFilter(request, response);
return;
}
String bearerToken = request.getHeader("Authorization");
if (bearerToken == null || !bearerToken.startsWith("Bearer ")) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");
return;
}
processJwtToken(request, response, filterChain, bearerToken);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("in doFilter");
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String path = httpRequest.getRequestURI();
if (path.startsWith("/auth/") || path.equals("/actuator/health")) {
chain.doFilter(request, response);
return;
}
String bearerJwt = httpRequest.getHeader("Authorization");
if (bearerJwt == null) {
// 토큰이 없는 경우 400을 반환합니다.
httpResponse.sendError(HttpServletResponse.SC_BAD_REQUEST, "JWT 토큰이 필요합니다.");
return;
}
String jwt = jwtUtil.substringToken(bearerJwt);
JwtFilter와 SecurityConfig 작성 후, 추가로 할 일.
AuthUserArgumentResolver
및 관련 설정 제거→ 기존 @Auth
어노테이션을 @AuthenticationPrincipal
로 교체
AuthUserArgumentResolver.java, WebConfig, FilterConfig
를 삭제
→
AuthUserArgumentResolver
의 역할 대체
@Auth
어노테이션을 통해 AuthUser
를 컨트롤러에 주입하는 커스텀 리졸버.@AuthenticationPrincipal
로 대체 가능.public ResponseEntity<?> getProfile(@AuthenticationPrincipal AuthUser authUser) { ... }
WebConfig
의 중복 설정 제거WebMvcConfigurer
를 통해 AuthUserArgumentResolver
를 등록.@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new AuthUserArgumentResolver());
}
FilterConfig
의 중복 필터 등록 문제 해결FilterRegistrationBean
으로 JwtFilter
를 서블릿 필터로 등록.@Bean
public FilterRegistrationBean<JwtFilter> jwtFilter() { ... }
SecurityFilterChain
에서 필터를 관리..addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
FilterConfig
삭제.@Auth를 사용한 곳을 찾아 해당 컨트롤러(@Auth가 있는)의 어노테이션을 @AuthenticationPrincipal로 교체
@Auth AuthUser authUser,
@PathVariable long todoId,
@AuthenticationPrincipal AuthUser authUser,
@PathVariable("todoId") long todoId,
왜 UserDetails를 구현할까?
Authentication
)의 principal로 UserDetails
타입을 기대.@AuthenticationPrincipal
로 주입 받을 때도 UserDetails
타입이면 더 많은 정보(권한 등)를 활용.
Implement methods를 누르면 쫘르륵 나온다.
아니 근데 아래 4줄의 코드는 대체 무엇인가?
→ 이 네 가지 boolean 메서드는 모두 계정의 "상태"를 나타내는 용도
1. isAccountNonExpired()
2. isAccountNonLocked()
3. isCredentialsNonExpired()
4. isEnabled()
JWT 기반 인증에서는 주로 모두 true로 고정해서 사용.
계정 상태 관리가 필요하면, DB 값에 따라 true/false를 반환.