Spring @CrossOrigin注解原理是什么

问题起源

  • 在Postman调用接口中,忘记设置Origin,发现@CrossOrigin未生效(响应头没有cors的)
  • 在filter中设置了Access-Control-Allow-Origin发现@CrossOrigin未生效(响应头没有cors的)

原理分析

先说原理:其实很简单,就是利用spring的拦截器实现往response里添加 Access-Control-Allow-Origin等响应头信息,我们可以看下spring是怎么做的

注:这里使用的spring版本为5.0.6

我们可以先往RequestMappingHandlerMapping 的initCorsConfiguration方法打一个断点,发现方法调用情况如下

如果controller在类上标了@CrossOrigin或在方法上标了@CrossOrigin注解,则spring 在记录mapper映射时会记录对应跨域请求映射,代码如下

 1     protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {
 2         HandlerMethod handlerMethod = createHandlerMethod(handler, method);
 3         Class<?> beanType = handlerMethod.getBeanType();
 4         //获取handler上的CrossOrigin 注解
 5         CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);
 6         //获取handler 方法上的CrossOrigin 注解
 7         CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
 8 
 9         if (typeAnnotation == null && methodAnnotation == null) {
10             //如果类上和方法都没标CrossOrigin 注解,则返回一个null
11             return null;
12         }
13         //构建一个CorsConfiguration 并返回
14         CorsConfiguration config = new CorsConfiguration();
15         updateCorsConfig(config, typeAnnotation);
16         updateCorsConfig(config, methodAnnotation);
17 
18         if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
19             for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
20                 config.addAllowedMethod(allowedMethod.name());
21             }
22         }
23         return config.applyPermitDefaultValues();
24     }

将结果返回到了AbstractHandlerMethodMapping#register,主要代码如下

1     CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
2                 if (corsConfig != null) {
3         //会保存handlerMethod处理跨域请求的配置
4         this.corsLookup.put(handlerMethod, corsConfig);
5     }

当一个跨域请求过来时,spring在获取handler时会判断这个请求是否是一个跨域请求,如果是,则会返回一个可以处理跨域的handler

 1     //AbstractHandlerMapping#getHandler
 2     HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
 3     //如果是一个跨域请求
 4 if (CorsUtils.isCorsRequest(request)) {
 5         //拿到跨域的全局配置
 6         CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
 7         //拿到hander的跨域配置
 8         CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
 9         CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
10         //处理跨域(即往响应头添加Access-Control-Allow-Origin信息等),并返回对应的handler对象
11         executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
12     }

我们可以看下如何判定一个请求是一个跨域请求,(可以知道为什么没有设置origin就没有走跨域了)

1     public static boolean isCorsRequest(HttpServletRequest request) {
2     //判定请求头是否有Origin 属性即可
3         return (request.getHeader(HttpHeaders.ORIGIN) != null);
4     }

那么,CorsConfiguration在什么时候生效呢,为什么提前设置了Access-Control-Allow-Origin,@CrossOrigin就不生效了呢

源码分析

对于CorsConfiguration的解析以及处理是在DefaultCorsProcessor#processRequest中进行的:

 1     public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException {
 2         if (!CorsUtils.isCorsRequest(request)) {
 3             return true;
 4         } else {
 5             ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);
 6             //判断是否已经有Access-Control-Allow-Origin,有的话则不处理跨域配置
 7             if (this.responseHasCors(serverResponse)) {
 8                 logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\"");
 9                 return true;
10             } else {
11                 ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
12                 //判断是否是同源,是同源,则不处理跨域配置
13                 if (WebUtils.isSameOrigin(serverRequest)) {
14                     logger.trace("Skip: request is from same origin");
15                     return true;
16                 } else {
17                     //是否是预检请求
18                     boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);
19                     if (config == null) {
20                         if (preFlightRequest) {
21                             this.rejectRequest(serverResponse);
22                             return false;
23                         } else {
24                             return true;
25                         }
26                     } else {
27                         //处理跨域配置
28                         return this.handleInternal(serverRequest, serverResponse, config, preFlightRequest);
29                     }
30                 }
31             }
32         }
33     }

判断是否已经有Access-Control-Allow-Origin的responseHasCors方法:

1     private boolean responseHasCors(ServerHttpResponse response) {
2         try {
3             return response.getHeaders().getAccessControlAllowOrigin() != null;
4         } catch (NullPointerException var3) {
5             return false;
6         }
7     }

判断是否是同源的请求,看一下WebUtils.isSameOrigin方法:

 判断是预检查请求,看一下isPreFlightRequest方法,主要是判断是否有Origin以及Access-Control-Request-Methods是否存在:

 1 public abstract class CorsUtils {
 2     public CorsUtils() {
 3     }
 4 
 5     public static boolean isCorsRequest(HttpServletRequest request) {
 6         return request.getHeader("Origin") != null;
 7     }
 8 
 9     public static boolean isPreFlightRequest(HttpServletRequest request) {
10         return isCorsRequest(request) && HttpMethod.OPTIONS.matches(request.getMethod()) && request.getHeader("Access-Control-Request-Method") != null;
11     }
12 }

最后,处理整个CORS配置:

 1     protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response, CorsConfiguration config, boolean preFlightRequest) throws IOException {
 2         String requestOrigin = request.getHeaders().getOrigin();
 3         String allowOrigin = this.checkOrigin(config, requestOrigin);
 4         HttpHeaders responseHeaders = response.getHeaders();
 5         responseHeaders.addAll("Vary", Arrays.asList("Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"));
 6         if (allowOrigin == null) {
 7             logger.debug("Reject: '" + requestOrigin + "' origin is not allowed");
 8             this.rejectRequest(response);
 9             return false;
10         } else {
11             HttpMethod requestMethod = this.getMethodToUse(request, preFlightRequest);
12             List<HttpMethod> allowMethods = this.checkMethods(config, requestMethod);
13             if (allowMethods == null) {
14                 logger.debug("Reject: HTTP '" + requestMethod + "' is not allowed");
15                 this.rejectRequest(response);
16                 return false;
17             } else {
18                 List<String> requestHeaders = this.getHeadersToUse(request, preFlightRequest);
19                 List<String> allowHeaders = this.checkHeaders(config, requestHeaders);
20                 if (preFlightRequest && allowHeaders == null) {
21                     logger.debug("Reject: headers '" + requestHeaders + "' are not allowed");
22                     this.rejectRequest(response);
23                     return false;
24                 } else {
25                     responseHeaders.setAccessControlAllowOrigin(allowOrigin);
26                     if (preFlightRequest) {
27                         responseHeaders.setAccessControlAllowMethods(allowMethods);
28                     }
29 
30                     if (preFlightRequest && !allowHeaders.isEmpty()) {
31                         responseHeaders.setAccessControlAllowHeaders(allowHeaders);
32                     }
33 
34                     if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {
35                         responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());
36                     }
37 
38                     if (Boolean.TRUE.equals(config.getAllowCredentials())) {
39                         responseHeaders.setAccessControlAllowCredentials(true);
40                     }
41 
42                     if (preFlightRequest && config.getMaxAge() != null) {
43                         responseHeaders.setAccessControlMaxAge(config.getMaxAge());
44                     }
45 
46                     response.flush();
47                     return true;
48                 }
49             }
50         }
51     }

 

转发请注明出处:https://www.cnblogs.com/fnlingnzb-learner/p/16921420.html

posted @ 2022-11-24 11:57  Boblim  阅读(445)  评论(0编辑  收藏  举报