1、AuthenticationEntryPoint接口

1.1、简介

  被ExceptionTranslationFilter用来作为认证方案的入口,即当用户请求处理过程中遇见认证异常时,被异常处理器(ExceptionTranslationFilter)用来开启特定的认证流程。接口定义如下:

public interface AuthenticationEntryPoint {

	void commence(HttpServletRequest request, HttpServletResponse response,
		AuthenticationException authException) throws IOException, ServletException;
	}
}

  在AuthenticationEntryPoint 接口中,只有一个commence()方法,用来开启认证方案。其中,request是遇到了认证异常的用户请求,response 是将要返回给用户的响应,authException 请求过程中遇见的认证异常。

  AuthenticationEntryPoint 实现类,可以修改响应头属性信息或开启新的认证流程。

1.2、类层级结构

spring Security 网址的输错地址给出提示信息并返回首页_自定义

  • HttpStatusEntryPoint 设置特定的响应状态字,并非触发一个真正的认证流程。
  • Http403ForbiddenEntryPoint 设置响应状态字为403,。通常在一个预验证过程,已经得出需要拒绝用户请求的情况下,被用于拒绝用户请求。一般会在AbstractPreAuthenticatedProcessingFilter认证过程中(认证失败时),被调用。
  • LoginUrlAuthenticationEntryPoint 基于登录页面的认证方案入口,通过配置获取loginFormUrl(对应登录地址),当出现认证异常时,会跳转到loginFormUrl对应的登录界面。对应UsernamePasswordAuthenticationFilter过滤器类。
  • BasicAuthenticationEntryPoint 对应标准Http Basic认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Basic realm="xxx"触发标准Http Basic认证流程。对应BasicAuthenticationFilter过滤器。
  • DigestAuthenticationEntryPoint 对应标准Http Digest认证流程的触发动作,向响应写入状态字401和头部WWW-Authenticate:"Digest realm="xxx"触发标准Http Digest认证流程。一般在SecurityEnforcementFilter中使用,对应DigestAuthenticationFilter认证过滤器。
  • DelegatingAuthenticationEntryPoint 代理类,它最终将认证任务委托给所代理的多个AuthenticationEntryPoint对象(entryPoints属性中存储),然后再设置其中一个AuthenticationEntryPoint对象作为默认值(在defaultEntryPoint中存储)。
  • OAuth2AuthenticationEntryPoint 该类是SpringSecurity Oauth2提供的一个实现类。通常可以在Spring Security配置时进行配置。

2、LoginUrlAuthenticationEntryPoint 使用场景

2.1、初始化

  当我们使用SpringSecurity时,通过重写WebSecurityConfigurerAdapter类中的configure(HttpSecurity http)方法实现一些自定义的配置。其中,loginPage()配置是用来配置自定义登录页面的,这个时候就会初始化LoginUrlAuthenticationEntryPoint 对象,即使不自定义登录页面(默认会使用“/login”登录页),也会初始化LoginUrlAuthenticationEntryPoint 对象。

 &esmp;如果使用默认登录页时,在AbstractAuthenticationFilterConfigurer配置类的构造函数中会调用setLoginPage()方法初始化默认登录页,如下所示:

protected AbstractAuthenticationFilterConfigurer() {
	setLoginPage("/login");
}

 &esmp;如果自定义登录页时,则在loginPage()方法中调用setLoginPage()初始化登录页,如下所示:

protected T loginPage(String loginPage) {
	setLoginPage(loginPage);
	updateAuthenticationDefaults();
	this.customLoginPage = true;
	return getSelf();
}

  setLoginPage()方法,默认设置了登录页地址,同时还会初始化一个LoginUrlAuthenticationEntryPoint对象作为认证异常时的认证流程入口,即认证失败时,会从新跳转到该地址进行重写认证。

