您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)如何在springMVC中利用 cors實(shí)現(xiàn)跨域,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
名詞解釋:跨域資源共享(Cross-Origin Resource Sharing)
簡(jiǎn)單說(shuō)就是只要協(xié)議、IP、http方法任意一個(gè)不同就是跨域。
spring MVC自4.2開(kāi)始添加了跨域的支持。
跨域具體的定義請(qǐng)移步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中配置
// 簡(jiǎn)單配置,未配置的均使用默認(rèn)值,就是全面放開(kāi) <mvc:cors> <mvc:mapping path="/**" /> </mvc:cors> // 這是一個(gè)全量配置 <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) { // ... } }
涉及概念
涉及的java類:
封裝信息的pojo
CorsConfiguration
存儲(chǔ)request與跨域配置信息的容器
CorsConfigurationSource、UrlBasedCorsConfigurationSource
具體處理類
CorsProcessor、DefaultCorsProcessor
CorsUtils
實(shí)現(xiàn)OncePerRequestFilter接口的Adapter
CorsFilter
校驗(yàn)request是否cors,并封裝對(duì)應(yīng)的Adapter
AbstractHandlerMapping、包括內(nèi)部類PreFlightHandler、CorsInterceptor
讀取CrossOrigin注解信息
AbstractHandlerMethodMapping、RequestMappingHandlerMapping
從xml文件中讀取跨域配置信息
CorsBeanDefinitionParser
跨域注冊(cè)輔助類
MvcNamespaceUtils
debug分析
要看懂代碼我們需要先了解下封裝跨域信息的pojo--CorsConfiguration
這邊是一個(gè)非常簡(jiǎn)單的pojo,除了跨域?qū)?yīng)的幾個(gè)屬性,就只有combine、checkOrigin、checkHttpMethod、checkHeaders。
屬性都是多值組合使用的。
// CorsConfiguration public static final String ALL = "*"; // 允許的請(qǐng)求源 private List<String> allowedOrigins; // 允許的http方法 private List<String> allowedMethods; // 允許的請(qǐng)求頭 private List<String> allowedHeaders; // 返回的響應(yīng)頭 private List<String> exposedHeaders; // 是否允許攜帶cookies private Boolean allowCredentials; // 預(yù)請(qǐng)求的存活有效期 private Long maxAge;
combine是將跨域信息進(jìn)行合并
3個(gè)check方法分別是核對(duì)request中的信息是否包含在允許范圍內(nèi)
配置初始化
在系統(tǒng)啟動(dòng)時(shí)通過(guò)CorsBeanDefinitionParser解析配置文件;
加載RequestMappingHandlerMapping時(shí),通過(guò)InitializingBean的afterProperties的鉤子調(diào)用initCorsConfiguration初始化注解信息;
配置文件初始化
在CorsBeanDefinitionParser類的parse方法中打一個(gè)斷點(diǎn)。
CorsBeanDefinitionParser的調(diào)用棧
通過(guò)代碼可以看到這邊解析
跨域信息的配置可以以path為單位定義多個(gè)映射關(guān)系。
解析時(shí)如果沒(méi)有定義則使用默認(rèn)設(shè)置
// CorsBeanDefinitionParser if (mappings.isEmpty()) { // 最簡(jiǎn)配置時(shí)的默認(rèn)設(shè)置 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 { // 單個(gè)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)); } // ... }
解析完成后,通過(guò)MvcNamespaceUtils.registerCorsConfiguratoions注冊(cè)
這邊走的是spring bean容器管理的統(tǒng)一流程,現(xiàn)在轉(zhuǎn)化為BeanDefinition然后再實(shí)例化。
// 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); // ... 設(shè)置默認(rèn)值 return config; }
跨域請(qǐng)求處理
HandlerMapping在正常處理完查找處理器后,在AbstractHandlerMapping.getHandler中校驗(yàn)是否是跨域請(qǐng)求,如果是分兩種進(jìn)行處理:
拿到處理器后,通過(guò)請(qǐng)求頭是否包含Origin判斷是否跨域,如果是跨域,通過(guò)UrlBasedCorsConfigurationSource獲取跨域配置信息,并委托g(shù)etCorsHandlerExecutionChain處理
UrlBasedCorsConfigurationSource是CorsConfigurationSource的實(shí)現(xiàn),從類名就可以猜出這邊request與CorsConfiguration的映射是基于url的。getCorsConfiguration中提取request中的url后,逐一驗(yàn)證配置是否匹配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); }
通過(guò)請(qǐng)求頭的http方法是否options判斷是否預(yù)請(qǐng)求,如果是使用PreFlightRequest替換處理器;如果是普通請(qǐng)求,添加一個(gè)攔截器CorsInterceptor。
PreFlightRequest是CorsProcessor對(duì)于HttpRequestHandler的一個(gè)適配器。這樣HandlerAdapter直接使用HttpRequestHandlerAdapter處理。
CorsInterceptor 是CorsProcessor對(duì)于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); }
上述就是小編為大家分享的如何在springMVC中利用 cors實(shí)現(xiàn)跨域了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。