Spring 注解面面通 之 @CrossOrigin 处理请求源码解析
@CrossOrigin
源码解析主要分为两个阶段:
① @CrossOrigin
注释的方法扫描注册。
② 请求匹配@CrossOrigin
注释的方法。
本文针对第②
阶段从源码角度进行解析,关于第①
阶段请参照《Spring 注解面面通 之 @CrossOrigin 注册处理方法源码解析》。
请求匹配@CrossOrigin
注释的方法
请求匹配@CrossOrigin
注释的方法流程:
1) DispatcherServlet.doDispatch(...)
、DispatcherServlet.getHandler(...)
、AbstractHandlerMapping.getHandler(...)
方法。
标题中所列方法在《Spring 注解面面通 之 @ModelAttribute 深入源码解析》均有介绍,可以查看进行参照,这里不再详细赘述。
2) AbstractHandlerMethodMapping.getCorsConfiguration(...)
方法。
① 若处理器为CorsConfigurationSource
类型,获取处理器CorsConfiguration
配置作为基础配置。
② 若处理方法为预处理方法,则返回默认配置CorsConfiguration
,其配置均为*
。
③ 若处理方法非预处理方法,根据处理方法查找CorsConfiguration
配置,同时与①
所得配置进行合并。
/*** 获取CorsConfiguration.*/
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {// 若处理器为CorsConfigurationSource类型,直接获取处理器CorsConfiguration配置.CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;// 请求方法是预处理方法.if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;}// 请求方法非预处理方法.else {// 根据处理器查找CorsConfiguration配置.CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);// 合并corsConfig和corsConfigFromMethod.corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);}}return corsConfig;
}
3) AbstractHandlerMapping.getCorsConfiguration(...)
方法。
若处理器为CorsConfigurationSource
类型,获取处理器CorsConfiguration
配置作为基础配置。
/*** 检索给定处理程序的CORS配置.* @param handler 处理器.* @param request 当前请求.* @return 处理程序的CORS配置,或者null(如果没有).*/
@Nullable
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {Object resolvedHandler = handler;if (handler instanceof HandlerExecutionChain) {resolvedHandler = ((HandlerExecutionChain) handler).getHandler();}if (resolvedHandler instanceof CorsConfigurationSource) {return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);}return null;
}
4) AbstractHandlerMethodMapping.MappingRegistry.getCorsConfiguration(...)
方法。
① 从HandlerMethod
解析实际的HandlerMethod
。
② 根据HandlerMethod
从corsLookup
中获取CorsConfiguration
配置。
/*** 返回CORS配置.* 线程安全并发使用.*/
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {// 解析实际的HandlerMethod.HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();// 从corsLookup中获取CorsConfiguration配置.return this.corsLookup.get(original != null ? original : handlerMethod);
}
5) AbstractHandlerMapping.getCorsHandlerExecutionChain(...)
方法。
① 若请求为预处理请求,AbstractHandlerMapping.PreFlightHandler
作为处理器实现。
② 若请求非预处理请求,增加拦截器AbstractHandlerMapping.CorsInterceptor
对请求进行拦截处理。
/*** 为CORS相关处理更新HandlerExecutionChain.* 对于预处理请求,默认实现用一个简单的HttpRequestHandler替换所选的处理程序,* 该处理程序调用配置的setCorsProcessor.* 对于实际的请求,默认实现插入一个HandlerInterceptor,它进行CORS相关的检查并添加CORS头.* @param request 当前请求.* @param chain 处理链.* @param config 适用的CORS配置(可能是null).*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,HandlerExecutionChain chain, @Nullable CorsConfiguration config) {// 是CORS预处理请求.if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);}// 不是CORS预处理请求. else {chain.addInterceptor(new CorsInterceptor(config));}return chain;
}
6) AbstractHandlerMapping.PreFlightHandler
、AbstractHandlerMapping.CorsInterceptor
类。
① 若请求非CORS
请求,则跳过处理逻辑。
② 若响应已包含Access-Control-Allow-Origin
头,则跳过处理逻辑。
③ 若请求与服务同源,则跳过处理逻辑。
④ 当CORS
配置为null
时,若请求为预处理请求,则拒绝请求,否则跳过处理逻辑。
/*** 处理请求.*/
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,HttpServletResponse response) throws IOException {// 非CORS请求,不进行处理.if (!CorsUtils.isCorsRequest(request)) {return true;}// 响应已包含Access-Control-Allow-Origin,不进行处理.ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);if (responseHasCors(serverResponse)) {logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");return true;}// 请求来自同源,不进行处理.ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);if (WebUtils.isSameOrigin(serverRequest)) {logger.debug("Skip CORS processing: request is from same origin");return true;}// 是否预处理请求.boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);if (config == null) {if (preFlightRequest) {rejectRequest(serverResponse);return false;}else {return true;}}// 请求处理方法.return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
}
① 获取请求Origin
头,检查并获取允许的源。
② 针对Vary
头,增加值:Origin
、Access-Control-Request-Method
、Access-Control-Request-Headers
。
③ 允许的源为空,则拒绝请求。
④ 获取请求方法,检查并获取允许的方法。
⑤ 允许的方法为空,则拒绝请求。
⑥ 获取请求头,检查并获取请求的头。
⑦ 若请求为预处理请求,且允许的头为空,拒绝请求。
⑧ 设置Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
、Access-Control-Expose-Headers
、Access-Control-Allow-Credentials
、Access-Control-Max-Age
。
/*** 请求处理方法.*/
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,CorsConfiguration config, boolean preFlightRequest) throws IOException {// 获取请求的Origin头.String requestOrigin = request.getHeaders().getOrigin();// 检查并获取允许源.String allowOrigin = checkOrigin(config, requestOrigin);HttpHeaders responseHeaders = response.getHeaders();// 增加Vary头,其值为:Origin、Access-Control-Request-Method、Access-Control-Request-Headers.responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));// 允许源为空,则拒绝请求.if (allowOrigin == null) {logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");rejectRequest(response);return false;}// 获取请求方法.HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);// 检查并获取允许的方法.List<HttpMethod> allowMethods = checkMethods(config, requestMethod);// 允许方法为空,则拒绝请求.if (allowMethods == null) {logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");rejectRequest(response);return false;}// 获取请求头.List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);// 检查并获取请求的头.List<String> allowHeaders = checkHeaders(config, requestHeaders);// 预处理请求,且允许的头为空,拒绝请求.if (preFlightRequest && allowHeaders == null) {logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");rejectRequest(response);return false;}// 设置允许的源.responseHeaders.setAccessControlAllowOrigin(allowOrigin);// 设置Access-Control-Allow-Methods.if (preFlightRequest) {responseHeaders.setAccessControlAllowMethods(allowMethods);}// 设置Access-Control-Allow-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;
}
总结
只有在了解实现细节的情况下,才能解决那些棘手的问题。随着前后端分离程序变得极为普遍,@CrossOrigin
的应用变得尤为重要。
源码解析基于spring-framework-5.0.5.RELEASE
版本源码。
若文中存在错误和不足,欢迎指正!
Spring 注解面面通 之 @CrossOrigin 处理请求源码解析相关推荐
- Spring 注解面面通 之 @CrossOrigin 注册处理方法源码解析
参照<Spring 注解面面通 之 @RequestMapping 注册处理方法源码解析>,其讲解了@RequestMapping注释的处理方法注册过程,而@CrossOrigin是基 ...
- Spring的Autowired自动装配(XML版本+Annotation版本+源码+解析)
http://moshowgame.iteye.com/blog/1607718 @Autowired自动装配 上面的例子我们用的都是手动装配的,如果DAO-Service一多那就很麻烦了,那么我们需 ...
- spring 注解试事物源码解析
spring 注解试事物源码解析 基于xml注解式事务入口 public class TxNamespaceHandler extends NamespaceHandlerSupport {stati ...
- Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析
我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...
- Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)
我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...
- Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)
在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...
- Spring源码解析之@Component注解的扫描
阅读须知 Spring源码版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文 承接Spring源码解析之context:component-scan标签解析,下面就是扫描的流程: Cl ...
- spring MVC cors跨域实现源码解析
spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就是跨域. sp ...
- Spring AOP源码解析-拦截器链的执行过程
一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...
最新文章
- 微生物组—宏基因组分析专题培训开课啦!10月北京
- 战略资产配置matlab,资产组合有效前沿的解和最优解(MATLAB语言)
- Mysql是否开启binlog日志开启方法
- 不使用注解和使用注解的web-service-dao结构
- timeSetEvent、回调函数、CCriticalSection
- java9 堆外内存_java堆外内存泄漏排查
- c语言链表贪吃蛇教程,编《贪吃蛇》最简单的算法,链表法
- phpexcel.php实际应用,PHP操作excel的一个例子(原创)-PHP教程,PHP应用
- 实验3.3 设计一个用于人事管理的People(人员)类
- nginx源码分析:打开监听套接字的流程
- OSG仿真案例(9)——JY61陀螺仪控制飞机姿态
- Origin申请、安装和激活手记
- Office—OneNote快捷键操作
- mysql 重置密码_mysql忘记密码如何重置密码,以及修改root密码的三种方法
- Xcode 8 size class
- WP Engine开发人员工具的好处
- 一个IT技术人如果转型做自由职业可以做哪些方向?
- 在网上打印双面和单面的资料哪里打印价格便宜
- 金蝶二次开发的常见类型
- 2021-9-23 base64学习
热门文章
- 《QQ西游》,最接近神的一个
- 国家精品在线开放课程_开放的互联网安全课程
- html单选按钮for,HTML如何实现RadioButton单选按钮
- 场效应管理解笔记(N沟道结型场效应管)
- 单片机_CT107D训练平台电路原理图\蓝桥杯训练板\输入输出模块\矩阵按键\蜂鸣器电路\继电器电路\LM386功率放大电路,驱动扬声器
- php中的run(),PHP daddslashes 使用方法介绍
- 打算去上海,不知道会如何?仅仅因为这个理由
- 山西被扣车辆成警察坐骑 违章罚款让车主埋单
- 建立二叉树并实现层序遍历
- ipados怎么把屏幕扩展到电脑