前面文章分别分析了SprignSecurity的执行流程和源码,也分析了一遍我要实现分布式系统使用Security认证的思路和问题。这篇文章主要是说明security认证失败后,要如何处理。
之前说过实现Security安全认证其实方式很多,大家从网络上能看到的例子也很多,实现的方法也各不相同。有重写UsernamePasswordAuthenticationFilter
过滤器的,也有不重写的,也有自定义controller登录接口的。但其实万变不离其宗,你终归要用到Security提供的各种过滤器和类。
1.成功和失败处理
1.1.源码分析
我们按照重写UsernamePasswordAuthenticationFilter
过滤器的思路看源码就可以把几种方式都看清。
如果你重写,那有一个核心工作你肯定会做,那就是重写attemptAuthentication
,获取到前端提交的用户名密码后,封装成UsernamePasswordAuthenticationToken
对象,调用AuthenticationManager
的‘authenticate’方法。逻辑大概就是这个样子吧。
代码上半部分是做了一下表单提交和json提交数据的兼容,确保两种方式都可以拿到前端请求来的用户名和密码。
那谁调用的这个方法呢?
那谁调用这个attemptAuthentication
呢?
点进去看只有一个地方AbstractAuthenticationProcessingFilter
,看他的源码一眼就能明白。源码有省略,为了看得直白点。
try {
authResult = attemptAuthentication(request, response);
}
catch (InternalAuthenticationServiceException failed) {
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
successfulAuthentication(request, response, chain, authResult);
unsuccessfulAuthentication
和successfulAuthentication
是不是很显眼,看名字就能看懂,处理失败的和处理成功的。
成功的处理
我们看这个类里面自己的处理逻辑:
- 向上下文中保存了用户信息;
- 推送了认证成功的事件;
- 最后调用了
seccessHandler
处理。
失败的处理
也看这个类里里面自己的处理逻辑:
- 清除上下文信息;
- 最后调用了
failureHandler
处理。
所以现在大家知道有哪些方式了吧?不卖关子直接说:
-
在你重写的过滤器里面,重写
successfulAuthentication
和unsuccessfulAuthentication
两个方法;
-
定义一个处理器类实现
AuthenticationSuccessHandler
,重写onAuthenticationSuccess
方法;定义一个处理器类实现AuthenticationFailureHandler
,重写onAuthenticationFailure
方法,分别处理认证成功和失败的逻辑。
1.2.区别
第一种方法,只有在你重写过滤器的时候才能用,因为这两个方法属于这个过滤器,不重写过滤器咋玩嘛。
第二种方法,重写或者没有重写过滤器都可以用,他们是Security的配置类中的属性,你只要在Security配置里面配置了就生效。
2. 第三种失败和异常处理
除此之外还有一个方法可以处理失败和异常,那就是自定义实现AuthenticationEntryPoint
类,重写commence
.方法。
从AuthenticationEntryPoint
类里面我们可以看到调用这个方法的有一个非常熟悉的handler类,名字也是failure,进去看一下。
这个类实现了AuthenticationFailureHandler
,而我自定义的失败处理器EdenLoginFailureHandler
,也是实现的这个类。
失败的处理方法onAuthenticationFailure
里面调用了AuthenticationEntryPoint
的commence
方法。
由此我们得出结论,如果我不使用前两种方式去处理失败的异常,那么自定义实现AuthenticationEntryPoint
类,重写commence
.方法也是完全可以实现的。而且方法的入参和前面两种都一样,HttpServletRequest
, HttpServletResponse
, AuthenticationException
都有,我们利用response向前端返回响应就可以了。
大概示例就是这样吧,大家根据自己情况,自由发挥就行。
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException failed) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter writer = httpServletResponse.getWriter();
R r = R.failed();
if (failed instanceof InsufficientAuthenticationException) {
r.setMsg("请先登录~~");
} else if (failed instanceof BadCredentialsException) {
r.setMsg("用户名或密码错误");
} else {
r.setMsg("用户认证失败,请检查后重试");
}
writer.write(new ObjectMapper().writeValueAsString(r));
writer.flush();
writer.close();
}