본문 바로가기

프로젝트

[ Spring Security ] Filter 에 @Component 어노테이션을 붙이면, 모든 URL 에 필터 로직이 적용된다.

프로젝트를 하면서 JWT를 통해 사용자 인증, 인가를 구현하게 되었는데

 

/

package ourtine.auth.filter;


import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import ourtine.auth.entity.UserDetailsImpl;
import ourtine.auth.service.UserDetailsServiceImpl;
import ourtine.auth.utils.JwtUtil;

import java.io.IOException;

@Slf4j(topic = "JWT 검증 및 인가")
@Component
public class JwtAuthorizationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    private final UserDetailsServiceImpl userDetailsService;

    public JwtAuthorizationFilter(JwtUtil jwtUtil, UserDetailsServiceImpl userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException {

        log.info("JWT 토큰 추출 및 Authentication 객체 생성 및 반환");

        String url = req.getRequestURI();

        log.info("url = " + url);

        if(StringUtils.hasText(url) && url.startsWith("/api/auth/kakao") || url.startsWith("/api/auth/apple")){
            filterChain.doFilter(req,res);
            return;
        }


        // 쿠키에서 JWT 토큰 빼오기
        String tokenValue = jwtUtil.getTokenFromRequest(req);

        log.info("tokenValue = " + tokenValue);

        // 토큰 존재 시, Authentication 객체 생성하여 인가 완료
        if (StringUtils.hasText(tokenValue)) {
            tokenValue = jwtUtil.substringToken(tokenValue);
            log.info(tokenValue);
            if (!jwtUtil.validateToken(tokenValue)) {
                log.error("Token Error");
                return;
            }
            log.info("Token Validation 완료");
            // Authentication 객체 생성 ( By Username )
            // ( SecurityContextHolder -> SecurityContext -> Authentication 객체 )
            // Authentication 객체 <- UsernamePasswordAuthenticationToken, OAuth2AuthenticationToken
            String username = jwtUtil.getUsername(tokenValue);
            try {
                log.info("username = " + username);
                log.info("setAuthentication");
                setAuthentication(username);
            } catch (Exception e) {
                log.error(e.getMessage());
                return;
            }
        }
        log.info("chain.doFilter() 호출");
        // JWT 토큰이 존재하지 않으면, 필터 통과!
        filterChain.doFilter(req, res);
    }
    // 인증 처리
    public void setAuthentication(String username) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = createAuthentication(username);
        context.setAuthentication(authentication);
        SecurityContextHolder.setContext(context);
    }
    // 인증 객체 생성 ( UserDetails - UsernameAuthentication )
    // ( PrincipalDetails - OAuth2Authentication )
    private Authentication createAuthentication(String username) {
        UserDetailsImpl userDetails = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}

이와 같이 코드를 사용했었다.

 

원래 내가 원하는 것은, 로그인 URL에 들어올때만 이 필터를 거치게 하고 싶었기에

 

Spring Security의 설정 파일인 WebSecurityConfig 파일에 다음과 같이 코드를 작성해놨었다.

 

package ourtine.auth.config.security;


import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import ourtine.auth.entity.UserRoleEnum;
import ourtine.auth.filter.JwtAuthorizationFilter;
import ourtine.auth.service.UserDetailsServiceImpl;
import ourtine.auth.utils.JwtUtil;

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity(debug = true) // Spring Security 지원을 가능하게 함, 디버깅 모드 활성화 : 어떤 필터를 타는지 로그를 찍어줌.
@EnableGlobalMethodSecurity(securedEnabled = true) // @Secured Annotation 활성화.
public class WebSecurityConfig {

    // JWT Provider
    private final JwtUtil jwtUtil;
    private final JwtAuthorizationFilter jwtAuthorizationFilter;
    private final UserDetailsServiceImpl userDetailsService;
    private final AuthenticationConfiguration authenticationConfiguration;


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //  // Spring Filter 등록 ( JwtAuthorizationFilter )
//    @Bean
//    public JwtAuthorizationFilter jwtAuthorizationFilter() {
//        return new JwtAuthorizationFilter(jwtUtil, userDetailsService);
//    }


    // FilterChain을 통해 인증, 인가 관리
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                        .csrf().disable()
                        .cors().disable()
                        .formLogin().disable()
                        .logout().disable()
                        .httpBasic().disable()
                        .rememberMe().disable()
                        .authorizeRequests()
                        .antMatchers("/api/auth/**").permitAll()
                        .antMatchers("/api/user/**").authenticated();
//                        .antMatchers("/api/user/**").hasRole(UserRoleEnum.USER.toString())
//                        .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
//                        .antMatchers("/csrf-token").permitAll()
//                        .antMatchers(HttpMethod.POST, "/authorize", "authorize/refresh", "/users").anonymous()
//                        .antMatchers(HttpMethod.POST, "/oauth/unlink").authenticated()
//                        .antMatchers("/oauth/**").permitAll()
//                        .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

FilterChain을 커스터마이징 해서 @Bean으로 등록하는 메서드를 보면

 

/api/user 하위 URL에 대한 요청에만 인증을 하고,

/api/auth 하위 URL에 대한 요청은 JWTAuthorizationFilter 가 통과되지 않도록 설정한 것을 볼 수 있었다.

 

하지만, JWTAuthorzationFilter 에 @Component로 Bean으로 등록 처리해주니,

URL 패턴을 지정할 수 없고, 모든 URL 패턴에 적용된다는 것을 알게 되었다!

( = @WebFilter 어노테이션을 이용하면, 특정 URL 패턴에 매핑되는 필터를 추가할 수 있다고 한다 )

 

@Component 을 제거하고, 한번 돌려보고 결과를 기록해봐야겠다