Spring Security中使用HttpServletResponse::sendError产生的一次非预期返回

前言

本文主要记录公司使用spring security时在认证失败阶段给客户端返回失败时的一次非预期返回。

所以下面会提到Spring Security在认证过程中的一些名称,如AuthenticationFilter,AuthenticationManager,AuthenticationProvider等,分别用来过滤请求,执行认证过程,提供具体认证方式。

spring security使用一个filter链完成对请求的认证(authentication)和授权(authorization)过程,详细过程在这里暂时不展开。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rcuFHbzU-1590807662798)(https://docs.spring.io/spring-security/site/docs/5.4.0-M1/reference/html5/images/servlet/architecture/securityfilterchain.png)]

场景

认证使用的Filter继承了UsernamePasswordAuthenticationFilter实现用户名密码等认证信息的获取

public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // ..省略从请求中获得用户名密码等操作
        // 创建认证信息
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(account, password);
        this.setDetails(request, token);
        // 执行认证过程
        return this.getAuthenticationManager().authenticate(token);
    }
}

MyAuthenticationFilter最终会被加入到上图提到的FilterChainProxy以完成对认证请求的处理。

当然MyAuthenticationFilter可以设置对应的认证url,认证成功处理,认证失败处理,具体认证的认证管理器:

MyAuthenticationFilter filter = new MyAuthenticationFilter();
// 登录成功失败处理器
filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
filter.setFilterProcessesUrl("/login.do");
// WebSecurityConfigurerAdapter中提供了默认authenticationManager的获取方法
filter.setAuthenticationManager(authenticationManager());
return filter;

问题就出在了authenticationFailureHandler,这个处理器原是被这样写的:

public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        response.sendError(403, "认证失败," + e.getMessage());
    }
}

其中AuthenticationFailureHandler接口定义了在请求认证过程所抛出的AuthenticationException类型异常的处理方法onAuthenticationFailure,在这个方法里,同事原本的意图应该是返回403加上认证失败的消息

但是我在使用时发现故意填错用户名密码等认证信息时,返回的响应总是:

HTTP/1.1 403 Forbidden
..
content-length: 0

没有预期的认证信息错误之类信息。

原因和暂时的解决方案

原因很容易想到是因为调用了sendError方法,查看sendError(int, String)方法的文档:

Sends an error response to the client using the specified status and clears the buffer. The server defaults to creating the response to look like an HTML-formatted server error page containing the specified message, setting the content type to "text/html".

意思就是,sendError(int, String)方法会设置对应的响应状态码,同时返回一个 HTML 格式的错误页,设置响应类型为text/html

其结果直接造成响应被转发到 /error 页,也就是进来一个新的请求 /error,但是我们的spring security授权管理没有对/error授权,直接被拦截下来,返回了授权失败的配置,也就是之前的返回结果,响应码403,无msg信息。

考虑了暂时的解决方案,直接使用setStatus后手动写入响应,不再走/error需要的error-page。

response.setContentType("text/plain;charset=utf-8");
response.setStatus(403);
response.getWriter().write("认证失败" + e.getMessage());
response.getWriter().flush();

得到了预期的响应结果。

参考

javax.servlet.http

https://stackoverflow.com/questions/14459045/httpservletresponsesenderror-how-to-change-contenttype

你可能感兴趣的:(Java,EE)