private void setLoginPage(String loginPage) {
	this.loginPage = loginPage;
	this.authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint(loginPage);
}

  通过上述初始化过程,AbstractAuthenticationFilterConfigurer对象的authenticationEntryPoint属性值,默认就设置成了LoginUrlAuthenticationEntryPoint对象。

  在后续初始化基于表单认证的配置类FormLoginConfigurer时,会注册authenticationEntryPoint对象到SpringSecurity处理流程中:首先,在FormLoginConfigurer类的init(H http)方法中,调用父类的AbstractAuthenticationFilterConfigurer的init(B http)的方法,然后又调用了registerDefaultAuthenticationEntryPoint()方法,这个时候如果异常处理器配置存在,就会为其中的defaultEntryPointMappings属性设置对应的AuthenticationEntryPoint对象,其中defaultEntryPointMappings对象是LinkedHashMap<RequestMatcher, AuthenticationEntryPoint>类型,自此ExceptionHandlingConfigurer配置类的defaultEntryPointMappings属性就完成了初始化。

  在后续初始化ExceptionHandlingConfigurer配置类时,首先会执行configure(H http)方法进行初始化,在该方法中又通过getAuthenticationEntryPoint()获取对应的AuthenticationEntryPoint对象,实现如下:

AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
	AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
	if (entryPoint == null) {
		entryPoint = createDefaultEntryPoint(http);
	}
	return entryPoint;
}

private AuthenticationEntryPoint createDefaultEntryPoint(H http) {
	if (this.defaultEntryPointMappings.isEmpty()) {
		return new Http403ForbiddenEntryPoint();
	}
	if (this.defaultEntryPointMappings.size() == 1) {
		return this.defaultEntryPointMappings.values().iterator().next();
	}
	DelegatingAuthenticationEntryPoint entryPoint = new DelegatingAuthenticationEntryPoint(
			this.defaultEntryPointMappings);
	entryPoint.setDefaultEntryPoint(this.defaultEntryPointMappings.values().iterator()
			.next());
	return entryPoint;
}

 &esmp;在getAuthenticationEntryPoint()方法中,首先判断authenticationEntryPoint属性是否有值(一般是用户为异常处理器配置自定义的AuthenticationEntryPoint 对象),如果默认时,该属性为null,所以通过createDefaultEntryPoint()创建默认对象,这时,如果配置了多个AuthenticationEntryPoint 对象(即defaultEntryPointMappings.size() > 1时),如果我们使用了SpringSecurity·配置时,默认只配置了LoginUrlAuthenticationEntryPoint对象,这时就返回了LoginUrlAuthenticationEntryPoint对象,否则就通过DelegatingAuthenticationEntryPoint 对象进行代理,后续再使用时再判断符合条件的AuthenticationEntryPoint对象(如果引入SpringSecurity Oauth2时,默认会加载两个AuthenticationEntryPoint对象)。

 &emspp;获取到了AuthenticationEntryPoint对象后,又回到了configure(H http)方法,这个时候,在创建异常处理过滤器时,就会把该对象赋值到ExceptionTranslationFilter对象的属性中,代码如下:

@Override
public void configure(H http) {
	AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
	ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(
			entryPoint, getRequestCache(http));
	AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
	exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
	exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
	http.addFilter(exceptionTranslationFilter);
}

//ExceptionTranslationFilter类
public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
	RequestCache requestCache) {
	Assert.notNull(authenticationEntryPoint,
			"authenticationEntryPoint cannot be null");
	Assert.notNull(requestCache, "requestCache cannot be null");
	this.authenticationEntryPoint = authenticationEntryPoint;
	this.requestCache = requestCache;
}

  通过前面的配置,我们知道,在初始化阶段,已经完成了为异常处理的过滤器ExceptionTranslationFilter的authenticationEntryPoint 属性设置了异常处理的入口端点。

