SpringSecurity系列 之 认证失败处理流程

1、常见用法

  我们使用SpringSecurity进行配置的时候,有三种方式实现认证失败时的后续处理:其一,通过failureUrl()配置认证失败的重定向路径(Redirect);其二,我们还可以通过failureForwardUrl()配置认证失败的转发路径(Forward),和重定向效果类似,区别主要在于前者是重定向(默认),后者是转发;其三,自定义认证失败处理器,主要通过实现AuthenticationFailureHandler接口实现,其实前面两种方式也是通过实现该接口实现的。

  failureUrl()配置方式:

//SpringSecurity配置类
@Override
protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
              .antMatchers("/error","/goLogin","/doLogin","/401","/static/**").permitAll()
              .anyRequest().authenticated()
              .and()
              .formLogin()
              .loginPage("/goLogin")
              .loginProcessingUrl("/doLogin")
              .failureUrl("/login/error");      
  }

  failureForwardUrl()配置方式,和failureUrl()方法类似:

//SpringSecurity配置类
@Override
protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
              .antMatchers("/error","/goLogin","/doLogin","/401","/static/**").permitAll()
              .anyRequest().authenticated()
              .and()
              .formLogin()
              .loginPage("/goLogin")
              .loginProcessingUrl("/doLogin")
              .failureForwardUrl("/login/error");      
  }

  自定义认证失败处理器,其实就是定义一个实现AuthenticationFailureHandler接口的处理类,然后通过failureHandler()方法进行注册就可以了,实现如下:

//SpringSecurity配置类
@Override
protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
              .antMatchers("/error","/goLogin","/doLogin","/401","/static/**").permitAll()
              .anyRequest().authenticated()
              .and()
              .formLogin()
              .loginPage("/goLogin")
              .loginProcessingUrl("/doLogin")
              .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
						//处理逻辑
                    }
                });
  }

2、AuthenticationFailureHandler接口

  AuthenticationFailureHandler是认证失败后的处理接口,通过实现该接口,可以定义各类复杂的处理方式。默认的行为是,认证失败时,会重定向到登录页面。

  AuthenticationFailureHandler类结构如下:
SpringSecurity系列 之 认证失败处理流程_第1张图片
  AuthenticationFailureHandler接口定义如下,其中onAuthenticationFailure()方法就是约定了认证失败后,将要执行的方法,该方法会在AbstractAuthenticationProcessingFilter类的unsuccessfulAuthentication()方法中调用。

public interface AuthenticationFailureHandler {

	void onAuthenticationFailure(HttpServletRequest request,
			HttpServletResponse response, AuthenticationException exception)
			throws IOException, ServletException;
}

3、failureForwardUrl()方法

  failureForwardUrl()方法和failureUrl()方法是类似的,不过failureForwardUrl()方法使用的是转发技术,由服务端触发跳转的。底层的实现,还是通过实现AuthenticationFailureHandler接口实现,具体实现如下:

//FormLoginConfigurer.java
public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) {
	failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
	return this;
}

//AbstractAuthenticationFilterConfigurer.java,是FormLoginConfigurer的父类
public final T failureHandler(
		AuthenticationFailureHandler authenticationFailureHandler) {
	this.failureUrl = null;
	this.failureHandler = authenticationFailureHandler;
	return getSelf();
}

  通过上述代码,我们知道:failureForwardUrl()方法就是创建了一个ForwardAuthenticationFailureHandler对象,然后把该对象赋值给了failureHandler 变量,而ForwardAuthenticationFailureHandler就是AuthenticationFailureHandler接口的实现类,具体实现如下:

public class ForwardAuthenticationFailureHandler implements AuthenticationFailureHandler {

	private final String forwardUrl;
	
	public ForwardAuthenticationFailureHandler(String forwardUrl) {
		Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl),
				() -> "'" + forwardUrl + "' is not a valid forward URL");
		this.forwardUrl = forwardUrl;
	}
	//通过foward方式实现跳转
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
		request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
		request.getRequestDispatcher(forwardUrl).forward(request, response);
	}
}

