這期內容當中小編將會給大家帶來有關如何在springMVC中利用 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, 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
存儲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中校驗是否是跨域請求,如果是分兩種進行處理:
拿到處理器后,通過請求頭是否包含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);
}上述就是小編為大家分享的如何在springMVC中利用 cors實現跨域了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。