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, //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>
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!