#spring-boot #spring-mvc #spring-security
#spring-boot #spring-mvc #spring-безопасность
Вопрос:
Я экспериментирую с Spring security и столкнулся со странным поведением. Моя идея — создать фильтр безопасности, который проверяет подлинность запросов на основе токенов JWT (или JWS):
public class JWTokenFilter extends AbstractAuthenticationProcessingFilter {
public JWTokenFilter(AuthenticationManager authenticationManager) {
super("/**"); //doesn't have any effect, every request still gets considered by this filter
setAuthenticationManager(authenticationManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token)) {
throw new TokenException("Token is empty");
}
var authentication = determineAuthentication(token.replace("Bearer","").trim());
//the AbstractAuthenticationProcessingFilter fills the Security context
return this.getAuthenticationManager().authenticate(authentication);
}
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
System.out.println("Asked for " request.getRequestURI());
return request.getHeader("Authorization") != null;
}
private TokenAuthentication<UserInfo> determineAuthentication(String token) {
var split = token.split("\.");
if (split.length < 2 || split.length > 3) {
throw new TokenException("Token malformed");
}
if (split.length == 2){
return new JWTAuthentication<>(token);
}else {
return new JWSAuthentication<>(token);
}
}
}
У меня есть 3 @RestController
класса, для которых сопоставлены их пути:
@RequestMapping("/admin")
@RequestMapping("/all")
@RequestMapping("/anon")
Наряду с этим у меня есть следующая конфигурация безопасности:
@Configuration
@Order(98)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/all/**","/anon/**")
.and()
.authorizeRequests().antMatchers("/all/**").permitAll()
.and()
.authorizeRequests().antMatchers("/anon/**").anonymous();
}
@Override
public void configure(WebSecurity web) {
web.ignoring().mvcMatchers("/webjars/**", "/css/**");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Configuration
@Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
@Lazy
@Autowired
private JWTokenFilter tokenFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //having /admin/** or /** makes no difference
.anyRequest().authenticated()
.and().addFilterBefore(tokenFilter,ExceptionTranslationFilter.class);//put this filter near the end of the chain
}
@Bean
public JWTokenFilter tokenFilter(JWTokenAuthenticationProvider jwTokenAuthenticationProvider,JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider){
var list = new ArrayList<AuthenticationProvider>();
list.add(jwsTokenAuthenticationProvider);
list.add(jwTokenAuthenticationProvider);
ProviderManager manager = new ProviderManager(list);
return new JWTokenFilter(manager);
}
}
}
Из этой конфигурации здесь мы видим, что существует 2 SecurityFilterChan
s (не считая /webjars
единиц и /css
):
- Это соответствует всем запросам
"/all/**"
и"/anon/**"
маршрутам REST - Это соответствует любому запросу
Поскольку 1. chain имеет значение ниже @Order(98)
, чем 2. @Order(99)
, это означает, что 1. chain будет рассмотрен первым, что показано отладчиком,
и сопоставляется, если входящий запрос выглядит следующим образом:
curl --request GET
--url http://localhost:8080/all/hello
Теперь я испытываю то, что JWTokenFilter
метод boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response)
всегда вызывается независимо от пути запроса!
И в выводе на консоль я могу найти Asked for /all/hello
.
Редактировать: моя версия spring boot 2.3.6.RELEASE
Мой вопрос:
Почему JWTokenFIlter
даже спрашивается, должен ли он проверять подлинность запросов с путями, которые не совпадают с SecurityFilterChain
путями, частью которых он является?
Ответ №1:
Я полагаю, что у меня есть лучший ответ, но я хотел бы также ответить на ваш первоначальный вопрос. Я разделил это на две части.
Улучшенный ответ
Я понимаю, что это не отвечает на первоначальный вопрос, но я думаю, вам может быть лучше использовать встроенную поддержку аутентификации на основе JWT. Я бы проверил раздел сервера ресурсов OAuth 2.0 справочной документации.
Ответ на Первоначальный вопрос
Spring Boot автоматически зарегистрирует любой Filter
открытый как a @Bean
для каждого запроса непосредственно в контейнере сервлета.
У вас есть два варианта, которые я вижу. Во — первых , следует избегать разоблачения JwtTokenFilter
как @Bean
такового .
@Configuration
@Order(99)
public static class TokenSecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
JWTokenAuthenticationProvider jwTokenAuthenticationProvider;
@Autowired
JWSTokenAuthenticationProvider jwsTokenAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //having /admin/** or /** makes no difference
.anyRequest().authenticated()
.and().addFilterBefore(tokenFilter(),ExceptionTranslationFilter.class);//put this filter near the end of the chain
}
public JWTokenFilter tokenFilter(){
var list = new ArrayList<AuthenticationProvider>();
list.add(jwsTokenAuthenticationProvider);
list.add(jwTokenAuthenticationProvider);
ProviderManager manager = new ProviderManager(list);
return new JWTokenFilter(manager);
}
}
В качестве альтернативы вы можете продолжить показ JwtTokenFilter
как a @Bean
и создать a, FilterRegistrationBean
который отключает регистрацию.
@Bean
public FilterRegistrationBean registration(JWTokenFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}