保存
ExceptionTranslationFilter在处理未认证信息异常时会使用RequestCache保存登录前请求页面信息。
所以我们就从创建ExceptionTranslationFilter和RequestCache开始分析。
ExceptionTranslationFilter和RequestCache都是由ExceptionHandlingConfigurer创建的,如下:
ExceptionHandlingCofigurer.java
private RequestCache requestCache = new HttpSessionRequestCache();//1
@Override
public void configure(H http) {
//...
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);//2
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http));//3
http.addFilter(exceptionTranslationFilter);//4
//...
}
- 创建HttpSessionRequestCache,用来保存登录前请求信息
- 获取 AuthenticationEntryPoint,用来处理认证异常
- 创建ExceptionTranslationFilter,并将RequestCache注入其中
- 将创建的ExceptionTranslationFilter添加到HttpSecurity中,后将添加到FilterChain中
ExceptionTranslationFilter创建流程分析完成,接下来我们解析它的功能。
我们查看它的doFilter()方法:
ExceptionTranslationFilter
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
}//1
catch (IOException ex) {
throw ex;
}//2
catch (Exception ex) {//3
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (securityException == null) {
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
if (securityException == null) {
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
handleSpringSecurityException(request, response, chain, securityException);//4
}
}
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, RuntimeException exception) throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
}
}//5
如果没有异常则直接跳到下一个Filter中
如果为IOException则抛出异常
如果都不是,则判断是否为AuthenticationException等异常
处理异常
处理异常可分为AuthenticationException和AccessDeniedException异常
这里我们只探究如何保存登录前请求页面的问题,所以只分析处理AuthenticationException的情况。
我们分析handleAuthenticationException方法
FilterSecurityInterceptor.java
private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException exception) throws ServletException, IOException { sendStartAuthentication(request, response, chain, exception);//1 } protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { SecurityContext context = SecurityContextHolder.createEmptyContext();//2 SecurityContextHolder.setContext(context);//3 this.requestCache.saveRequest(request, response);//4 this.authenticationEntryPoint.commence(request, response, reason);//5 }
请求开始认证
SecurityContextHolder创建一个SecurityContext,这里会创建一个SecurityContextImpl,代表运行环境
保存运行环境
requestCache保存登录信息
分析saveRequest方法:
HttpSessionRequestCache.java
@Override public void saveRequest(HttpServletRequest request, HttpServletResponse response) { if (!this.requestMatcher.matches(request)) { return;//1 } DefaultSavedRequest savedRequest = new DefaultSavedRequest(request, this.portResolver);//2 if (this.createSessionAllowed || request.getSession(false) != null) {//3 request.getSession().setAttribute(this.sessionAttrName, savedRequest); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Saved request %s to session", savedRequest.getRedirectUrl())); } } else {//4 this.logger.trace("Did not save request since there's no session and createSessionAllowed is false"); } }
- 如果请求无法匹配,则返回
- 创建需要保存的Request
- 将保存的Request保存于Session中
处理异常
保存登录信息已经分析完,处理异常就不分析了,一般是跳转到登录
至此,保存登录信息源码分析已完成。
登录
接下来我们将分析如何登录成功跳转回登录前页面
登录成功跳转回登录前页面是由SaveRequestAwareAuthenticationSuccessHandler完成的
所以我们从SaveRequestAwareAuthenticationSuccessHandler创建开始分析
UsernamePasswordAuthenticationFilter过滤器负责调用SaveRequestAwareAuthenticationSuccessHandler
FormLoginConfigurer创建了UsernamePasswordAuthenticationFilter过滤器和SaveRequestAwareAuthenticationSuccessHandler
我们从FromLoginConfigurer开始分析
FromLoginConfigure
private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();//1
private AuthenticationSuccessHandler successHandler = this.defaultSuccessHandler;//2
protected AbstractAuthenticationFilterConfigurer(F authenticationFilter, String defaultLoginProcessingUrl) {
this();
this.authFilter = authenticationFilter;
if (defaultLoginProcessingUrl != null) {
loginProcessingUrl(defaultLoginProcessingUrl);
}
}
public FormLoginConfigurer() {//3
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
@Override
public void configure(B http) throws Exception {
//...
RequestCache requestCache = http.getSharedObject(RequestCache.class);//4
if (requestCache != null) {
this.defaultSuccessHandler.setRequestCache(requestCache);
}
this.authFilter.setAuthenticationSuccessHandler(this.successHandler);//5
http.addFilter(filter);//6
//...
}
- 创建默认的SavedRequestAwareAuthenticationSuccessHandler
- 把默认的SavedRequestAwareAuthenticationSuccessHandler赋予successHandler
- 创建默认的UsernamePasswordAuthenticationFilter作为authFilter
- 获取RequestCache,RequestCache的创建在前面已经介绍过了,保存着登录前信息s
- 设置认证成功后的处理器SavedRequestAwareAuthenticationSuccessHandler
- 在HttpSecurity中设置UsernamePasswordAuthenticationFilter,后面会将其设置在FilterChainProxy
我们再分析UsernamePasswordAuthenticationFilter,该类用于认证请求,关于认证方面分析不会太深入
UsernamePasswordAuthenticationFilter.java
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//...1
successfulAuthentication(request, response, chain, authenticationResult);//2
//...3
}
认证
认证成功处理(主线)
分析这个函数
UsernamePasswordAuthenticationFilter.java
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { //... this.rememberMeServices.loginSuccess(request, response, authResult);//1 this.successHandler.onAuthenticationSuccess(request, response, authResult);//2 //... }
记住我处理
认证成功处理(主线)
现在我们开始分析successHandler是怎么处理登录前信息的
SavedRequestAwawareAuthenticationSuccessHandler.java
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { SavedRequest savedRequest = this.requestCache.getRequest(request, response); if (savedRequest == null) { super.onAuthenticationSuccess(request, response, authentication); return; }//1 String targetUrlParameter = getTargetUrlParameter();//2 if (isAlwaysUseDefaultTargetUrl() || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) { this.requestCache.removeRequest(request, response); super.onAuthenticationSuccess(request, response, authentication); return; }//3 clearAuthenticationAttributes(request);//4 // Use the DefaultSavedRequest URL String targetUrl = savedRequest.getRedirectUrl();//5 getRedirectStrategy().sendRedirect(request, response, targetUrl);//6 }
- 获取保存的请求,如果为空则直接返回
- 获取targetUrlParameter,该参数一般为空,不过也可以通过setTargetUrlParameter()函数自定义
- 如果该处理器设置了alwaysUseDefaultTargetUrl和targetUrlParameter属性,则移除先前保存的Request,再转发到设定的url。
- 如果该request中有session,则清除WebAttributes.AUTHENTICATION_EXCEPTION,如果没有,则不清除
- 获取保存请求的url
- 重定向到保存请求的url
至此,保存登录前的请求页面和跳转回登录前页面的源码分析就结束了



