Spring 安全匿名 401 而不是 403

Spring Security anonymous 401 instead of 403

我在 spring 安全中的默认行为有问题,授权请求随 Java 配置提供。

http
       ....
       .authorizeRequests()
          .antMatchers("/api/test/secured/*").authenticated()

当我在没有登录(使用匿名用户)的情况下调用例如 /api/test/secured/user 时,它 returns 403 Forbidden。当匿名用户想要通过 authenticated()@PreAuthorize 资源获得保护时,是否有一种简单的方法将状态更改为 401 Unauthorized?

我有解决方案here:

http
   .authenticationEntryPoint(authenticationEntryPoint)

AuthenticationEntryPoint 源代码:

@Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {

    private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class);

    /**
     * Always returns a 401 error code to the client.
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
            ServletException {

        log.debug("Pre-authenticated entry point called. Rejecting access");
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
    }
}

有了 spring 安全 4.x 已经有一个 class

org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint 

Spring开机还包括一个

org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint

并且他们要求开发人员使用符合规范的两个好处都必须设置 401 responses requires that header WWW-Authenticate,示例 401 响应可以是:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                   error="invalid_token",
                   error_description="The access token expired"

因此,在您的安全配置中,您定义并自动装配了一个 class

的 bean

例如 spring 启动应用程序:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public Http401AuthenticationEntryPoint securityException401EntryPoint(){

        return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
    }

...
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
                .antMatchers("/login").anonymous()
                .antMatchers("/").anonymous()
                .antMatchers("/api/**").authenticated()
            .and()
            .csrf()
                .disable()
                .headers()
                .frameOptions().disable()
            .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .logout()
                .permitAll()
         .exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());
}

相关行是:

 .exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());

您需要扩展 AuthenticationEntryPoint 以根据异常进行自定义。

@ControllerAdvice
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
      throws IOException, ServletException {
    // 401
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
  }

  @ExceptionHandler (value = {AccessDeniedException.class})
  public void commence(HttpServletRequest request, HttpServletResponse response,
      AccessDeniedException accessDeniedException) throws IOException {
    // 401
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Failed : " + accessDeniedException.getMessage());
  }
}

在您的 SecurityConfig 中指定上述自定义 AuthenticationEntryPoint,如下所示:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity (prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.exceptionHandling()
        .authenticationEntryPoint(new MyAuthenticationEntryPoint());
  }
}

自 Spring 启动 2 class Http401AuthenticationEntryPoint 已删除(参见 Spring Boot Issue 10725)。

使用 HttpStatusEntryPoint 而不是 Http401AuthenticationEntryPoint HttpStatus.UNAUTHORIZED:

http.exceptionHandling()
    .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));

Spring Boot 2 中使用 lambda 表达式的简单方法:

@Override
public void configure(HttpSecurity http) throws Exception {
    http.
        ...
        .exceptionHandling()
            .authenticationEntryPoint((request, response, e) -> {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.setContentType("application/json");
                response.getWriter().write("{ \"error\": \"You are not authenticated.\" }");
            })
        ...
}

谁对工作机制感兴趣。
如果你不设置 http.exceptionHandling().authenticationEntryPoint() spring 将使用 defaultAuthenticationEntryPoint() 和方法 ExceptionHandlingConfigurer.createDefaultEntryPoint() 将 returnnew Http403ForbiddenEntryPoint()
因此,只需创建 Http401UnauthorizedEntryPoint()。上面回答了怎么做,没有重复。

P.S。这是 Spring Security 5.2 的实际情况。5.RELEASE