failureUrl()failureForwardUrl() 的区别

前者是客户端 302 重定向,后者是服务端跳转,能够携带一些信息,包括登录错误信息

可通过如下方式显示登录错误信息:

image.png

<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()) // 自定义失败处理器,适合前后端分离
                
                // 省略更多配置