Spring Security SavedRequestAwareAuthenticationSuccessHandler类

SavedRequestAwareAuthenticationSuccessHandler类是SpringSecurity提供的登录成功处理器,登录成功后该处理器会从Session中获取认证之前访问的url,然后将用户重定向到该url地址,

  1. 设置认证之前的url

    RequestCache.java

    	/**
    	 * Caches the current request for later retrieval, once authentication has taken
    	 * place. Used by <tt>ExceptionTranslationFilter</tt>.
    	 * @param request the request to be stored
    	 */
    	void saveRequest(HttpServletRequest request, HttpServletResponse response);
    
    	/**
    	 * Returns the saved request, leaving it cached.
    	 * @param request the current request
    	 * @return the saved request which was previously cached, or null if there is none.
    	 */
    	SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response);
    
    	/**
    	 * Returns a wrapper around the saved request, if it matches the current request. The
    	 * saved request should be removed from the cache.
    	 * @param request
    	 * @param response
    	 * @return the wrapped save request, if it matches the original, or null if there is
    	 * no cached request or it doesn't match.
    	 */
    	HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response);
    
    	/**
    	 * Removes the cached request.
    	 * @param request the current request, allowing access to the cache.
    	 */
    	void removeRequest(HttpServletRequest request, HttpServletResponse response);
    

    在spring security中RequestCache有三个默认实现,HttpSessionRequestCache通过session存储前一次请求信息、NullRequestCache一般禁用session的情况下使用,不会存储前一次请求信息、CookieRequestCache使用cookie存储前一次请求信息。

    ExceptionTranslationFilter.java

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    			throws IOException, ServletException {
    		try {
    			chain.doFilter(request, response);
    		}
    		catch (IOException ex) {
    			throw ex;
    		}
    		catch (Exception ex) {
    			// Try to extract a SpringSecurityException from the stacktrace
    			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);
    		}
    	}
    
    protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
    			AuthenticationException reason) throws ServletException, IOException {
    		// SEC-112: Clear the SecurityContextHolder's Authentication, as the
    		// existing Authentication is no longer considered valid
    		SecurityContext context = SecurityContextHolder.createEmptyContext();
    		SecurityContextHolder.setContext(context);
    		this.requestCache.saveRequest(request, response);
    		this.authenticationEntryPoint.commence(request, response, reason);
    	}
    

    ExceptionTranslationFilter在捕获身份认证异常之后会调用requestCache将此次请求存储,以便登录成功后由SavedRequestAwareAuthenticationSuccessHandler调用此次请求信息,并重定向到url。

  2. 重定向到认证前url

    SavedRequestAwareAuthenticationSuccessHandler.java

    private RequestCache requestCache = new HttpSessionRequestCache();
    
    	@Override
    	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;
    		}
    		String targetUrlParameter = getTargetUrlParameter();
    		if (isAlwaysUseDefaultTargetUrl()
    				|| (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
    			this.requestCache.removeRequest(request, response);
    			super.onAuthenticationSuccess(request, response, authentication);
    			return;
    		}
    		clearAuthenticationAttributes(request);
    		// Use the DefaultSavedRequest URL
    		String targetUrl = savedRequest.getRedirectUrl();
    		getRedirectStrategy().sendRedirect(request, response, targetUrl);
    	}
    

    在SavedRequestAwareAuthenticationSuccessHandler源码中有一个RequestCache属性,在登录成功后会调用RequestCache.getRequest获取前一次请求信息然后重定向到该url。

  3. 自定义RequestCache

    public class RedisRequestCache implements RequestCache {
    
    
        static final String SAVED_REQUEST = "SPRING_SECURITY_SAVED_REQUEST";
    
        protected final Log logger = LogFactory.getLog(this.getClass());
    
        private PortResolver portResolver = new PortResolverImpl();
    
        private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
    
        private String sessionAttrName = SAVED_REQUEST;
    
        @Autowired
        private RedisCache redisCache;
    
        /**
         * Stores the current request, provided the configuration properties allow it.
         */
        @Override
        public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
            if (!this.requestMatcher.matches(request)) {
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace(
                            LogMessage.format("Did not save request since it did not match [%s]", this.requestMatcher));
                }
                return;
            }
            String redirectUrl = UrlUtils.buildFullRequestUrl(request);
            // DefaultSavedRequest savedRequest = new DefaultSavedRequest(request, this.portResolver);
            redisCache.setCacheObject(this.sessionAttrName, encodeCookie(redirectUrl));
        }
    
        @Override
        public SavedRequest getRequest(HttpServletRequest currentRequest, HttpServletResponse response) {
            String originalURI = redisCache.getCacheObject(this.sessionAttrName);
            if (StringUtils.isEmpty(originalURI)) {
                return null;
            }
            // var str = JSON.toJSONString(originalURI);
            UriComponents uriComponents = UriComponentsBuilder.fromUriString(decodeCookie(originalURI)).build();
            DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder();
            int port = getPort(uriComponents);
            return builder.setScheme(uriComponents.getScheme()).setServerName(uriComponents.getHost())
                    .setRequestURI(uriComponents.getPath()).setQueryString(uriComponents.getQuery()).setServerPort(port)
                    .setMethod(currentRequest.getMethod()).build();
            // return JSON.parseObject(str, SavedRequest.class);
            // return (SavedRequest) savedRequest;
        }
    
        @Override
        public void removeRequest(HttpServletRequest currentRequest, HttpServletResponse response) {
            redisCache.deleteObject(this.sessionAttrName);
        }
    
        @Override
        public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {
            SavedRequest saved = getRequest(request, response);
            if (saved == null) {
                this.logger.trace("No saved request");
                return null;
            }
            if (!matchesSavedRequest(request, saved)) {
                if (this.logger.isTraceEnabled()) {
                    this.logger.trace(LogMessage.format("Did not match request %s to the saved one %s",
                            UrlUtils.buildRequestUrl(request), saved));
                }
                return null;
            }
            removeRequest(request, response);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Loaded matching saved request %s", saved.getRedirectUrl()));
            }
            return request;
        }
    
        private boolean matchesSavedRequest(HttpServletRequest request, SavedRequest savedRequest) {
            if (savedRequest instanceof DefaultSavedRequest) {
                DefaultSavedRequest defaultSavedRequest = (DefaultSavedRequest) savedRequest;
                return defaultSavedRequest.doesRequestMatch(request, this.portResolver);
            }
            String currentUrl = UrlUtils.buildFullRequestUrl(request);
            return savedRequest.getRedirectUrl().equals(currentUrl);
        }
    
        private int getPort(UriComponents uriComponents) {
            int port = uriComponents.getPort();
            if (port != -1) {
                return port;
            }
            if ("https".equalsIgnoreCase(uriComponents.getScheme())) {
                return 443;
            }
            return 80;
        }
    
        private static String encodeCookie(String cookieValue) {
            return Base64.getEncoder().encodeToString(cookieValue.getBytes());
        }
    
        private static String decodeCookie(String encodedCookieValue) {
            return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
        }
    
        public void setRequestMatcher(RequestMatcher requestMatcher) {
            this.requestMatcher = requestMatcher;
        }
    
        public void setPortResolver(PortResolver portResolver) {
            this.portResolver = portResolver;
        }
    
        public void setSessionAttrName(String sessionAttrName) {
            this.sessionAttrName = sessionAttrName;
        }
    
    }
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
      http.requestCache(httpSecurityRequestCacheConfigurer -> {
        httpSecurityRequestCacheConfigurer.requestCache(redisRequestCache());
      });
    }
    

    自定义登录成功处理器需要手动配置自定义的RequestCache。

posted @ 2022-05-28 23:46  我爱这世间美貌女子  阅读(249)  评论(0编辑  收藏  举报