Spring Security Web 5.1.2 源码解析 -- SimpleUrlAuthenticationFailureHandler

概述

AuthenticationFailureHandler接口定义了Spring Security Web在遇到认证错误时所使用的处理策略。

典型做法一般是将用户重定向到认证页面(比如认证机制是用户名表单认证的情况)让用户再次认证。当然具体实现类可以根据需求实现更复杂的逻辑,比如根据异常做不同的处理等等。举个例子,如果遇到CredentialsExpiredException异常(AuthenticationException异常的一种,表示密码过期失效),可以将用户重定向到修改密码页面而不是登录认证页面。

该接口定义如下 :

package org.springframework.security.web.authentication;

public interface AuthenticationFailureHandler {

	/**
	 * 认证失败时会调用此方法
	 * @param request 出现认证失败时所处于的请求.
	 * @param response 对应上面请求的响应对象.
	 * @param exception 携带认证失败原因的认证失败异常对象
	 * request.
	 */
	void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException;
}

Spring Security Web框架内部,缺省使用的认证错误处理策略是AuthenticationFailureHandler的实现类SimpleUrlAuthenticationFailureHandler。它由配置指定一个defaultFailureUrl,表示认证失败时缺省使用的重定向地址。一旦认证失败,它的方法onAuthenticationFailure被调用时,它就会将用户重定向到该地址。如果该属性没有设置,它会向客户端返回一个401状态码。另外SimpleUrlAuthenticationFailureHandler还有一个属性useForward,如果该属性设置为true,页面跳转将不再是重定向(redirect)机制,取而代之的是转发(forward)机制。

源代码解析

具体实现代码如下 :

package org.springframework.security.web.authentication;

public class SimpleUrlAuthenticationFailureHandler implements
		AuthenticationFailureHandler {
	protected final Log logger = LogFactory.getLog(getClass());

	// 认证失败时缺省使用的重定向地址
	// 缺省情况下,defaultFailureUrl 会被外部设置为 /login?error
	private String defaultFailureUrl;
	// 是否使用 forward, 缺省为 false, 表示使用 redirect
	private boolean forwardToDestination = false;
	// 是否在需要session的时候允许创建session
	private boolean allowSessionCreation = true;
	// 页面重定向策略
	private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

	public SimpleUrlAuthenticationFailureHandler() {
	}

	public SimpleUrlAuthenticationFailureHandler(String defaultFailureUrl) {
		setDefaultFailureUrl(defaultFailureUrl);
	}

	/**
	 * Performs the redirect or forward to the defaultFailureUrl if set, otherwise
	 * returns a 401 error code.
	 * 
	 * If redirecting or forwarding, saveException will be called to cache the
	 * exception for use in the target view.
	 */
	public void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException {

		if (defaultFailureUrl == null) {
			logger.debug("No failure URL set, sending 401 Unauthorized error");

			// 如果 defaultFailureUrl 没有设置,向客户端返回 401 错误 : Unauthorized
			response.sendError(HttpStatus.UNAUTHORIZED.value(),
				HttpStatus.UNAUTHORIZED.getReasonPhrase());
		}
		else {
			saveException(request, exception);

			if (forwardToDestination) {
				// 指定了使用 forward 的情况
				logger.debug("Forwarding to " + defaultFailureUrl);

				request.getRequestDispatcher(defaultFailureUrl)
						.forward(request, response);
			}
			else {
				// 指定了使用  redirect 的情况 , 缺省情况
				logger.debug("Redirecting to " + defaultFailureUrl);
				redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
			}
		}
	}

	/**
	 * Caches the AuthenticationException for use in view rendering.
	 * 
	 * If 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 allowSessionCreation is true a session will be created.
	 * Otherwise the exception will not be stored.
	 */
	protected final void saveException(HttpServletRequest request,
			AuthenticationException exception) {
		if (forwardToDestination) {
		// forward 的情况,保存异常到 request 属性 : SPRING_SECURITY_LAST_EXCEPTION
			request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
		}
		else {
		// redirect 的情况 , 保存异常到 session : SPRING_SECURITY_LAST_EXCEPTION
			HttpSession session = request.getSession(false);

			if (session != null || allowSessionCreation) {
				request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION,
						exception);
			}
		}
	}

	/**
	 * The URL which will be used as the failure destination.
	 *
	 * @param defaultFailureUrl the failure URL, for example "/loginFailed.jsp".
	 */
	public void setDefaultFailureUrl(String defaultFailureUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(defaultFailureUrl),
				() -> "'" + defaultFailureUrl + "' is not a valid redirect URL");
		this.defaultFailureUrl = defaultFailureUrl;
	}

	protected boolean isUseForward() {
		return forwardToDestination;
	}

	/**
	 * If set to true, performs a forward to the failure destination URL instead
	 * of a redirect. Defaults to false.
	 */
	public void setUseForward(boolean forwardToDestination) {
		this.forwardToDestination = forwardToDestination;
	}

	/**
	 * Allows overriding of the behaviour when redirecting to a target URL.
	 */
	public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
		this.redirectStrategy = redirectStrategy;
	}

	protected RedirectStrategy getRedirectStrategy() {
		return redirectStrategy;
	}

	protected boolean isAllowSessionCreation() {
		return allowSessionCreation;
	}

	public void setAllowSessionCreation(boolean allowSessionCreation) {
		this.allowSessionCreation = allowSessionCreation;
	}
}

应用位置

  1. FormLoginConfigurer基类AbstractAuthenticationFilterConfigurer
	// 表单认证安全配置阶段指定 failureHandler
	// 缺省情况下,这里 authenticationFailureUrl 是 /login?error
	public final T failureUrl(String authenticationFailureUrl) {
		T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(
				authenticationFailureUrl));
		this.failureUrl = authenticationFailureUrl;
		return result;
	}
  1. UsernamePasswordAuthenticationFilter基类AbstractAuthenticationProcessingFilter
// 在 UsernamePasswordAuthenticationFilter 对象创建时的初始值,不过一般
// 该初始值都会被安全配置中的 failureHandler 覆盖
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
  1. SessionManagementFilter
    SessionManagementFilter在针对认证成功时如果执行有关session策略逻辑失败,也会认为是认证失败,从而需要一个AuthenticationFailureHandler
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();

参考文章

Spring Security Web 5.1.2 源码解析 – 框架缺省使用的页面重定向策略

你可能感兴趣的:(spring,Spring,Security,分析)