JWT에서 Filter는 인증 및 인가를 처리하는 역할을 합니다.
일반적으로 Spring Security와 연동되어 사용되며, 인증 및 인가를 처리하기 전에 Filter가 요청을 가로채고 필요한 처리를 수행한 후 다음 단계로 요청을 전달합니다.
JWT에서 Filter는 JWT 토큰을 검증하고, 만료 기간을 확인하며, 유저 정보를 검증하고, 권한을 확인하는 등의 작업을 수행합니다. 이를 통해 서버는 보안성을 높이면서도 유연한 인증 및 인가 처리가 가능합니다.
인증(Authentication)은 사용자가 누구인지 확인하는 과정이며, 인가(Authorization)는 해당 사용자가 어떤 자원에 접근할 수 있는 권한이 있는지 확인하는 과정입니다.
인증은 대표적으로 사용자의 아이디와 패스워드를 입력하여 로그인하는 과정이 있습니다.
이를 통해 사용자가 누구인지 확인하고, 로그인한 사용자에게는 인증토큰(Authentication Token)이 발급됩니다.
인가는 인증된 사용자가 어떤 자원(Resource)에 접근할 수 있는지 여부를 확인하는 과정입니다. 예를 들어, 관리자 권한을 가진 사용자는 관리자 페이지에만 접근할 수 있도록 설정할 수 있습니다. 이를 위해 사용자의 권한 정보가 필요하며, 대표적으로 권한 부여(Authorization)를 통해 권한을 관리합니다.
package com.choigoyo.filter;
import javax.servlet.*;
import java.io.IOException;
public class Filter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("필터1");
chain.doFilter(request,response);
}
}
Filter를 관리할 패키지를 생성하고 테스트 할 Filter파일을 작성하여 테스트해보겠습니다.
httpSecurity.addFilter(new Filter1());
위 코드를 SecurityConfig 파일에 추가하고 실행을 누르면 아래와 같은 오류가 발생합니다.
Caused by: org.springframework.beans.BeanInstantiationException:
Failed to instantiate [javax.servlet.Filter]:
Factory method 'springSecurityFilterChain' threw exception;
nested exception is java.lang.IllegalArgumentException:
The Filter class com.choigoyo.filter.Filter1 does not have a registered order and cannot be added without a specified order.
Consider using addFilterBefore or addFilterAfter instead.
오류 메시지를 확인해보면 filter.Filter1 클래스가 등록된 순서가 없어서 springSecurityFilterChain 에 추가할 수 없다고 나와 있습니다.
이를 해결하는 방법으로는 Filter1 클래스에 @Order 어노테이션을 사용하여 등록 순서를 명시하거나,
SecurityConfig.configure 메서드에 addFilterBefore or addFilterAfter 를 사용하여 순서를 필터 순서를 지정해줄 수도 있습니다.
@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// Filter의 순서를 지정해 줌
httpSecurity.addFilterBefore(new Filter1(),BasicAuthenticationFilter.class);
httpSecurity.csrf().disable();
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // session을 사용하지 않는다
.and()
.addFilter(corsFilter) // @CrossOrigin(인증 없을 때) , 시큐리티 필터에 등록(인증 있을 때)
.formLogin().disable() // id pw 로그인을 form 로그인을 하지 X
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/api/v1/user/**")
.access("hasRole('user') or hasRole('manager') or hasRole('admin')")
.antMatchers("/api/v1/manager/**")
.access("hasRole('manager') or hasRole('admin')")
.antMatchers("/api/v1/admin/**")
.access("hasRole('admin')")
.anyRequest().permitAll(); // 설정 외 모든 경로는 인증없이 접근가능
}
}
httpSecurity.addFilterBefore(new Filter1(),BasicAuthenticationFilter.class);
이전 코드와 다르게 Filter1이 BasicAuthenticationFilter 이전에 동작하도록 수정했습니다.
또 다른 방법으로는 config 패키지에 FilterConfig 클래스를 작성하여 설정해주는 방법도 있습니다.
package com.choigoyo.config;
import com.choigoyo.filter.Filter1;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // IoC (Inversion of Control) 제어의 역전을 의미
public class FilterConfig {
@Bean
public FilterRegistrationBean<Filter1> filter1(){
FilterRegistrationBean<Filter1> bean = new FilterRegistrationBean<>(new Filter1());
bean.addUrlPatterns("/*"); // 모든 요청에서 필터를 적용
bean.setOrder(0); // 필터의 순서 지정 / 우선순위 가장 높음
return bean;
}
}
FilterConfig 파일에 필터에 대한 내용을 설정해주고
웹브라우저에 요청을 해서 오류 없이 잘 동작하는 것을 확인했습니다.
같은 방식으로 필요한 필터를 만들어 filterConfig에서 설정하여 실행하면 됩니다.
이제 필터의 우선순위를 테스트해 보겠습니다. 먼저 우선순위 체크를 위해 필터1, 필터2, 필터3을 준비하고, 2개의 필터는 FilterConfig에 우선순위를 정한 뒤, 나머지 1개의 필터는 SecurityConfig에 넣어 실행해 보도록 하겠습니다.
package com.choigoyo.config;
import com.choigoyo.filter.Filter1;
import com.choigoyo.filter.Filter2;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration // IoC (Inversion of Control) 제어의 역전을 의미
public class FilterConfig {
@Bean
public FilterRegistrationBean<Filter1> filter1(){
FilterRegistrationBean<Filter1> bean = new FilterRegistrationBean<>(new Filter1());
bean.addUrlPatterns("/*"); // 모든 요청에서 필터를 적용
bean.setOrder(0); // 필터의 순서 지정 / 우선순위 가장 높음
return bean;
}
@Bean
public FilterRegistrationBean<Filter2> filter2(){
FilterRegistrationBean<Filter2> bean = new FilterRegistrationBean<>(new Filter2());
bean.addUrlPatterns("/*"); // 모든 요청에서 필터를 적용
bean.setOrder(1); // 필터의 순서 지정
return bean;
}
}
SecurityConfig에 등록한 Filter3이 가장먼저 실행되고
이후 나머지 Filter1,Filter2 는 우선순위 지정에 따라 실행되었습니다.
SecurityConfig에 등록한 Filter3은 addFilterBefore 이나 addFilterAfter 모두 같은 결과가 나옵니다.
즉, Security 필터체인이 만들어낸 필터체인보다 먼저 실행이 된다는 것 입니다.
필터가 적용되는 시점을 Spring 필터체인 순서를 참고하여 SecurityConfig에 등록하여 사용할 수 있습니다.