简单重现Cors问题

启动两个Spring Application, 分别为8080,8081端口.
8080提供API接口, 8081提供页面去访问8080端口的API.

8080端口的API


@RestController
public class Controller {
    @GetMapping("/hello")
    //@CrossOrigin
    public String index(){
        return "8080";
    }

    @PostMapping("/hello")
    //@CrossOrigin
    public String post(){
        return "8080";
    }
}

8081端口的页面

<html>
<head>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</head>
<body>
<div id="app"></div>
<input type="button" onclick="btnClick()" value="get_button">
<input type="button" onclick="btnClick2()" value="post_button">
<script>
    function btnClick() {
        $.get('http://localhost:8080/hello', function (msg) {
            $("#app").html(msg);
        });
    }

    function btnClick2() {
        $.post('http://localhost:8080/hello', function (msg) {
            $("#app").html(msg);
        });
    }
</script>
</body>
</html>

直接访问即可出现has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.错误.

解决方法很简单, 只需加上CrossOrigin注解.

#源码阅读

Annotation for permitting cross-origin requests on specific handler classes and/or handler methods. Processed if an appropriate {@code HandlerMapping} is configured. 可以用在类和方法上, 如果配置正确就会处理.
Both Spring Web MVC and Spring WebFlux support this annotation through the {@code RequestMappingHandlerMapping} in their respective modules.The values from each type and method level pair of annotations are added to annotations are added to a CorsConfiguration and then default values are applied via CorsConfiguration applyPermitDefaultValues.
Web/WebFlux都可以使用该注解, 注解中的值会被添加到CorsConfiguration中, 并被设为默认值.

注解的调用位置: RequestMappingHandlerMapping.class 的 initCorsConfiguration方法

@Override
	protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
		HandlerMethod handlerMethod = createHandlerMethod(handler, method);
		Class<?> beanType = handlerMethod.getBeanType();
		CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, 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);

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

配置完成, 在DefaultCorsProcessor方法会往response中增加header

@Override
	@SuppressWarnings("resource")
	public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
			HttpServletResponse response) throws IOException {

		Collection<String> varyHeaders = response.getHeaders(HttpHeaders.VARY);
		if (!varyHeaders.contains(HttpHeaders.ORIGIN)) {
			response.addHeader(HttpHeaders.VARY, HttpHeaders.ORIGIN);
		}
		if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD)) {
			response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD);
		}
		if (!varyHeaders.contains(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS)) {
			response.addHeader(HttpHeaders.VARY, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
		}