Maison  >  Article  >  Java  >  Exemple d'analyse de code du code source d'implémentation inter-domaines Spring MVC Cors

Exemple d'analyse de code du code source d'implémentation inter-domaines Spring MVC Cors

黄舟
黄舟original
2017-03-09 10:27:501907parcourir

Cet article présente principalement l'analyse du code source de l'implémentation inter-domaines Spring MVC cors. Il a une très bonne valeur de référence. Jetons-y un coup d'œil avec l'éditeur

Explication des termes : Cross-Origin Resource Sharing (Partage de ressources Cross-Origin)

Pour faire simple. , tant que le protocole IP, toute différence dans la méthode http est inter-domaine.

spring MVC a ajouté la prise en charge de plusieurs domaines depuis la version 4.2.

Pour la définition spécifique de cross-domain, veuillez vous rendre sur Mozilla pour voir

Cas d'utilisation

Il y en a 3 utilisations inter-domaines dans la méthode spring mvc :

Configurer CorsFilter dans web.xml

<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>

Configurer

// 简单配置,未配置的均使用默认值,就是全面放开
<mvc:cors> 
 <mvc:mapping path="/**" /> 
</mvc:cors> 
// 这是一个全量配置
<mvc:cors> 
 <mvc:mapping path="/api/**" 
  allowed-origins="http://domain1.com, http://www.php.cn/" 
  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>

en XML à l'aide d'annotations

@CrossOrigin(maxAge = 3600) 
@RestController 
@RequestMapping("/account") 
public class AccountController { 
 @CrossOrigin("http://domain2.com") 
 @RequestMapping("/{id}") 
 public Account retrieve(@PathVariable Long id) { 
  // ... 
 } 
}

Concepts impliqués

  • Package spécifique à CorsConfiguration Le pojo des informations de configuration inter-domaines

  • Requête CorsConfigurationSource et le conteneur pour mapper les informations de configuration inter-domaines

  • Classe CorsProcessor qui effectue spécifiquement des opérations inter-domaines

  • Classe d'initialisation des informations de configuration inter-domaines Nogan

  • Adaptateur inter-domaines Nogan

Classes Java impliquées :

pojo qui encapsule les informations

CorsConfiguration

qui stocke demande et informations de configuration inter-domaines Conteneur

CorsConfigurationSource, UrlBasedCorsConfigurationSource

Classe de traitement spécifique

CorsProcessor, DefaultCorsProcessor

CorsUtils

implémenter l'adaptateur d'interface OncePerRequestFilter

CorsFilter

vérifier si la requête est cors et encapsuler le Adaptateur correspondant

AbstractHandlerMapping, y compris la classe interne PreFlightHandler, CorsInterceptor

Lire les informations d'annotation CrossOrigin

AbstractHandlerMethodMapping, RequestMappingHandlerMapping

à partir du fichier XML Lire les informations de configuration inter-domaines

CorsBeanDefinitionParser

Classe auxiliaire d'enregistrement inter-domaines

MvcNamespaceUtils

Analyse de débogage

Pour comprendre le code, nous devons d'abord comprendre le pojo qui encapsule les informations inter-domaines - CorsConfiguration

Il s'agit d'un pojo très simple, à l'exception de quelques correspondances entre domaines. Les attributs sont uniquement combine, checkOrigin, checkHttpMethod et checkHeaders.

Les attributs sont utilisés en combinaison avec plusieurs valeurs.

 // 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;

combiner consiste à fusionner des informations inter-domaines

Les trois méthodes de vérification consistent à vérifier si les informations contenues dans la demande sont incluses dans le cadre autorisé Dans le cadre

Initialisation de la configuration

analyse le fichier de configuration via CorsBeanDefinitionParser lorsque le système démarre

lorsque ; chargement de RequestMappingHandlerMapping, via InitializingBean Le hook afterProperties appelle initCorsConfiguration pour initialiser les informations d'annotation

Initialisation du fichier de configuration

Placer un point d'arrêt dans la méthode d'analyse ; de la classe CorsBeanDefinitionParser.

La pile d'appels de CorsBeanDefinitionParser

Vous pouvez voir l'analyse ici à travers le code

Inter-domaine La configuration des informations peut définir plusieurs relations de mappage en unités de chemins.

S'il n'y a pas de définition lors de l'analyse, les paramètres par défaut seront utilisés

// 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));
  }
  // ...
 }

Une fois l'analyse terminée, enregistrez-vous via MvcNamespaceUtils. registerCorsConfiguratoions

Ce que nous suivons ici est le processus unifié de gestion des conteneurs de beans Spring, qui est maintenant converti en BeanDefinition puis instancié.

// 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);
 }

Initialisation de l'annotation

Scanner la méthode annotée avec CrossOrigin dans la initCorsConfiguration de RequestMappingHandlerMapping et extraire les informations .

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;
 }

Traitement des demandes d'origine croisée

Après avoir manipulé normalement le processeur de recherche, HandlerMapping vérifiera s'il s'agit d'une requête inter-domaine dans AbstractHandlerMapping.getHandler. S'il s'agit d'une requête inter-domaine, elle sera traitée de deux manières :

    .
  • S'il s'agit d'une pré-requête, remplacez le processeur par la classe interne PreFlightHandler

  • S'il s'agit d'une requête normale, ajoutez l'intercepteur 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);
 }


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn