首頁 > Java > java教程 > spring MVC cors跨域實現原始碼的範例程式碼解析

spring MVC cors跨域實現原始碼的範例程式碼解析

黄舟
發布: 2017-03-09 10:27:50
原創
1943 人瀏覽過

本文主要介紹了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, //m.sbmmt.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

#從網域設定資訊

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);
 }
登入後複製


以上是spring MVC cors跨域實現原始碼的範例程式碼解析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板