源码地址:https://github.com/springsecuritydemo/microservice-auth-center03
当我们登录失败的时候,SpringSecurity 帮我们跳转到了 /login?error
URL,奇怪的是不管是控制台还是网页上都没有打印错误信息。
这是因为首先 /login?error
是SpringSecurity 默认的失败 URL,其次如果你不自己处理这个异常,这个异常时不会被处理的。
我们先来列举下一些 SpringSecurity 中常见的异常:
UsernameNotFoundException
(用户不存在)DisableException
(用户已被禁用)BadCredentialsException
(坏的凭据)LockedException
(账号锁定)CerdentialsExpiredException
(证书过期)AuthenticationException
的子类,然后我们看 SpringSecurity 是如何处理 AuthenticationException
异常的。SpringSecurity的异常处理是在过滤器中进行的,我们在 AbastrctAuthenticationProcessingFilter
中找到了对 Authentication
的处理:
unsuccessfulAuthentication()
中,转交给了 SimpleUrlAuthenticationFailureHandler
类的 onAuthencicationFailure()
处理。
defaultFailureUrl
。
a. 如果没有设置,直接返回 401 错误,即 HttpStatus.UNAUTHORIZED
的值。
b. 如果设置了,首先执行 saveException()
方法。然后判断 forwardToDestination
是否为服务器调整,默认使用重定向即客户端跳转。
forwardToDestination
,如果使用服务器跳转则写入Request
,客户端跳转则写入 Session
。写入名为 WebAttributes.AUTHENTICATION_EXCEPTION
常量对应值SPRING_SECURITY_LAST_EXCEPTION
,值为 AuthenticationException
对象。
至此 SpringSecurity 完成了异常处理,总结下流程:
–> AbstractAuthenticationProcessingFilter.doFilter()
–> AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication()
–> SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure()
–> SimpleUrlAuthenticationFailureHandler.saveException()
上面通过源码看着挺复杂,但真正处理起来SpringSecurity为我们提供了方便的方式,我们只需要指定错误的url,然后在该方法中对异常进行处理即可。
WebSecurityConfig
中添加 .failureUrl("/login/error")
: @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 如果有允许匿名的url,填在下面
// .antMatchers().permitAll()
.anyRequest().authenticated()
.and()
// 设置登陆页
.formLogin().loginPage("/login")
// 设置登陆成功url
.defaultSuccessUrl("/").permitAll()
// 设置登录失败url
.failureUrl("/login/error")
// 自定义登陆用户名和密码参数,默认为username和password
// .usernameParameter("username")
// .passwordParameter("password")
.and()
.logout().permitAll()
// 自动登录
.and().rememberMe()
.tokenRepository(persistentTokenRepository())
// 有效时间,单位:s
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
// 关闭CSRF跨域
http.csrf().disable();
}
loginError
方法完成异常处理操作: @GetMapping("/login/error")
@ResponseBody
public Result loginError(HttpServletRequest request) {
AuthenticationException authenticationException = (AuthenticationException) request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
log.info("authenticationException={}", authenticationException);
Result result = new Result();
result.setCode(201);
if (authenticationException instanceof UsernameNotFoundException || authenticationException instanceof BadCredentialsException) {
result.setMsg("用户名或密码错误");
} else if (authenticationException instanceof DisabledException) {
result.setMsg("用户已被禁用");
} else if (authenticationException instanceof LockedException) {
result.setMsg("账户被锁定");
} else if (authenticationException instanceof AccountExpiredException) {
result.setMsg("账户过期");
} else if (authenticationException instanceof CredentialsExpiredException) {
result.setMsg("证书过期");
} else {
result.setMsg("登录失败");
}
return result;
}
首先我们修改 CustomUserDetailsService
loadUserByUsername()
方法的返回值:
细心的同学再完成上面功能是会发现,当我们输入的用户名不存在时,不会抛出UserNameNotFoundException
,而是抛出 BadCredentialsException
这个异常,如果有需要区分 用户名不存在和密码错误的,可参考https://blog.csdn.net/wzl19870309/article/details/70314085。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有