Spring Boot permitAll 在 WebSecurityConfigurerAdapter 中不起作用

Posted

技术标签:

【中文标题】Spring Boot permitAll 在 WebSecurityConfigurerAdapter 中不起作用【英文标题】:Spring Boot permitAll not working in WebSecurityConfigurerAdapter 【发布时间】:2021-03-06 22:00:08 【问题描述】:

当我 POST 到 /api/v1/auth/register 时,我收到了配置的 accessDeniedHandler 生成的 403 响应。但是,我希望这个请求会被允许,因为它被 permitAll() 覆盖并且在 anyRequest().authenticated() 之前。

GET /api/v1/reference/countries 之类的请求可以正常工作。此外,命中这些/api/v1/auth/** 端点的集成测试也可以工作,这表明与 CORS 有关,尽管预检请求是 200。

知道这个安全配置有什么问题吗?

@Configuration
@EnableGlobalMethodSecurity(
    prePostEnabled = true,
    securedEnabled = true,
    jsr250Enabled = true
)
@Order(1)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter 
    private final UserDetailsServiceImpl userDetailsService;
    private final Config config;
    private final ObjectMapper objectMapper;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
            .cors()
            .and()

            .authorizeRequests()
            .antMatchers(
                "/api/v1/auth/register",
                "/api/v1/auth/register/check",
                "/api/v1/auth/register/activate",
                "/api/v1/auth/password/update",
                "/api/v1/auth/recover",
                "/api/v1/auth/recover/check",
                "/api/v1/auth/recover/reset",
                "/api/v1/csrf-token",
                "/api/v1/reference/**"
            )
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()

            .formLogin()
            .successHandler((request, response, authentication) -> 
                response.getWriter().append("OK");
                response.setStatus(HttpServletResponse.SC_OK);
            )
            .failureHandler((request, response, exception) -> 
                response.getWriter().append("Invalid credentials or inactive account");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            )
            .loginProcessingUrl("/api/v1/auth/login")
            .permitAll()
            .and()

            .logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/api/v1/auth/logout", "POST"))
            .permitAll()
            .and()

            .exceptionHandling()
            .accessDeniedHandler((request, response, accessDeniedException) -> 
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                objectMapper.writeValue(
                    response.getWriter(),
                    ErrorResponseBody
                        .builder()
                        .code(ErrorType.ACCESS_DENIED)
                        .status(HttpServletResponse.SC_FORBIDDEN)
                        .message("Access denied")
                        .build()
                );
            )
            .authenticationEntryPoint((request, response, authException) -> 
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                objectMapper.writeValue(
                    response.getWriter(),
                    ErrorResponseBody
                        .builder()
                        .code(ErrorType.LOGIN_REQUIRED)
                        .status(HttpServletResponse.SC_UNAUTHORIZED)
                        .message("You are not authorized to access this resource")
                        .build()
                );
            )
            .and()

            .userDetailsService(userDetailsService);

        if (config.isCsrfDisabled()) 
            http
                .csrf()
                .disable();
        
    

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService);
    

    @Bean
    public BCryptPasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Bean
    public CorsConfigurationSource corsConfigurationSource() 
        final var configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.setAllowedOrigins(config.getAllowedOrigins());
        configuration.setAllowedMethods(asList("GET", "POST", "PUT", "PATCH", "DELETE"));
        configuration.setAllowedHeaders(Arrays.asList(HttpHeaders.AUTHORIZATION, HttpHeaders.CACHE_CONTROL, HttpHeaders.CONTENT_TYPE, HttpHeaders.ACCEPT));
        final var source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", configuration);
        return source;
    

这是我的 CORS 配置:

@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class WebConfig 
    private final Config config;

    @Bean
    public WebMvcConfigurer corsConfigurer() 
        return new WebMvcConfigurer() 
            @Override
            public void addCorsMappings(CorsRegistry registry) 
                final var allowedOrigins = Optional
                    .ofNullable(config.getAllowedOrigins())
                    .map(origins -> origins.toArray(new String[]))
                    .orElse(new String[]);

                System.out.println("Enabling CORS for the following origins:" + Arrays.asList(allowedOrigins).toString());

                registry
                    .addMapping("/api/**")
                    .allowedOrigins(allowedOrigins)
                    .allowCredentials(true)
                    .allowedMethods("*")
                    .allowedHeaders("*");
            
        ;
    

我从http://localhost:3000 调用这些端点,这是config.getAllowedOrigins() 返回的项目之一。

这是来自请求的 Spring Security 调试日志:

2020-11-24 06:51:16.110  INFO 1 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-11-24 06:51:16.112  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-11-24 06:51:16.158  INFO 1 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 44 ms
2020-11-24 06:51:16.193 DEBUG 1 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-11-24 06:51:16.200 DEBUG 1 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-11-24 06:51:16.205 DEBUG 1 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2020-11-24 06:51:16.206 DEBUG 1 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2020-11-24 06:51:16.214 DEBUG 1 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-11-24 06:51:16.217 DEBUG 1 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 4 of 13 in additional filter chain; firing Filter: 'CorsFilter'
2020-11-24 06:51:16.286 DEBUG 1 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@461bbc94
2020-11-24 06:51:16.290 DEBUG 1 --- [nio-8080-exec-1] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2020-11-24 06:51:16.299 DEBUG 1 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
2020-11-24 06:51:16.483 DEBUG 1 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-11-24 06:51:16.485 DEBUG 1 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-11-24 06:51:16.486 DEBUG 1 --- [nio-8080-exec-2] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2020-11-24 06:51:16.487 DEBUG 1 --- [nio-8080-exec-2] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2020-11-24 06:51:16.488 DEBUG 1 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-11-24 06:51:16.489 DEBUG 1 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 4 of 13 in additional filter chain; firing Filter: 'CorsFilter'
2020-11-24 06:51:16.494 DEBUG 1 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/auth/register at position 5 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
2020-11-24 06:51:16.525 DEBUG 1 --- [nio-8080-exec-2] o.s.security.web.csrf.CsrfFilter         : Invalid CSRF token found for http://dev.api.example.com/api/v1/auth/register
2020-11-24 06:51:16.609 DEBUG 1 --- [nio-8080-exec-2] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@461bbc94
2020-11-24 06:51:16.610 DEBUG 1 --- [nio-8080-exec-2] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2020-11-24 06:51:16.615 DEBUG 1 --- [nio-8080-exec-2] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

【问题讨论】:

您是否尝试过为 Spring Security 启用调试日志记录?这能说明问题吗? 为 Spring Security 启用调试日志记录后,请求挂起然后超时。 看起来熵不足与启用日志记录同时发生。 【参考方案1】:

configure(HttpSecurity http) 中使用.csrf().disable() 禁用CSRF 更多细节在here 中解释

@Override
    protected void configure(HttpSecurity http) throws Exception 
        http
           .csrf().disable()
           ... // other configurations

【讨论】:

谢谢。我选择了选项 2。选项 1 不是一个好主意,因为 ti 完全禁用了网络安全。很高兴接受这个答案,去掉了选项 1。 是的,configure(WebSecurity web) 理想情况下应该用于静态内容。

以上是关于Spring Boot permitAll 在 WebSecurityConfigurerAdapter 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

带有 RequestHeaderMatcher 的 Spring Boot Security PermitAll 不起作用

即使使用 permitall antMatchers,Spring boot Oauth 2 配置也会导致 401

Spring Security permitAll()不匹配排除网址[重复]

Spring Boot Security 基于角色的访问控制

在 Spring Boot 中使用 Useroles 访问 JSP 页面的权限

Spring Boot - 具有安全性的 Angularjs $stateProvider