赞
踩
被ExceptionTranslationFilter用来作为认证方案的入口,即当用户请求处理过程中遇见认证异常时,被异常处理器(ExceptionTranslationFilter)用来开启特定的认证流程。接口定义如下:
public interface AuthenticationEntryPoint {
void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException;
}
}
在AuthenticationEntryPoint 接口中,只有一个commence()方法,用来开启认证方案。其中,request是遇到了认证异常的用户请求,response 是将要返回给用户的响应,authException 请求过程中遇见的认证异常。
AuthenticationEntryPoint 实现类,可以修改响应头属性信息或开启新的认证流程。
当我们使用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 属性设置了异常处理的入口端点。
在《未认证的请求是如何重定向到登录地址的》中,我们知道,最终就是通过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()方法,获取要跳转的地址,该地址就是用户自定义或系统默认的登录页地址。
其实,在实际项目中,DelegatingAuthenticationEntryPoint 用的还是比较多的,不过这个对开发人员来说是透明的,在前面分析初始化过程中时,其中ExceptionHandlingConfigurer配置类创建默认AuthenticationEntryPoint对象时,会根据defaultEntryPointMappings的元素个数来判断是否创建DelegatingAuthenticationEntryPoint 对象,当个数大于两个时,就会创建DelegatingAuthenticationEntryPoint 对象,同时会把defaultEntryPointMappings中的第一个作为默认的AuthenticationEntryPoint对象。
DelegatingAuthenticationEntryPoint和LoginUrlAuthenticationEntryPoint 处理过程是比较类似的,当defaultEntryPointMappings元素大于1时,就会使用DelegatingAuthenticationEntryPoint作为代理,然后把真正使用具体AuthenticationEntryPoint对象的工作在DelegatingAuthenticationEntryPoint代理对象的commence()方法中进行。首先,进入DelegatingAuthenticationEntryPoint代理对象的commence()方法还是在ExceptionTranslationFilter异常处理过滤器链的sendStartAuthentication()方法中,实现如下:
在DelegatingAuthenticationEntryPoint代理对象的commence()方法中,通过entryPoints(其实就是前面提到的defaultEntryPointMappings属性)键值对中的key值来判断符合当前请求的RequestMatcher对象,然后再根据RequestMatcher对象从entryPoints获取对应的AuthenticationEntryPoint对象,如下所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。