Spring Boot 2返回401而不是403

18

使用 Spring Boot 1.5.6.RELEASE,我能够像如何让Spring Security在未经身份验证的情况下响应未授权(HTTP 401代码)中所描述的那样发送HTTP状态代码401而不是403,方法如下:

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //...
        http.exceptionHandling()
                .authenticationEntryPoint(new Http401AuthenticationEntryPoint("myHeader"));
        //...
    }
}

使用org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint类。

我刚刚升级到Spring Boot 2.0.0.RELEASE,发现该类不再存在(至少在该包中不存在)。

问题:

  • Spring Boot中是否仍存在这个类(Http401AuthenticationEntryPoint)?

  • 如果没有,对于现有项目保持一致性并以这个状态码(401)而不是403作为依赖的其他实现,有什么好的替代方案?


请注意,这与Spring Security anonymous 401 instead of 403不同,因为它特别指的是SpringBoot 2 (该帖子中的解决方案在SpringBoot版本2中不再适用或根本不需要)。


这个回答解决了你的问题吗?Spring Security匿名401而不是403 - Leponzo
@Leponzo,感谢您指出这一点。我认为它不应该被视为重复,并在原始帖子上添加了一条评论,解释了为什么--供未来读者参考。我确实找到了一个非常类似于我下面发布的答案的答案(https://dev59.com/p10a5IYBdhLWcg3wJl6o#52986942)--但它实际上是在我的之后发布的:) ... 无论如何,在更广泛的范围内,我认为它们是不同的问题。 - lealceldeiro
5个回答

35

注意

默认情况下,当添加spring-boot-starter-security作为依赖项并执行未经授权的请求时,Spring Boot 2将返回401

如果您需要更改安全机制行为以进行自定义配置,则可能会更改这种情况。如果是这种情况,并且您确实需要强制使用401状态,请阅读下面的原始帖子。

原始帖子

org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint已被移除,使用org.springframework.security.web.authentication.HttpStatusEntryPoint代替。

在我的情况下,代码如下:

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //...
        http.exceptionHandling()
            .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
        //...
    }
}

奖励

如果您需要在响应正文中返回某些信息或以某种方式自定义响应,则可以执行以下操作:

1- 扩展AuthenticationEntryPoint

public class MyEntryPoint implements AuthenticationEntryPoint {
    private final HttpStatus httpStatus;
    private final Object responseBody;

    public MyEntryPoint(HttpStatus httpStatus, Object responseBody) {
        Assert.notNull(httpStatus, "httpStatus cannot be null");
        Assert.notNull(responseBody, "responseBody cannot be null");
        this.httpStatus = httpStatus;
        this.responseBody = responseBody;
    }

    @Override
    public final void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.setStatus(httpStatus.value());

        try (PrintWriter writer = response.getWriter()) {
            writer.print(new ObjectMapper().writeValueAsString(responseBody));
        }
    }
}

2- 将MyEntryPoint的一个实例提供给安全配置

public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // customize your response body as needed
        Map<String, String> responseBody = new HashMap<>();
        responseBody.put("error", "unauthorized");

        //...
        http.exceptionHandling()
            .authenticationEntryPoint(new MyEntryPoint(HttpStatus.UNAUTHORIZED, responseBody));
        //...
    }
}

3
目前不良的凭证请求返回401错误,但是响应内容为空。此外,未经授权的请求本应返回403错误,但也会返回带有空响应体的401错误。 - Nelio Alves
1
这会返回没有正文的401。对于js来说没问题,但在Firefox中查看时是一个空白页面,在Chrome中则显示“此页面无法工作”。不过,很容易制作自己的Htp401AuthenticationEntryPoint替代品并使用它。只需实现AuthenticationEntryPoint并设置您想要的任何状态和消息即可。 - Planky
@Planky非常感谢您指出这一点!我只是根据您的评论提出了其他人可能会遵循的一种可能方法 :) - lealceldeiro

3

仅是对@lealceldeiro回答的一些详细解释:

在Spring Boot 2之前,我的安全配置类看起来是这样的:

@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public Http401AuthenticationEntryPoint securityException401EntryPoint() {
      return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
    }

    @Autowired
    private Http401AuthenticationEntryPoint authEntrypoint;

    @Override
    protected void configure(HttpSecurity http) throws Exception {

      // some http configuration ...

      // Spring Boot 1.5.x style
      http.exceptionHandling().authenticationEntryPoint(authEntrypoint);
    }
//...
}

现在在Spring Boot 2中,它看起来像这样:

@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {

    //Bean configuration for Http401AuthenticationEntryPoint can be removed

    //Autowiring also removed

    @Override
    protected void configure(HttpSecurity http) throws Exception {

      // some http configuration ...

      // Spring Boot 2 style
      http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    }
//...
}

请参考Spring Boot Github库中的PR Remove Http401AuthenticationEntryPoint评论。


2
解决方案看起来很好,很干净,但缺少必需的“WWW-Authenticate”头,该头将告诉客户端如何尝试进行身份验证。 - dube

2

Http401AuthenticationEntryPoint已被删除。

请参阅Spring Boot Github Repo > Issue#10715(Remove Http401AuthenticationEntryPoint):

删除Http401AuthenticationEntryPoint

rwinch于2017年10月20日发表评论
据我所知,它在Spring Boot代码库中没有使用,因此最好删除Http401AuthenticationEntryPoint

根据您的要求,您可以使用以下内容:


1
谢谢,来自Spring Boot Git仓库的链接非常有用。在我的回答中,未来的读者可以看到我如何根据自己的需求使用HttpStatusEntryPoint - lealceldeiro

1

对于响应式(WebFlux)堆栈,您可以通过添加以下 @Bean 来捕获某些特定异常并覆盖返回的状态码:

@Component
class MyErrorAttributes : DefaultErrorAttributes() {
override fun getErrorAttributes(
    request: ServerRequest,
    options: ErrorAttributeOptions
): MutableMap<String, Any> {
    val cause = super.getError(request)

    val errorAttributes = super.getErrorAttributes(request, options)

    when (cause) {
        is TokenExpiredException -> {
            errorAttributes["status"] = HttpStatus.UNAUTHORIZED.value()
            errorAttributes["error"] = HttpStatus.UNAUTHORIZED.reasonPhrase
        }
    }

    return errorAttributes
}
}

不错!这个语法是从哪里来的? - lealceldeiro
它是Kotlin语言。 - Roman T

0
您可以通过覆盖 AuthenticationEntryPoint 类来自定义逻辑,以下代码应该可以正常工作:
@Component public class AuthEntryPointException implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
        AuthenticationException authException) throws IOException {
        response.setStatus(HttpStatus.SC_UNAUTHORIZED);
        response.setContentType("application/json");
        response.getWriter().write("{\"result\":\"UNAUTHORIZED\",\"message\":\"UNAUTHORIZED or Invalid Token\"}");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接