failureUrl() 和 failureForwardUrl() 的区别前者是客户端 302 重定向,后者是服务端跳转,能够携带一些信息,包括登录错误信息
可通过如下方式显示登录错误信息:

<aside> 💡
请求失败时,在 Session 中存储错误信息,Session 是跨请求共享的,唯一标识是存储在浏览器 Cookie 里的 JSESSIONID
但是重定向请求不会展示 Session 中的信息,转发请求才能展示 Session 中的信息(这一点还需要阅读 Spring 相关文档了解原理)
和上一讲的逻辑一样,登录失败逻辑由 AuthenticationFailureHandler 接口来处理:
classDiagram
direction BT
class AuthenticationEntryPointFailureHandler
class AuthenticationFailureHandler {
<<Interface>>
}
class DelegatingAuthenticationFailureHandler
class ExceptionMappingAuthenticationFailureHandler
class ForwardAuthenticationFailureHandler
class SimpleUrlAuthenticationFailureHandler
AuthenticationEntryPointFailureHandler ..> AuthenticationFailureHandler
DelegatingAuthenticationFailureHandler ..> AuthenticationFailureHandler
ExceptionMappingAuthenticationFailureHandler --> SimpleUrlAuthenticationFailureHandler
ForwardAuthenticationFailureHandler ..> AuthenticationFailureHandler
SimpleUrlAuthenticationFailureHandler ..> AuthenticationFailureHandler
以 SimpleUrlAuthenticationFailureHandler 为例,部分源码:
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (this.defaultFailureUrl == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Sending 401 Unauthorized error since no failure URL is set");
}
else {
this.logger.debug("Sending 401 Unauthorized error");
}
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
return;
}
saveException(request, exception);
if (this.forwardToDestination) {
this.logger.debug("Forwarding to " + this.defaultFailureUrl);
request.getRequestDispatcher(this.defaultFailureUrl).forward(request, response);
}
else {
this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);
}
}
/**
* Caches the {@code AuthenticationException} for use in view rendering.
* <p>
* If {@code forwardToDestination} is set to true, request scope will be used,
* otherwise it will attempt to store the exception in the session. If there is no
* session and {@code allowSessionCreation} is {@code true} a session will be created.
* Otherwise the exception will not be stored.
*/
protected final void saveException(HttpServletRequest request, AuthenticationException exception) {
if (this.forwardToDestination) {
request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
return;
}
HttpSession session = request.getSession(false);
if (session != null || this.allowSessionCreation) {
request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
}
}
同样地,如果是前后端分离的场景,我们可以自定义一个 AuthenticationFailureHandler 的实现类:
public class MyAuthenticationFailureHandler implements
AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
Map<String, Object> resp = new HashMap<>();
resp.put("status", 500);
resp.put("msg", "登录失败!" + exception.getMessage());
ObjectMapper om = new ObjectMapper();
String s = om.writeValueAsString(resp);
response.getWriter().write(s);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/mylogin.html")
.loginProcessingUrl("/doLogin")
.defaultSuccessUrl("/index.html")
// .failureUrl("/mylogin.html") // 重定向,不会展示 Session 里的错误信息
// .failureForwardUrl("/mylogin.html") // 转发,会展示 Session 里的错误信息
.failureHandler(new MyAuthenticationFailureHandler()) // 自定义失败处理器,适合前后端分离
// 省略更多配置