[Spring] 9. Spring Security -2

Kim yoon beom·2025년 5월 14일
0

전 velog글 [Spring] 9. Spring Security -1 이후의 추가 글.
https://velog.io/@kimybeom/9.-Spring-Security

  • 의존성 추가(build.gradle)
//Spring Security
    implementation 'org.springframework.boot:spring-boot-starter-security'
    testImplementation 'org.springframework.security:spring-security-test'


▶️1. OncePerRequestFilter의 설계 목적

  • OncePerRequestFilter"한 요청당 필터가 한 번만 실행되도록 보장"하는 Spring의 특수 필터이다.

  • 이 클래스를 상속받으면 doFilterInternal 메서드를 오버라이드해야 한다.

  • doFilter 메서드는 이미 OncePerRequestFilter 내부에서 구현되어 있으며, doFilterInternal을 호출하는 구조로 설계되었다.


▶️2. doFilter vs doFilterInternal

  • doFilter
    • 기본 Filter 인터페이스의 메서드.
    • 모든 서블릿 필터에서 구현해야 하는 메서드.
    • OncePerRequestFilter에서는 final로 선언되어 오버라이드가 불가능.
  • doFilterInternal
    • OncePerRequestFilter에서 제공하는 추상 메서드
    • 실제 필터 로직을 이 메서드에 구현한다.
    • OncePerRequestFilter가 요청 당 한 번만 실행되도록 관리한다.

▶️3. 왜 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 작성 후, 추가로 할 일.

  1. AuthUserArgumentResolver 및 관련 설정 제거
  2. 컨트롤러들 수정하기

→ 기존 @Auth 어노테이션을 @AuthenticationPrincipal로 교체

AuthUserArgumentResolver.java, WebConfig, FilterConfig를 삭제

  1. AuthUserArgumentResolver의 역할 대체

    • 기존@Auth 어노테이션을 통해 AuthUser를 컨트롤러에 주입하는 커스텀 리졸버.
    • 변경 후: Spring Security의 @AuthenticationPrincipal로 대체 가능.
    public ResponseEntity<?> getProfile(@AuthenticationPrincipal AuthUser authUser) { ... }
  1. WebConfig의 중복 설정 제거
    • 기존WebMvcConfigurer를 통해 AuthUserArgumentResolver를 등록.
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new AuthUserArgumentResolver());
}
  • 변경 후: Spring Security가 인증 정보를 자동으로 주입하므로 설정 불필요 → 관련 코드 삭제.
  1. FilterConfig의 중복 필터 등록 문제 해결
  • 기존FilterRegistrationBean으로 JwtFilter를 서블릿 필터로 등록.
    @Bean
    public FilterRegistrationBean<JwtFilter> jwtFilter() { ... }
  • 변경 후: Spring Security의 SecurityFilterChain에서 필터를 관리.
    .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
  • 문제: 두 곳에서 필터를 등록하면 중복 실행 발생 → FilterConfig 삭제.

  • (FilterConfig 코드) 해당 코드 삭제.

@Auth를 사용한 곳을 찾아 해당 컨트롤러(@Auth가 있는)의 어노테이션을 @AuthenticationPrincipal로 교체

▶️@AuthenticationPrincipal을 사용하면서 컨트롤러의 수정.

  • 기존 방식 (@Auth + ArgumentResolver)
@Auth AuthUser authUser,
@PathVariable long todoId,
  • Spring Security 방식
@AuthenticationPrincipal AuthUser authUser,
@PathVariable("todoId") long todoId,
  • Spring Security와 완벽하게 호환 → 추후 인증 방식 변경(예: OAuth2, SSO 등)에도 그대로 사용 가능 → 유지보수와 확장성이 훨씬 좋아진다.

▶️P.S AuthUser.java(dto)를 UserDetails 인터페이스를 구현 형태로 변경

왜 UserDetails를 구현할까?

  • Spring Security는 인증 객체(Authentication)의 principal로 UserDetails 타입을 기대.
  • @AuthenticationPrincipal로 주입 받을 때도 UserDetails 타입이면 더 많은 정보(권한 등)를 활용.
  • 권한 체크, 인증 정보 활용이 더 표준적이고 유연해진다.


Implement methods를 누르면 쫘르륵 나온다.

  • 정리 후

아니 근데 아래 4줄의 코드는 대체 무엇인가?

→ 이 네 가지 boolean 메서드는 모두 계정의 "상태"를 나타내는 용도

1. isAccountNonExpired()

  • 설명: 계정이 "만료되지 않았는지"를 반환.

2. isAccountNonLocked()

  • 설명: 계정이 "잠겨 있지 않은지"를 반환.

3. isCredentialsNonExpired()

  • 설명: "비밀번호(자격 증명)가 만료되지 않았는지"를 반환.

4. isEnabled()

  • 설명: 계정이 "활성화되어 있는지"를 반환.

JWT 기반 인증에서는 주로 모두 true로 고정해서 사용.
계정 상태 관리가 필요하면, DB 값에 따라 true/false를 반환.

profile
나는.원한다.개발자

0개의 댓글