Spring Security SavedRequestAwareAuthenticationSuccessHandler类
SavedRequestAwareAuthenticationSuccessHandler类是SpringSecurity提供的登录成功处理器,登录成功后该处理器会从Session中获取认证之前访问的url,然后将用户重定向到该url地址,
-
设置认证之前的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。
-
重定向到认证前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。
-
自定义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。