4、failureUrl()方法

  failureUrl()方法是通过浏览器重定向,实现认证失败后的跳转的,其实failureUrl()方法也可以实现和failureForwardUrl()方法一模一样的功能,我们一步步进行分析。

  首先,failureUrl()方法的实现,和failureForwardUrl()方法类似,不过这里使用的是SimpleUrlAuthenticationFailureHandler处理器,具体如下:

//AbstractAuthenticationFilterConfigurer.java
public final T failureUrl(String authenticationFailureUrl) {
	T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(
			authenticationFailureUrl));
	this.failureUrl = authenticationFailureUrl;
	return result;
}

  通过上述代码,我们可以清楚的知道使用了SimpleUrlAuthenticationFailureHandler对象作为处理,我们下面分析一下该处理器的onAuthenticationFailure()方法,实现如下:

//SimpleUrlAuthenticationFailureHandler.java
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");

		response.sendError(HttpStatus.UNAUTHORIZED.value(),
			HttpStatus.UNAUTHORIZED.getReasonPhrase());
	}else {
		//保存异常信息
		saveException(request, exception);

		if (forwardToDestination) {
			logger.debug("Forwarding to " + defaultFailureUrl);

			request.getRequestDispatcher(defaultFailureUrl)
					.forward(request, response);
		}else {
			logger.debug("Redirecting to " + defaultFailureUrl);
			redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
		}
	}
}

  SimpleUrlAuthenticationFailureHandler的onAuthenticationFailure()方法其实提供了重定向(默认)和转发两种实现方式,通过forwardToDestination变量进行控制。

5、自定义处理器方式

  自定义处理器方式其实就是认证失败处理流程的最基本的实现方式,前面两种也是SpringSecurity框架基于这种方式提供了两种常用的方案而已。配置方法实现如下:

public final T failureHandler(
		AuthenticationFailureHandler authenticationFailureHandler) {
	this.failureUrl = null;
	this.failureHandler = authenticationFailureHandler;
	return getSelf();
}

  这里也是直接把自定义的处理器直接赋值给了failureHandler 变量,等待后续使用。

6、AuthenticationEntryPointFailureHandler类

  该类主要是为了适配AuthenticationEntryPoint实现,其中onAuthenticationFailure()方法其实就是由AuthenticationEntryPoint实现类的commence()方法实现,具体代码如下:

public class AuthenticationEntryPointFailureHandler implements AuthenticationFailureHandler {

	private final AuthenticationEntryPoint authenticationEntryPoint;

	public AuthenticationEntryPointFailureHandler(AuthenticationEntryPoint authenticationEntryPoint) {
		Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
		this.authenticationEntryPoint = authenticationEntryPoint;
	}

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException exception) throws IOException, ServletException {
		this.authenticationEntryPoint.commence(request, response, exception);
	}
}

7、认证失败处理过程

  如果我们没有配置任何认证失败处理相关内容,当我们输入错误的用户名或密码的时候,我们会发现会重新定向到登录页面,而且路径会添加上"?error"字符,例如"http://localhost:8888/qriver-admin/goLogin?error",为什么会这样呢?我们下面一步步进行分析。

7.1、初始化配置

  在启动项目的时候,如果我们没有进行认证失败处理器的配置,系统会默认为我们配置一个,该方法主要在AbstractAuthenticationFilterConfigurer类的updateAuthenticationDefaults()方法中实现,在该方法中其实定义了loginProcessingUrl、failureHandler和loginPage三类默认配置,具体实现如下:

//AbstractAuthenticationFilterConfigurer.java
protected final void updateAuthenticationDefaults() {
	if (loginProcessingUrl == null) {
		loginProcessingUrl(loginPage);
	}
	if (failureHandler == null) {
		failureUrl(loginPage + "?error");
	}

	final LogoutConfigurer<B> logoutConfigurer = getBuilder().getConfigurer(
			LogoutConfigurer.class);
	if (logoutConfigurer != null && !logoutConfigurer.isCustomLogoutSuccess()) {
		logoutConfigurer.logoutSuccessUrl(loginPage + "?logout");
	}
}

  上述updateAuthenticationDefaults()方法会在init()和loginPage()两个方法中被调用。如果我们通过loginPage()配置了自定义的登录界面,那么就会重定向到我们自定义的页面,否则就会重定向到默认的登录页面。

  然后,updateAuthenticationDefaults()方法又通过调用failureUrl()方法进行配置,这个时候实现了failureUrl 和failureHandler 的初始化,其中failureHandler 处理器实际上就是使用的SimpleUrlAuthenticationFailureHandler对象,实现如下:

