SpringSecurity保存登录前的请求页面和跳转回登录前页面的源码分析
保存
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
至此,保存登录前的请求页面和跳转回登录前页面的源码分析就结束了