前言
- 想象一个场景:
- 当我们想要在某个论坛访问某个帖子的时候,因为没有认证,此时就会跳转到登录页
- 然后进行认证,认证完成后就会自动跳转到那个帖子中
- 而这个功能正是基于RequestCacheAwareFilter来实现了
1. RequestCacheConfigurer
- RequestCacheConfigurer是RequestCacheAwareFilter对应的配置类,也正是默认开启的配置类之一

1.1 requestCache(...)
- RequestCacheConfigurer中可供用户调用的只有一个requestCache(...)
public RequestCacheConfigurer<H> requestCache(RequestCache requestCache) {
getBuilder().setSharedObject(RequestCache.class, requestCache);
return this;
}
- 可以看出就是往SharedObject中注册一个RequestCache
- RequestCache:在身份认证发生后,缓存当前请求以供后续使用
public interface RequestCache {
void saveRequest(HttpServletRequest request, HttpServletResponse response);
SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response);
HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response);
void removeRequest(HttpServletRequest request, HttpServletResponse response);
}
- RequestCache有两个实现,区别就在于将原请求信息保存在哪:
- CookieRequestCache:
- HttpSessionRequestCache:
- 简单的看下HttpSessionRequestCache的saveRequest(...)方法
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;
}
DefaultSavedRequest savedRequest = new DefaultSavedRequest(request, this.portResolver);
if (this.createSessionAllowed || request.getSession(false) != null) {
request.getSession().setAttribute(this.sessionAttrName, savedRequest);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Saved request %s to session", savedRequest.getRedirectUrl()));
}
}
else {
this.logger.trace("Did not save request since there's no session and createSessionAllowed is false");
}
}
- 可以看到原请求被保存为DefaultSavedRequest对象
public DefaultSavedRequest(HttpServletRequest request, PortResolver portResolver) {
Assert.notNull(request, "Request required");
Assert.notNull(portResolver, "PortResolver required");
addCookies(request.getCookies());
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
if (HEADER_IF_MODIFIED_SINCE.equalsIgnoreCase(name) || HEADER_IF_NONE_MATCH.equalsIgnoreCase(name)) {
continue;
}
Enumeration<String> values = request.getHeaders(name);
while (values.hasMoreElements()) {
this.addHeader(name, values.nextElement());
}
}
addLocales(request.getLocales());
addParameters(request.getParameterMap());
this.method = request.getMethod();
this.pathInfo = request.getPathInfo();
this.queryString = request.getQueryString();
this.requestURI = request.getRequestURI();
this.serverPort = portResolver.getServerPort(request);
this.requestURL = request.getRequestURL().toString();
this.scheme = request.getScheme();
this.serverName = request.getServerName();
this.contextPath = request.getContextPath();
this.servletPath = request.getServletPath();
}
1.2 init(...)
- 没什么好说的,就是确保SharedObject中会有一个RequestCache
@Override
public void init(H http) {
http.setSharedObject(RequestCache.class, getRequestCache(http));
}
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if (result != null) {
return result;
}
result = getBeanOrNull(RequestCache.class);
if (result != null) {
return result;
}
HttpSessionRequestCache defaultCache = new HttpSessionRequestCache();
defaultCache.setRequestMatcher(createDefaultSavedRequestMatcher(http));
return defaultCache;
}
- 但是这里有一个重点就是有哪些请求才需要被保存呢,这就需要注册指定的请求匹配器(RequestMatcher)
- 下图就是RequestCacheConfigurer中默认注册的请求匹配器了
private RequestMatcher createDefaultSavedRequestMatcher(H http) {
RequestMatcher notFavIcon = new NegatedRequestMatcher(new AntPathRequestMatcher("/**/favicon.*"));
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null;
List<RequestMatcher> matchers = new ArrayList<>();
if (isCsrfEnabled) {
RequestMatcher getRequests = new AntPathRequestMatcher("/**", "GET");
matchers.add(0, getRequests);
}
matchers.add(notFavIcon);
matchers.add(notMatchingMediaType(http, MediaType.APPLICATION_JSON));
matchers.add(notXRequestedWith);
matchers.add(notMatchingMediaType(http, MediaType.MULTIPART_FORM_DATA));
matchers.add(notMatchingMediaType(http, MediaType.TEXT_EVENT_STREAM));
return new AndRequestMatcher(matchers);
}
- 要注意的是这里注册的是一个AndRequestMatcher,也就说请求需要满足内部的所有RequestMatcher才算匹配成功
1.3 configure(...)
2. ExceptionTranslationFilter
- 前面说了RequestCacheAwareFilter是当用户未认证的情况下,将原请求保存起来供后续认证完成后使用的
- 所以说就一定有一个地方是判断认证失败了的,然后将请求保存起来的,这个地方就是ExceptionTranslationFilter
- ExceptionTranslationFilter:是专门用于处理FilterSecurityInterceptor抛出的两大异常
- 认证异常:AuthenticationException
- 访问被拒绝异常:AccessDeniedException
- 在对应配置类的configure(...)方法中从SharedObject中获得了RequestCache并将其保存在ExceptionTranslationFilter中
@Override
public void configure(H http) {
...
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint,
getRequestCache(http));
...
}
private RequestCache getRequestCache(H http) {
RequestCache result = http.getSharedObject(RequestCache.class);
if (result != null) {
return result;
}
return new HttpSessionRequestCache();
}
- 而在ExceptionTranslationFilter的sendStartAuthentication(...)方法中,就将原请求保存起来了
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(context);
this.requestCache.saveRequest(request, response);
this.authenticationEntryPoint.commence(request, response, reason);
}
3 RequestCacheAwareFilter
- 此过滤器的源码较少, 直接看doFilter(...)方法
- 是直接包装当前请求,转换为认证前的请求
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest wrappedSavedRequest = this.requestCache.getMatchingRequest((HttpServletRequest) request,
(HttpServletResponse) response);
chain.doFilter((wrappedSavedRequest != null) ? wrappedSavedRequest : request, response);
}
- 最后再讲讲在SpringSecurity中RequestCache机制的完整逻辑
- 用户访问/A接口,没有对应的权限就会被ExceptionTranslationFilter包装当前请求
- 然后会被身份认证入口点(AuthenticationEntryPoint)重定向到登录页
- 用户在登录页输入用户名和密码向服务器发起认证请求
- 紧接着在UsernamePasswordAuthenticationFilter中完成认证后,到达RequestCacheAwareFilter,并将当前请求包装为原请求也就是/A请求
- 注意此时的请求路径已经由/login变为了/A,而此时就会进入SpringMVC的DispatcherServlet中
- 然后我们看专门处理@RequestMapping的RequestMappingHandlerMapping中是如何获取处理方法的
