Spring Security with Spring Boot:将基本身份验证与 JWT 令牌身份验证混合使用

2022-01-10 00:00:00 jwt java spring-boot spring-security

我正在尝试让 Spring Security 的基本身份验证与 JWT 令牌身份验证并行工作,但没有成功.我已经为我的 Web 控制台和 JWT 实现了基本身份验证,以保护许多 API 端点.这是我的配置:

I am trying to get Spring Security's basic authentication to work side by side with JWT token authentication with no success. I have implemented basic authentication for my web console and JWT to secure a number of API endpoints. Here's my config:

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MultiHttpSecurityConfig {

@Autowired
private UserDetailsService userDetailsService;    

@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder
            .userDetailsService(this.userDetailsService)
            .passwordEncoder(bCryptPasswordEncoder());
}

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

/**
 * 
 * API Security configuration
 *
 */
@Configuration
@Order(1) 
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter{

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        return new JwtAuthenticationTokenFilter();
    }

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity   
            .csrf().disable()
            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()                
            // don't create session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeRequests().antMatchers("/api/**","/refresh/**").authenticated()
            .antMatchers("/auth/**").permitAll().anyRequest().authenticated();               
        // Custom JWT based security filter
        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        // disable page caching
        httpSecurity.headers().cacheControl();
    }
}

/**
 * 
 * Form login security configuration
 *
 */
@Configuration
public static class FormLoginWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ConsoleAuthenticationEntryPoint consoleAuthenticationEntryPoint;

    @Override
    protected void configure(HttpSecurity http) throws Exception {          
        http.httpBasic().and().exceptionHandling().authenticationEntryPoint(
                consoleAuthenticationEntryPoint).and()
         .authorizeRequests().antMatchers("/console/**").authenticated()
         .antMatchers(HttpMethod.GET,
                    "/*.html",
                    "/favicon.ico",
                    "/**/*.html",
                    "/**/*.css",
                    "/**/*.js").permitAll()
         .anyRequest().authenticated()
         .and()
         .formLogin().defaultSuccessUrl("/console/home")
         .loginPage("/console/login")
         .permitAll()
         .and()
         .logout()
         .permitAll();
        http.csrf().disable();
    }
}

}

我注意到我用 Order(1) 注释的配置是 Spring Security 选择的配置,而另一个则被完全忽略.就像上面的配置一样,如果我尝试访问/console/login,我会收到 401 错误.任何帮助将不胜感激.

I have noticed that the configuration I annotate with Order(1) is the one that is picked by Spring Security and the other is completely ignored. Like in the above config, I get 401 error if I try to access /console/login. Any help would be much appreciated.

推荐答案

原因是因为ApiWebSecurityConfigurationAdapterFormLoginWebSecurityConfig都没有使用antMatcher()代码>.这意味着两种安全配置都将处理所有路径,即使您之后使用 antMatchers() 也是如此.因此,具有最低顺序的配置 (@Order(1)) 将处理所有事情,而另一个则什么也不做.

The reason why is because neither ApiWebSecurityConfigurationAdapter nor FormLoginWebSecurityConfig uses the antMatcher(). This means that both security configurations will handle all paths, even though you're using antMatchers() afterwards. Due to this, the configuration with the lowest order (@Order(1)) will handle everything, while the other one will do nothing.

docs 中也提到了这一点:

http.antMatcher 声明此 HttpSecurity 将仅适用于以 /api/

The http.antMatcher states that this HttpSecurity will only be applicable to URLs that start with /api/

因此,要解决此问题,您必须为您的一个配置(或两者)提供一个 antMatcher.例如,如果表单登录只应用于 /console/login/console/home,您可以将配置更改为:

So, to fix this problem, you have to povide an antMatcher to one of your configurations (or both). For example, if the form login should only be applied to /console/login and /console/home, you could change the configuration to:

@Override
protected void configure(HttpSecurity http) throws Exception {          
    http
        .antMatcher("/console/**") // Add this
        .httpBasic().and()
        .exceptionHandling().authenticationEntryPoint(consoleAuthenticationEntryPoint).and()
        .authorizeRequests().antMatchers("/console/**").authenticated()
        .antMatchers(HttpMethod.GET,
                "/*.html",
                "/favicon.ico",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js").permitAll()
        .anyRequest().authenticated().and()
        .formLogin().defaultSuccessUrl("/console/home")
        .loginPage("/console/login").permitAll().and()
        .logout().permitAll().and() // Make sure to use .and() to add the .csrf()
        .csrf().disable();
}

关于这个主题的另一个好读物是这个问题:何时使用 Spring Security`santMatcher()?

Another good read about this topic is this question: When to use Spring Security`s antMatcher()?

请注意,您不应像添加 .csrf().disable() 那样两次使用 http 构建器,将其添加到另一个构建器就像我在上面的代码中所做的那样.

Please note that you shouldn't use the http builder twice like you did to add the .csrf().disable(), add it to the other builder like I did in the code above.

另外请注意,您可能需要更改订单.您应该将顺序放在配置最详细的 antMatcher() 上,在本例中为 FormLoginWebSecurityConfig.

Also be aware that you'll likely have to change the order. You should put the order on the configuration with the most detailed antMatcher(), in this case FormLoginWebSecurityConfig.

相关文章