目录
零、上篇回顾
一、SpringMVC执行流程
让我们先来回顾一下SpringMVC执行流程,其中CORS
的实现主要在HandlerMapping
实现。
二、HanndlerMapping中如何实现CORS? —— 基于Spring 5.3.9 版本
1. AbstractHandlerMapping
AbstractHandlerMapping类是HanndlerMapping接口的实现类,我们来看看他有哪些CORS相关的属性及方法。
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
public void setCorsProcessor(CorsProcessor corsProcessor) {
Assert.notNull(corsProcessor, "CorsProcessor must not be null");
this.corsProcessor = corsProcessor;
}
public CorsProcessor getCorsProcessor() {
return this.corsProcessor;
}
// 内部类---------------------------------------------------------
private class PreFlightHandler implements HttpRequestHandler, CorsConfigurationSource {
@Nullable
private final CorsConfiguration config;
public PreFlightHandler(@Nullable CorsConfiguration config) {
this.config = config;
}
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
AbstractHandlerMapping.this.corsProcessor.processRequest(this.config, request, response);
}
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
private class CorsInterceptor implements HandlerInterceptor, CorsConfigurationSource {
@Nullable
private final CorsConfiguration config;
public CorsInterceptor(@Nullable CorsConfiguration config) {
this.config = config;
}
// ----------------核心方法2----------------
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
return asyncManager.hasConcurrentResult() ? true : AbstractHandlerMapping.this.corsProcessor.processRequest(this.config, request, response);
}
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
return this.config;
}
}
// ----------------核心方法1----------------
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
return new HandlerExecutionChain(new AbstractHandlerMapping.PreFlightHandler(config), interceptors);
} else {
chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config));
return chain;
}
}
2. CorsProcessor类
上面我们提到当执行getCorsHandlerExecutionChain方法时,若是CORS请求,则在 拦截器处理链链首 添加CORS拦截器。
chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config));
CorsInterceptor拦截器为内部类,其核心方法为preHandle,主要是调用了this.corsProcessor.processRequest(this.config, request, response)
。
CorsProcessor只是接口,其具体实现类为DefaultCorsProcessor。
DefaultCorsProcessor.processRequest具体实现如下
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {
// 对于CORS请求,需要通过CORS相关Header帮助判断是否直接返回缓存即可
Collection<String> varyHeaders = response.getHeaders("Vary");
if (!varyHeaders.contains("Origin")) {
response.addHeader("Vary", "Origin");
}
if (!varyHeaders.contains("Access-Control-Request-Method")) {
response.addHeader("Vary", "Access-Control-Request-Method");
}
if (!varyHeaders.contains("Access-Control-Request-Headers")) {
response.addHeader("Vary", "Access-Control-Request-Headers");
}
// 这部分检测如果不是CORS请求,则直接返回true
if (!CorsUtils.isCorsRequest(request)) {
return true;
} else if (response.getHeader("Access-Control-Allow-Origin") != null) {
// 如果已经存相关HEADER,代表前面已经有Filter/Inteceptor进行了CORS相关设置
logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
return true;
} else {
// 判断是否为CORS预检请求——OPTIONS方法
boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
if (config == null) {
if (preFlightRequest) {
this.rejectRequest(new ServletServerHttpResponse(response));
return false;
} else {
return true;
}
} else {
// 处理逻辑核心,请看具体实现
return this.handleInternal(new ServletServerHttpRequest(request), new ServletServerHttpResponse(response), config, preFlightRequest);
}
}
}
// handleInternal具体实现
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response, CorsConfiguration config, boolean preFlightRequest) throws IOException {
String requestOrigin = request.getHeaders().getOrigin();
String allowOrigin = this.checkOrigin(config, requestOrigin); // 判断是否匹配Origin,不匹配则返回null
HttpHeaders responseHeaders = response.getHeaders();
if (allowOrigin == null) { // 这里代表未成功匹配Origin,拒绝请求
logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
this.rejectRequest(response);
return false;
} else {
HttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);
List<HttpMethod> allowMethods = this.checkMethods(config, requestMethod);
if (allowMethods == null) {
logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
this.rejectRequest(response);
return false;
} else {
// -----------------!!! 真正添加CORS 相关 Header 参数的位置 !!!-----------------
List<String> requestHeaders = this.getHeadersToUse(request, preFlightRequest);
List<String> allowHeaders = this.checkHeaders(config, requestHeaders);
if (preFlightRequest && allowHeaders == null) {
logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
this.rejectRequest(response);
return false;
} else {
// -----------------!!! 设置Access-Control-Allow-Origin !!!-----------------
responseHeaders.setAccessControlAllowOrigin(allowOrigin);
if (preFlightRequest) {
// -----------------!!! 设置Access-Control-Allow-Methods !!!-----------------
responseHeaders.setAccessControlAllowMethods(allowMethods);
}
// -----------------!!! 设置Access-Control-Expose-Headers !!!-----------------
if (preFlightRequest && !allowHeaders.isEmpty()) {
responseHeaders.setAccessControlAllowHeaders(allowHeaders);
}
// -----------------!!! 设置Access-Control-Expose-Headers !!!-----------------
if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
}
// -----------------!!! 设置Access-Control-Allow-Credentials !!!-----------------
if (Boolean.TRUE.equals(config.getAllowCredentials())) {
responseHeaders.setAccessControlAllowCredentials(true);
}
// -----------------!!! 设置Access-Control-Max-Age !!!-----------------
if (preFlightRequest && config.getMaxAge() != null) {
responseHeaders.setAccessControlMaxAge(config.getMaxAge());
}
response.flush();
return true;
}
}
}
}
补充知识: HTTP Header 中 Vary字段作用
Vary HTTP 响应头决定如何满足未来的请求头,以决定一个缓存的响应是否可以使用,而不是请求从源服务器一个新的一个。
Vary的意义在于告诉代理服务器/缓存/CDN,如何判断是否直接返回缓存资源即可。比如Vary中有User-Agent,那么即使相同的请求,如果用户使用IE打开了一个页面,再用Fir> efox打开这个页面的时候,CDN/代理会认为是不同的页面,如果Vary中没有User-Agent,那么CDN/代理会认为是相同的页面,直接给用户返回缓存的页面,而不会再去web服务器请求相应的页面。
三、思考
- Spring Web 5.3.9版本CORS是通过CorsInterceptor实现的,CorsInterceptor实现了HandlerInterceptor接口,因此可知其实际是通过拦截器实现的。那么在Spring Web中,Filter和InterceptorInterceptor的区别是什么?
1.过滤器:
依赖于servlet容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,获取我们想要获取的数据.
比如:在过滤器中修改字符编码;在过滤器中修改 HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等
2.拦截器:
依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上基于Java的反射机制,属于面向切面编程(AOP)的一种运用。由于拦截器是基于web框架的调用.
因此可以使用spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
- Filter和Interceptor执行顺序?Interceptor链先执行还是Filter链先执行?
首先,Filter是基于Servlet的,通过即< url-pattern >/hello< /url-pattern >标签产生关联,其他相关标签是< serlvet-mapping >和< filter-mapping标签 >。
<servlet-mapping>
<servlet-name>servlet</servlet-name>
<url-pattern>/hello</url-pattern>
<url-pattern>/world</url-pattern>
<url-pattern>/home1</url-pattern>
</servlet-mapping>
<filter-mapping>
<filter-name>filter</filter-name>
<url-pattern>/hello</url-pattern>
<url-pattern>/home2</url-pattern>
</filter-mapping>
而Interceptor是基于MVC的,相关xml配置。
<!-- 拦截器 -->
<mvc:interceptors>
<!-- 对所有请求都拦截,公共拦截器可以有多个 -->
<bean name="baseInterceptor" class="com.scorpios.interceptor.BaseInterceptor" />
<mvc:interceptor>
<!-- 对/test.html进行拦截 -->
<mvc:mapping path="/test.html"/>
<!-- 特定请求的拦截器只能有一个 -->
<bean class="com.scorpios.interceptor.TestInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
Filter的执行是有先后顺序的,根据在web.xml中配置的先后顺序。其主要有三个方法init、doFilter、destory,我们主要关注doFilter方法,在这个方法中chain.doFilter会将请求及响应发送给下一个Filter,我们在这句调用前执行前置操作及后置操作即可。
public class LoggerFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化参数,在创建Filter时自动调用,当我们需要设置初始化参数的时候,可以写到该方法中");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String contextPath = request.getServletContext().getContextPath();
System.out.println("执行Filter前置操作");
chain.doFilter(request, response);
System.out.println("执行Filter后置操作");
}
@Override
public void destroy() {
System.out.println("在销毁Filter时自动调用");
}
}
最后来点图片帮助理解 (图片来自参考资料的文章中)。
参考资料:
https://blog.csdn.net/chenleixing/article/details/44573495
https://www.jianshu.com/p/1b61e5556521
https://www.jb51.net/article/211240.htm