spring MVC cors跨域实现源码解析
名词解释:跨域资源共享(cross-origin resource sharing)
简单说就是只要协议、ip、http方法任意一个不同就是跨域。
spring mvc自4.2开始添加了跨域的支持。
跨域具体的定义请移步mozilla查看
使用案例
spring mvc中跨域使用有3种方式:
在web.xml中配置corsfilter
<filter> <filter-name>cors</filter-name> <filter-class>org.springframework.web.filter.corsfilter</filter-class> </filter> <filter-mapping> <filter-name>cors</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
在xml中配置
// 简单配置,未配置的均使用默认值,就是全面放开 <mvc:cors> <mvc:mapping path="/**" /> </mvc:cors> // 这是一个全量配置 <mvc:cors> <mvc:mapping path="/api/**" allowed-origins="http://domain1.com, http://domain2.com" allowed-methods="get, put" allowed-headers="header1, header2, header3" exposed-headers="header1, header2" allow-credentials="false" max-age="123" /> <mvc:mapping path="/resources/**" allowed-origins="http://domain1.com" /> </mvc:cors>
使用注解
@crossorigin(maxage = 3600) @restcontroller @requestmapping("/account") public class accountcontroller { @crossorigin("http://domain2.com") @requestmapping("/{id}") public account retrieve(@pathvariable long id) { // ... } }
涉及概念
- corsconfiguration 具体封装跨域配置信息的pojo
- corsconfigurationsource request与跨域配置信息映射的容器
- corsprocessor 具体进行跨域操作的类
- 诺干跨域配置信息初始化类
- 诺干跨域使用的adapter
涉及的java类:
封装信息的pojo
corsconfiguration
存储request与跨域配置信息的容器
corsconfigurationsource、urlbasedcorsconfigurationsource
具体处理类
corsprocessor、defaultcorsprocessor
corsutils
实现onceperrequestfilter接口的adapter
corsfilter
校验request是否cors,并封装对应的adapter
abstracthandlermapping、包括内部类preflighthandler、corsinterceptor
读取crossorigin注解信息
abstracthandlermethodmapping、requestmappinghandlermapping
从xml文件中读取跨域配置信息
corsbeandefinitionparser
跨域注册辅助类
mvcnamespaceutils
debug分析
要看懂代码我们需要先了解下封装跨域信息的pojo--corsconfiguration
这边是一个非常简单的pojo,除了跨域对应的几个属性,就只有combine、checkorigin、checkhttpmethod、checkheaders。
属性都是多值组合使用的。
// corsconfiguration public static final string all = "*"; // 允许的请求源 private list<string> allowedorigins; // 允许的http方法 private list<string> allowedmethods; // 允许的请求头 private list<string> allowedheaders; // 返回的响应头 private list<string> exposedheaders; // 是否允许携带cookies private boolean allowcredentials; // 预请求的存活有效期 private long maxage;
combine是将跨域信息进行合并
3个check方法分别是核对request中的信息是否包含在允许范围内
配置初始化
在系统启动时通过corsbeandefinitionparser解析配置文件;
加载requestmappinghandlermapping时,通过initializingbean的afterproperties的钩子调用initcorsconfiguration初始化注解信息;
配置文件初始化
在corsbeandefinitionparser类的parse方法中打一个断点。
corsbeandefinitionparser的调用栈
通过代码可以看到这边解析
跨域信息的配置可以以path为单位定义多个映射关系。
解析时如果没有定义则使用默认设置
// corsbeandefinitionparser if (mappings.isempty()) { // 最简配置时的默认设置 corsconfiguration config = new corsconfiguration(); config.setallowedorigins(default_allowed_origins); config.setallowedmethods(default_allowed_methods); config.setallowedheaders(default_allowed_headers); config.setallowcredentials(default_allow_credentials); config.setmaxage(default_max_age); corsconfigurations.put("/**", config); }else { // 单个mapping的处理 for (element mapping : mappings) { corsconfiguration config = new corsconfiguration(); if (mapping.hasattribute("allowed-origins")) { string[] allowedorigins = stringutils.tokenizetostringarray(mapping.getattribute("allowed-origins"), ","); config.setallowedorigins(arrays.aslist(allowedorigins)); } // ... }
解析完成后,通过mvcnamespaceutils.registercorsconfiguratoions注册
这边走的是spring bean容器管理的统一流程,现在转化为beandefinition然后再实例化。
// mvcnamespaceutils public static runtimebeanreference registercorsconfigurations(map<string, corsconfiguration> corsconfigurations, parsercontext parsercontext, object source) { if (!parsercontext.getregistry().containsbeandefinition(cors_configuration_bean_name)) { rootbeandefinition corsconfigurationsdef = new rootbeandefinition(linkedhashmap.class); corsconfigurationsdef.setsource(source); corsconfigurationsdef.setrole(beandefinition.role_infrastructure); if (corsconfigurations != null) { corsconfigurationsdef.getconstructorargumentvalues().addindexedargumentvalue(0, corsconfigurations); } parsercontext.getreadercontext().getregistry().registerbeandefinition(cors_configuration_bean_name, corsconfigurationsdef); parsercontext.registercomponent(new beancomponentdefinition(corsconfigurationsdef, cors_configuration_bean_name)); } else if (corsconfigurations != null) { beandefinition corsconfigurationsdef = parsercontext.getregistry().getbeandefinition(cors_configuration_bean_name); corsconfigurationsdef.getconstructorargumentvalues().addindexedargumentvalue(0, corsconfigurations); } return new runtimebeanreference(cors_configuration_bean_name); }
注解初始化
在requestmappinghandlermapping的initcorsconfiguration中扫描使用crossorigin注解的方法,并提取信息。
requestmappinghandlermapping_initcorsconfiguration
// requestmappinghandlermapping @override protected corsconfiguration initcorsconfiguration(object handler, method method, requestmappinginfo mappinginfo) { handlermethod handlermethod = createhandlermethod(handler, method); crossorigin typeannotation = annotatedelementutils.findmergedannotation(handlermethod.getbeantype(), crossorigin.class); crossorigin methodannotation = annotatedelementutils.findmergedannotation(method, crossorigin.class); if (typeannotation == null && methodannotation == null) { return null; } corsconfiguration config = new corsconfiguration(); updatecorsconfig(config, typeannotation); updatecorsconfig(config, methodannotation); // ... 设置默认值 return config; }
跨域请求处理
handlermapping在正常处理完查找处理器后,在abstracthandlermapping.gethandler中校验是否是跨域请求,如果是分两种进行处理:
- 如果是预请求,将处理器替换为内部类preflighthandler
- 如果是正常请求,添加corsinterceptor拦截器
拿到处理器后,通过请求头是否包含origin判断是否跨域,如果是跨域,通过urlbasedcorsconfigurationsource获取跨域配置信息,并委托getcorshandlerexecutionchain处理
urlbasedcorsconfigurationsource是corsconfigurationsource的实现,从类名就可以猜出这边request与corsconfiguration的映射是基于url的。getcorsconfiguration中提取request中的url后,逐一验证配置是否匹配url。
// urlbasedcorsconfigurationsource public corsconfiguration getcorsconfiguration(httpservletrequest request) { string lookuppath = this.urlpathhelper.getlookuppathforrequest(request); for(map.entry<string, corsconfiguration> entry : this.corsconfigurations.entryset()) { if (this.pathmatcher.match(entry.getkey(), lookuppath)) { return entry.getvalue(); } } return null; } // abstracthandlermapping public final handlerexecutionchain gethandler(httpservletrequest request) throws exception { object handler = gethandlerinternal(request); // ... handlerexecutionchain executionchain = gethandlerexecutionchain(handler, request); if (corsutils.iscorsrequest(request)) { corsconfiguration globalconfig = this.corsconfigsource.getcorsconfiguration(request); corsconfiguration handlerconfig = getcorsconfiguration(handler, request); corsconfiguration config = (globalconfig != null ? globalconfig.combine(handlerconfig) : handlerconfig); executionchain = getcorshandlerexecutionchain(request, executionchain, config); } return executionchain; } // httpheaders public static final string origin = "origin"; // corsutils public static boolean iscorsrequest(httpservletrequest request) { return (request.getheader(httpheaders.origin) != null); }
通过请求头的http方法是否options判断是否预请求,如果是使用preflightrequest替换处理器;如果是普通请求,添加一个拦截器corsinterceptor。
preflightrequest是corsprocessor对于httprequesthandler的一个适配器。这样handleradapter直接使用httprequesthandleradapter处理。
corsinterceptor 是corsprocessor对于hnalderinterceptoradapter的适配器。
// abstracthandlermapping protected handlerexecutionchain getcorshandlerexecutionchain(httpservletrequest request, handlerexecutionchain chain, corsconfiguration config) { if (corsutils.ispreflightrequest(request)) { handlerinterceptor[] interceptors = chain.getinterceptors(); chain = new handlerexecutionchain(new preflighthandler(config), interceptors); } else { chain.addinterceptor(new corsinterceptor(config)); } return chain; } private class preflighthandler implements httprequesthandler { private final corsconfiguration config; public preflighthandler(corsconfiguration config) { this.config = config; } @override public void handlerequest(httpservletrequest request, httpservletresponse response) throws ioexception { corsprocessor.processrequest(this.config, request, response); } } private class corsinterceptor extends handlerinterceptoradapter { private final corsconfiguration config; public corsinterceptor(corsconfiguration config) { this.config = config; } @override public boolean prehandle(httpservletrequest request, httpservletresponse response, object handler) throws exception { return corsprocessor.processrequest(this.config, request, response); } } // corsutils public static boolean ispreflightrequest(httpservletrequest request) { return (iscorsrequest(request) && request.getmethod().equals(httpmethod.options.name()) && request.getheader(httpheaders.access_control_request_method) != null); }
可以去github查看:
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!