SpringSecurity保存登录前的请求页面和跳转回登录前页面的源码分析

SpringSecurity保存登录前的请求页面和跳转回登录前页面的源码分析


保存

ExceptionTranslationFilter在处理未认证信息异常时会使用RequestCache保存登录前请求页面信息。

所以我们就从创建ExceptionTranslationFilterRequestCache开始分析。

ExceptionTranslationFilterRequestCache都是由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
    //...
}
  1. 创建HttpSessionRequestCache,用来保存登录前请求信息
  2. 获取 AuthenticationEntryPoint,用来处理认证异常
  3. 创建ExceptionTranslationFilter,并将RequestCache注入其中
  4. 将创建的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
  1. 如果没有异常则直接跳到下一个Filter

  2. 如果为IOException则抛出异常

  3. 如果都不是,则判断是否为AuthenticationException等异常

  4. 处理异常

  5. 处理异常可分为AuthenticationExceptionAccessDeniedException异常

    这里我们只探究如何保存登录前请求页面的问题,所以只分析处理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
    }
    
    1. 请求开始认证

    2. SecurityContextHolder创建一个SecurityContext,这里会创建一个SecurityContextImpl,代表运行环境

    3. 保存运行环境

    4. 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");
         }
      }
      
      1. 如果请求无法匹配,则返回
      2. 创建需要保存的Request
      3. 将保存的Request保存于Session中
    5. 处理异常

      保存登录信息已经分析完,处理异常就不分析了,一般是跳转到登录

至此,保存登录信息源码分析已完成。


登录

接下来我们将分析如何登录成功跳转回登录前页面

登录成功跳转回登录前页面是由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
    //...
}
  1. 创建默认的SavedRequestAwareAuthenticationSuccessHandler
  2. 把默认的SavedRequestAwareAuthenticationSuccessHandler赋予successHandler
  3. 创建默认的UsernamePasswordAuthenticationFilter作为authFilter
  4. 获取RequestCacheRequestCache的创建在前面已经介绍过了,保存着登录前信息s
  5. 设置认证成功后的处理器SavedRequestAwareAuthenticationSuccessHandler
  6. 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
}
  1. 认证

  2. 认证成功处理(主线)

    分析这个函数

    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
        //...
    }
    
    1. 记住我处理

    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
      }
      
      1. 获取保存的请求,如果为空则直接返回
      2. 获取targetUrlParameter,该参数一般为空,不过也可以通过setTargetUrlParameter()函数自定义
      3. 如果该处理器设置了alwaysUseDefaultTargetUrltargetUrlParameter属性,则移除先前保存的Request,再转发到设定的url。
      4. 如果该request中有session,则清除WebAttributes.AUTHENTICATION_EXCEPTION,如果没有,则不清除
      5. 获取保存请求的url
      6. 重定向到保存请求的url

至此,保存登录前的请求页面和跳转回登录前页面的源码分析就结束了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值