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 注解内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Java的Spring框架中的注解的用法

    1. 使用Spring注解来注入属性 1.1. 使用注解以前我们是怎样注入属性的 类的实现: class UserManagerImpl implements UserManager { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } ... } 配置文件: <bean id="userManagerImpl" class="com.

  • Spring常用注解汇总

    本文汇总了Spring的常用注解,以方便大家查询和使用,具体如下: 使用注解之前要开启自动扫描功能 其中base-package为需要扫描的包(含子包). <context:component-scan base-package="cn.test"/> @Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean. @Scope注解 作用域 @Lazy(true) 表示延迟初始化 @Service用于

  • 详解spring boot mybatis全注解化

    本文重点给大家介绍spring boot mybatis 注解化的实例代码,具体内容大家参考下本文: pom.xml <!-- 引入mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version

  • 详解SpringBoot AOP 拦截器(Aspect注解方式)

    常用用于实现拦截的有:Filter.HandlerInterceptor.MethodInterceptor 第一种Filter属于Servlet提供的,后两者是spring提供的,HandlerInterceptor属于Spring MVC项目提供的,用来拦截请求,在MethodInterceptor之前执行. 实现一个HandlerInterceptor可以实现接口HandlerInterceptor,也可以继承HandlerInterceptorAdapter类,两种方法一样.这个不在本文

  • Spring @Bean vs @Service注解区别

    今天跟同事讨论了一下在Spring Boot中,是使用@Configuration和@Bean的组合来创建Bean还是直接使用 @Service等注解放在类上的方式.笔者倾向于使用第一种,即@Configuration和@Bean的组合. 先来看一个例子,目标是创建SearchService的一个Bean. 直接使用@Service的方式: // SearchService.java package li.koly.search; import java.util.List; public in

  • 详解spring注解式参数校验

    一般入参我们都会转为vo对象.那么直接在对象的属性上注解即可. 其实spring用的是hibernate的validator. 步骤 1.配置spring.xml <mvc:annotation-driven /> 2.配置自己的validate类. <bean id="validateArgsAOP" class="com.my.validate.aop.ValidateArgsAOP"/> <aop:config> <a

  • spring学习教程之@ModelAttribute注解运用详解

    本文主要给大家介绍了关于java中@ModelAttribute使用的相关资料,分享出来供大家参考学习,下面来一起看看详细的介绍: 一.@ModelAttribute注释方法  例子(1),(2),(3)类似,被@ModelAttribute注释的方法会在此controller每个方法执行前被执行,因此对于一个controller映射多个URL的用法来说,要谨慎使用. (1)@ModelAttribute注释void返回值的方法 @Controller public class HelloWor

  • Spring Boot 基于注解的 Redis 缓存使用详解

    看文本之前,请先确定你看过上一篇文章<Spring Boot Redis 集成配置>并保证 Redis 集成后正常可用,因为本文是基于上文继续增加的代码. 一.创建 Caching 配置类 RedisKeys.Java package com.shanhy.example.redis; import java.util.HashMap; import java.util.Map; import javax.annotation.PostConstruct; import org.springf

  • Spring @CrossOrigin 注解原理实现

    现实开发中,我们难免遇到跨域问题,以前笔者只知道jsonp这种解决方式,后面听说spring只要加入@CrossOrigin即可解决跨域问题.本着好奇的心里,笔者看了下@CrossOrigin 作用原理,写下这篇博客. 先说原理:其实很简单,就是利用spring的拦截器实现往response里添加 Access-Control-Allow-Origin等响应头信息,我们可以看下spring是怎么做的 注:这里使用的spring版本为5.0.6 我们可以先往RequestMappingHandle

  • Spring @Conditional注解原理解析

    这篇文章主要介绍了Spring @Conditional注解原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 @Conditional是Spring4新提供的注解,它的作用是根据某个条件加载特定的bean. 我们需要创建实现类来实现Condition接口,这是Condition的源码 public interface Condition { boolean matches(ConditionContext var1, AnnotatedT

  • spring @Component注解原理解析

    这篇文章主要介绍了spring @Component注解原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.@controller 控制器(注入服务) 2.@service 业务(注入dao) 3.@repository dao(实现dao访问) 4.@component (把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>) 5.@Comp

  • Spring的组合注解和元注解原理与用法详解

    本文实例讲述了Spring的组合注解和元注解原理与用法.分享给大家供大家参考,具体如下: 一 点睛 从Spring 2开始,为了相应JDK 1.5推出的注解功能,Spring开始加入注解来替代xml配置.Spring的注解主要用来配置和注入Bean,以及AOP相关配置.随着注解的大量使用,尤其相同的多个注解用到各个类或方法中,会相当繁琐.出现了所谓的样本代码,这是Spring设计要消除的代码. 元注解:可以注解到别的注解上去的注解. 组合注解:被注解的注解,组合注解具备其上的元注解的功能. Sp

  • 简单实现Spring的IOC原理详解

    控制反转(InversionofControl,缩写为IoC) 简单来说就是当自己需要一个对象的时候不需要自己手动去new一个,而是由其他容器来帮你提供:Spring里面就是IOC容器. 例如: 在Spring里面经常需要在Service这个装配一个Dao,一般是使用@Autowired注解:类似如下 public Class ServiceImpl{ @Autowired Dao dao; public void getData(){ dao.getData(); } 在这里未初始化Dao直接

  • Spring @Transactional工作原理详解

    本文将深入研究Spring的事务管理.主要介绍@Transactional在底层是如何工作的.之后的文章将介绍: propagation(事务传播)和isolation(隔离性)等属性的使用 事务使用的陷阱有哪些以及如何避免 JPA和事务管理 很重要的一点是JPA本身并不提供任何类型的声明式事务管理.如果在依赖注入容器之外使用JPA,事务处理必须由开发人员编程实现. UserTransaction utx = entityManager.getTransaction(); try{ utx.be

  • Spring AOP注解失效的坑及JDK动态代理

    @Transactional @Async等注解不起作用 之前很多人在使用Spring中的@Transactional, @Async等注解时,都多少碰到过注解不起作用的情况. 为什么会出现这些情况呢?因为这些注解的功能实际上都是Spring AOP实现的,而其实现原理是通过代理实现的. JDK动态代理 以一个简单的例子理解一下JDK动态代理的基本原理: //目标类接口 public interface JDKProxyTestService { void run(); } //目标类 publ

  • spring依赖注入原理与用法实例分析

    本文实例讲述了spring依赖注入原理与用法.分享给大家供大家参考,具体如下: 一 点睛 控制反转和依赖注入在Spring环境下是等同的概念,控制反转是通过依赖注入实现的.所谓依赖注入指的是容器负责创建对象和维护对象间的依赖关系,而不是通过对象本身负责自己的创建和解决自己的依赖. 依赖注入的主要目的是为了解耦,体现一种组合的概念.如果你希望你的类具备某项功能的时候,是继承自一个具有次功能的父类好呢?还是组合另外一个具有此功能的类好呢?答案是不言而喻的,继承一个父类,子类和父类耦合了,组合另外一个

  • Spring事务管理原理及方法详解

    这篇文章主要介绍了Spring事务管理原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 事务,在日常开发或者面试中都必定会涉及到.开发工作中,结合数据库开发理解就是:一组dml要么全部成功执行提交,要么因为某一个操作异常,撤销之前所做的成功的操作,整体执行失败.再简单点的一句话:生死与共. 由此,可以看出,事务的必要性:在开发工作中,保证操作数据的安全性.事务的控制也就是保证数据的访问安全性. 一.事务的四大特性 A:原子性(ato

  • Spring事务annotation原理详解

    这篇文章主要介绍了Spring事务annotation原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在使用Spring的时候,配置文件中我们经常看到 annotation-driven 这样的注解,其含义就是支持注解,一般根据前缀 tx.mvc 等也能很直白的理解出来分别的作用. <tx:annotation-driven/> 就是支持事务注解的(@Transactional) . <mvc:annotation-driven

随机推荐