Spring @CrossOrigin 注解原理实现

 更新时间:2020年07月08日 11:54:46   作者:暗中观察  
这篇文章主要介绍了Spring @CrossOrigin 注解原理实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

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

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

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

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

到此这篇关于Spring @CrossOrigin 注解原理实现的文章就介绍到这了,更多相关Spring @CrossOrigin 注解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现顺序栈的示例代码

    Java实现顺序栈的示例代码

    线性表和栈都是我们常用的数据结构,栈可以看成一种特殊状态的线性表。线性表分为顺序表和链表,使用线性表中的顺序表来实现栈时这种栈被称为顺序栈。这篇文章总结了如何使用顺序表实现栈,需要的可以参考一下
    2022-11-11
  • Java volatile如何实现禁止指令重排

    Java volatile如何实现禁止指令重排

    这篇文章主要介绍了Java volatile如何实现禁止指令重排,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Dubbo LoadBalance基于权重的随机负载均衡算法提高服务性能

    Dubbo LoadBalance基于权重的随机负载均衡算法提高服务性能

    这篇文章主要为大家介绍了Dubbo LoadBalance基于权重的随机负载均衡算法提高服务性能详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
    2023-10-10
  • Java基础之不简单的数组

    Java基础之不简单的数组

    数组(Array)是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量
    2021-09-09
  • 通过Session案例分析一次性验证码登录

    通过Session案例分析一次性验证码登录

    这篇文章主要介绍了通过Session案例分析一次性验证码登录,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-03-03
  • fasterxml jackson反序列化时对于非静态内部类报错问题及解决

    fasterxml jackson反序列化时对于非静态内部类报错问题及解决

    这篇文章主要介绍了fasterxml jackson反序列化时对于非静态内部类报错问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • Java五子棋单机版源码分享

    Java五子棋单机版源码分享

    这篇文章主要为大家分享了Java五子棋单机版源码,JavaGUI编写单机版五子棋,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • 一文搞懂Java顶层类之Object类的使用

    一文搞懂Java顶层类之Object类的使用

    java.lang.Object类是Java语言中的根类,即所有类的父类。它中描述的所有方法子类都可以使用。本文主要介绍了Object类中toString和equals方法的使用,感兴趣的小伙伴可以了解一下
    2022-11-11
  • java内存模型jvm虚拟机简要分析

    java内存模型jvm虚拟机简要分析

    Java 内存模型的主要目的是定义程序中各种变量的访问规则, 关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节
    2021-09-09
  • 浅谈java8中map的新方法--replace

    浅谈java8中map的新方法--replace

    下面小编就为大家带来一篇浅谈java8中map的新方法--replace。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10

最新评论