Spring @CrossOrigin 注解原理实现

作者:暗中观察 时间:2022-09-03 10:40:04 

现实开发中,我们难免遇到跨域问题,以前笔者只知道jsonp这种解决方式,后面听说spring只要加入@CrossOrigin即可解决跨域问题。本着好奇的心里,笔者看了下@CrossOrigin 作用原理,写下这篇博客。

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

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

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

Spring @CrossOrigin 注解原理实现

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


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,主要代码如下


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

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


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);
}

我们可以看下如何判定一个请求是一个跨域请求,


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

再看下getCorsHandlerExecutionChain 是如何获取一个handler


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方法,我们往这里打个断点

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添加响应头信息而已

来源:https://my.oschina.net/u/3574106/blog/3022246

标签:Spring,@CrossOrigin,注解
0
投稿

猜你喜欢

  • Mybatis逆向工程运行代码实例

    2021-06-22 04:54:59
  • C#实现简单的计算器功能

    2021-05-31 01:52:46
  • @SpringBootTest 注解报红问题及解决

    2022-08-14 23:24:51
  • 如何使用SpringSecurity保护程序安全

    2022-09-08 19:57:50
  • 为什么不要使用 async void的原因分析

    2023-11-24 21:10:27
  • Unity使用鼠标旋转物体效果

    2021-10-17 05:08:12
  • springboot 实战:异常与重定向问题

    2022-03-06 15:44:54
  • 多线程(多窗口卖票实例讲解)

    2021-09-02 02:47:26
  • java连接SQL Server数据库的方法

    2022-10-14 04:16:56
  • 利用java制作简单的音乐播放器

    2022-01-21 11:39:32
  • java实现163邮箱发送邮件到qq邮箱成功案例

    2023-09-18 02:38:09
  • Java ConcurrentHashMap用法案例详解

    2023-08-30 02:01:41
  • SpringBoot 返回Json实体类属性大小写的解决

    2023-08-05 12:30:49
  • 浅谈java中OO的概念和设计原则(必看)

    2023-11-24 13:09:56
  • SpringBoot打Jar包在命令行运行流程详解

    2023-11-24 16:53:59
  • Java 判断实体对象及所有属性是否为空的操作

    2022-12-06 14:32:07
  • 如何在IDE部署springboot项目(有swagger和无swagger都是一样的)到服务器或者虚拟机上的docker

    2023-09-01 00:33:25
  • 实例讲解Java读取一般文本文件和word文档的方法

    2023-11-13 05:09:53
  • java8 stream中Collectors.toMap空指针问题及解决

    2023-01-16 13:05:28
  • java springmvc实现验证码功能

    2022-09-07 12:12:41
  • asp之家 软件编程 m.aspxhome.com