当前位置: 移动技术网 > IT编程>开发语言>Java > spring MVC cors跨域实现源码解析

spring MVC cors跨域实现源码解析

2019年07月22日  | 移动技术网IT编程  | 我要评论

名词解释:跨域资源共享(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查看:

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持移动技术网!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网