2.2、使用

  在《未认证的请求是如何重定向到登录地址的》中,我们知道,最终就是通过LoginUrlAuthenticationEntryPoint对象的commence()方法实现了跳转到登录页的功能。我们这里再简单梳理一下其中的逻辑:首先,未认证的地址(需要认证时)经过FilterSecurityInterceptor过滤器的beforeInvocation()方法时,因为没有认证信息,所以抛出了AccessDeniedException 异常,该异常被ExceptionTranslationFilter捕获,然后调用handleSpringSecurityException()处理异常,又调用sendStartAuthentication()方法开启新的认证流程,这个时候就会通过调用LoginUrlAuthenticationEntryPoint的commence()方法实现。

  为什么这里使用的是LoginUrlAuthenticationEntryPoint对象,而不是其他AuthenticationEntryPoint对象呢,这个和我们配置的SpringSecurity是有关系的,而且和我们选择的认证方案也是相关的,在前面的初始化过程中,已经分析了,这里不再重复。

  在LoginUrlAuthenticationEntryPoint的commence()方法中,又提供了forward和redirect两种跳转方式,具体实现如下:

public void commence(HttpServletRequest request, HttpServletResponse response,
	AuthenticationException authException) throws IOException, ServletException {

	String redirectUrl = null;

	if (useForward) {
		if (forceHttps && "http".equals(request.getScheme())) {
			redirectUrl = buildHttpsRedirectUrlForRequest(request);
		}
		if (redirectUrl == null) {
			String loginForm = determineUrlToUseForThisRequest(request, response,
					authException);
			if (logger.isDebugEnabled()) {
				logger.debug("Server side forward to: " + loginForm);
			}
			RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
			dispatcher.forward(request, response);
			return;
		}
	}else {
		redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
	}
	redirectStrategy.sendRedirect(request, response, redirectUrl);
}

  在commence()方法中,如果useForward=true,则使用forward方式进行跳转,通过调用RequestDispatcher 的forward()方式实现请求的转发;当使用redirect方式时,通过调用redirectStrategy的sendRedirect()方法实现转发,其中redirectStrategy对象默认是DefaultRedirectStrategy对象。

  两种跳转方式,都通过determineUrlToUseForThisRequest()方法,获取要跳转的地址,该地址就是用户自定义或系统默认的登录页地址。

3、DelegatingAuthenticationEntryPoint 使用场景

  其实,在实际项目中,DelegatingAuthenticationEntryPoint 用的还是比较多的,不过这个对开发人员来说是透明的,在前面分析初始化过程中时,其中ExceptionHandlingConfigurer配置类创建默认AuthenticationEntryPoint对象时,会根据defaultEntryPointMappings的元素个数来判断是否创建DelegatingAuthenticationEntryPoint 对象,当个数大于两个时,就会创建DelegatingAuthenticationEntryPoint 对象,同时会把defaultEntryPointMappings中的第一个作为默认的AuthenticationEntryPoint对象。

  DelegatingAuthenticationEntryPoint和LoginUrlAuthenticationEntryPoint 处理过程是比较类似的,当defaultEntryPointMappings元素大于1时,就会使用DelegatingAuthenticationEntryPoint作为代理,然后把真正使用具体AuthenticationEntryPoint对象的工作在DelegatingAuthenticationEntryPoint代理对象的commence()方法中进行。首先,进入DelegatingAuthenticationEntryPoint代理对象的commence()方法还是在ExceptionTranslationFilter异常处理过滤器链的sendStartAuthentication()方法中,实现如下:

spring Security 网址的输错地址给出提示信息并返回首页_异常处理_02


  在DelegatingAuthenticationEntryPoint代理对象的commence()方法中,通过entryPoints(其实就是前面提到的defaultEntryPointMappings属性)键值对中的key值来判断符合当前请求的RequestMatcher对象,然后再根据RequestMatcher对象从entryPoints获取对应的AuthenticationEntryPoint对象,如下所示:

spring Security 网址的输错地址给出提示信息并返回首页_异常处理_03