//AbstractAuthenticationFilterConfigurer.java
public final T failureUrl(String authenticationFailureUrl) {
	T result = failureHandler(new SimpleUrlAuthenticationFailureHandler(
			authenticationFailureUrl));
	this.failureUrl = authenticationFailureUrl;
	return result;
}

public final T failureHandler(
	AuthenticationFailureHandler authenticationFailureHandler) {
	this.failureUrl = null;
	this.failureHandler = authenticationFailureHandler;
	return getSelf();
}

  而AbstractAuthenticationProcessingFilter对象(实际是UsernamePasswordAuthenticationFilter对象)初始化的时候,又调用AbstractAuthenticationFilterConfigurer类的configure()方法,进而调用了AbstractAuthenticationProcessingFilter对象的setAuthenticationFailureHandler()方法,把上一步中初始化的SimpleUrlAuthenticationFailureHandler对象赋值给了AbstractAuthenticationProcessingFilter对象的failureHandler 变量,实现如下:

//AbstractAuthenticationFilterConfigurer.java
@Override
public void configure(B http) throws Exception {
	authFilter.setAuthenticationFailureHandler(failureHandler);
}
//AbstractAuthenticationProcessingFilter.java
public void setAuthenticationFailureHandler(
	AuthenticationFailureHandler failureHandler) {
	Assert.notNull(failureHandler, "failureHandler cannot be null");
	this.failureHandler = failureHandler;
}
7.2、处理流程

  前面提到的内容,其实都是在启动项目时,进行初始化的。那么初始化之后,又是如何产生作用的呢?我们下面开始分析认证失败处理器是如何工作的。

  认证失败处理器其实就是在unsuccessfulAuthentication()方法中调用执行的,实现如下:

//AbstractAuthenticationProcessingFilter.java
protected void unsuccessfulAuthentication(HttpServletRequest request,
		HttpServletResponse response, AuthenticationException failed)
		throws IOException, ServletException {
	SecurityContextHolder.clearContext();

	//省略 debug ……
	
	rememberMeServices.loginFail(request, response);

	failureHandler.onAuthenticationFailure(request, response, failed);
}

  在unsuccessfulAuthentication()方法中,首先清除SecurityContextHolder中存储的上下文信息,然后通过rememberMeServices的loginFail()方法处理浏览器缓存信息,最后通过调用failureHandler的onAuthenticationFailure()方法完成认证失败处理器的调用,这里failureHandler对象其实就是SimpleUrlAuthenticationFailureHandler对象。

  首先,我们分析一下rememberMeServices的loginFail()方法,该方法就是处理浏览器缓存的,实现如下:

//AbstractRememberMeServices.java
@Override
public final void loginFail(HttpServletRequest request, HttpServletResponse response) {
	logger.debug("Interactive login attempt was unsuccessful.");
	cancelCookie(request, response);
	onLoginFail(request, response);
}
//空方法,不做任何实现
protected void onLoginFail(HttpServletRequest request, HttpServletResponse response) {
}
//处理浏览器cookie信息
protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) {
	logger.debug("Cancelling cookie");
	Cookie cookie = new Cookie(cookieName, null);
	cookie.setMaxAge(0);
	cookie.setPath(getCookiePath(request));
	if (cookieDomain != null) {
		cookie.setDomain(cookieDomain);
	}
	if (useSecureCookie == null) {
		cookie.setSecure(request.isSecure());
	}
	else {
		cookie.setSecure(useSecureCookie);
	}
	response.addCookie(cookie);
}

  然后,我们分析一下SimpleUrlAuthenticationFailureHandler的onAuthenticationFailure()方法,该方法主要实现认证失败的后续处理,一般是实现页面的跳转或认证失败数据的返回,前面已经分析过该方法,这里不再贴出代码了。

  至此基于SpringSecurity的认证失败处理流程,我们基本上就学习完了,下一节我们将继续学习其他的内容。

你可能感兴趣的:(Spring,Spring,Cloud,SpringSecurity)