Spring @CrossOrigin 註解原理

現實開發中,咱們不免遇到跨域問題,之前筆者只知道jsonp這種解決方式,後面據說spring只要加入@CrossOrigin便可解決跨域問題。本着好奇的內心,筆者看了下@CrossOrigin 做用原理,寫下這篇博客。java

先說原理:其實很簡單,就是利用spring的攔截器實現往response裏添加 Access-Control-Allow-Origin等響應頭信息,咱們能夠看下spring是怎麼作的spring

注:這裏使用的spring版本爲5.0.6json

咱們能夠先往RequestMappingHandlerMapping 的initCorsConfiguration方法打一個斷點,發現方法調用狀況以下跨域

若是controller在類上標了@CrossOrigin或在方法上標了@CrossOrigin註解,則spring 在記錄mapper映射時會記錄對應跨域請求映射,代碼以下app

RequestMappingHandlerMapping
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		Class<?> beanType = handlerMethod.getBeanType();
        //獲取handler上的CrossOrigin 註解
		CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
       //獲取handler 方法上的CrossOrigin 註解
		CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
        
		if (typeAnnotation == null && methodAnnotation == null) {
            //若是類上和方法都沒標CrossOrigin 註解,則返回一個null
			return null;
		}
        //構建一個CorsConfiguration 並返回
		CorsConfiguration config = new CorsConfiguration();
		updateCorsConfig(config, typeAnnotation);
		updateCorsConfig(config, methodAnnotation);

		if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
			for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
				config.addAllowedMethod(allowedMethod.name());
			}
		}
		return config.applyPermitDefaultValues();
	}

將結果返回到了AbstractHandlerMethodMapping#register,主要代碼以下cors

CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
				if (corsConfig != null) {
//會保存handlerMethod處理跨域請求的配置
					this.corsLookup.put(handlerMethod, corsConfig);
				}

當一個跨域請求過來時,spring在獲取handler時會判斷這個請求是不是一個跨域請求,若是是,則會返回一個能夠處理跨域的handlerjsonp

AbstractHandlerMapping#getHandler	
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
		//若是是一個跨域請求
if (CorsUtils.isCorsRequest(request)) {
        //拿到跨域的全局配置
			CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
          //拿到hander的跨域配置
			CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
			CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
           //處理跨域(即往響應頭添加Access-Control-Allow-Origin信息等),並返回對應的handler對象
			executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
		}

咱們能夠看下如何斷定一個請求是一個跨域請求,this

public static boolean isCorsRequest(HttpServletRequest request) {
//斷定請求頭是否有Origin 屬性便可
		return (request.getHeader(HttpHeaders.ORIGIN) != null);
	}

再看下getCorsHandlerExecutionChain 是如何獲取一個handlerdebug

protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
			HandlerExecutionChain chain, @Nullable 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;
	}

也就是在調用目標方法前會先調用CorsInterceptor#preHandle,咱們觀察獲得其也是調用了corsProcessor.processRequest方法,咱們往這裏打個斷點code

processRequest方法的主要邏輯以下

public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
			HttpServletResponse response) throws IOException {
       //....
       //調用了自身的handleInternal方法
		return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
	}


protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,
			CorsConfiguration config, boolean preFlightRequest) throws IOException {

		String requestOrigin = request.getHeaders().getOrigin();
		String allowOrigin = checkOrigin(config, requestOrigin);
		HttpHeaders responseHeaders = response.getHeaders();

		responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,
				HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));

		if (allowOrigin == null) {
			logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");
			rejectRequest(response);
			return false;
		}

		HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);
		List<HttpMethod> allowMethods = checkMethods(config, requestMethod);
		if (allowMethods == null) {
			logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");
			rejectRequest(response);
			return false;
		}

		List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);
		List<String> allowHeaders = checkHeaders(config, requestHeaders);
		if (preFlightRequest && allowHeaders == null) {
			logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");
			rejectRequest(response);
			return false;
		}
        //設置響應頭
		responseHeaders.setAccessControlAllowOrigin(allowOrigin);

		if (preFlightRequest) {
			responseHeaders.setAccessControlAllowMethods(allowMethods);
		}

		if (preFlightRequest && !allowHeaders.isEmpty()) {
			responseHeaders.setAccessControlAllowHeaders(allowHeaders);
		}

		if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
			responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
		}

		if (Boolean.TRUE.equals(config.getAllowCredentials())) {
			responseHeaders.setAccessControlAllowCredentials(true);
		}

		if (preFlightRequest && config.getMaxAge() != null) {
			responseHeaders.setAccessControlMaxAge(config.getMaxAge());
		}
        //刷新
		response.flush();
		return true;
	}

至此@CrossOrigin的使命就完成了,說白了就是用攔截器給response添加響應頭信息而已

相關文章
相關標籤/搜索