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