热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

crossorigin注解添加了解决不了跨域问题_CORS与@CrossOrigin详解

1、跨域的基本概念a、跨域的解释要了解跨域,首先需要知晓浏览器的同源策略,简单的说就是两个请求协议、端口、主机都相同,则两个请求具有相同的
d30b622f7f1b932b4b4e003a82229eaf.png

1、跨域的基本概念

a、跨域的解释

要了解跨域,首先需要知晓浏览器的同源策略,简单的说就是两个请求协议、端口、主机都相同,则两个请求具有相同的源,可以自由访问。下表以http://store.company.com/dir/page.html为例,进行一些 url 的同源检测:

837880abc306872061b4bcc1751c415f.png

浏览器的同源策略控制了不同源之间的交互,也就出现了跨域的问题,对于为什么引入同源策略,官方给出的解释是 “同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。”浏览器的同源策略在一定程度上能规避一些危险、提高攻击的成本。

b、跨域问题的解决

  • JSONP: 这种解决跨域问题的方式前后端都需要有改动,在此不做介绍。
  • CORS: 一个W3C标准,全程跨域资源共享 (Cross-Origin Resource Sharing),本文也主要讲解该种解决方式。

2、CORS

a、简单请求&非简单请求

浏览器将 CORS 请求分为两类:简单请求和复杂请求,简单请求需要满足以下两种条件:

  1. 请求方式为 HEAD、GET、POST 这三种方式之一
  2. HTTP头信息中开发者添加的信息不超过以下几种:
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type : 只限于三个值 : application/x-www-form-urlencode、multipart/form-data、text/plain

凡是不同时满足上述两个条件的,就属于复杂请求。浏览器对两种请求的处理方式也是不一样的。

b、简单请求

对于简单请求,浏览器直接发出 CORS 请求,具体来说,就是在头信息之中,添加一个 Origin 字段.

a5ed57cfb9ae2b8ad146257d822c0b8f.png
简单请求

上图头信息中, Origin 字段用来说明,本次请求来自来个源,服务器根据这个值决定是否同意这个请求。如果该值在许可范围(即允许跨域访问),服务器就会返回一个正常的 HTTP 回应,会多出几个头信息字段:

54f4b667ed0e7b7312ea0125d389dcc3.png
简单请求 Response

上图中的头信息中,有三个与跨域有关的字段,都以 Access-Control- 开头。

  1. Access-Control-Allow-Origin : 该字段必须的,表示接受该值对应的域名的请求。
  2. Access-Control-Allow-Credentials : 该值是一个布尔值,表示是否允许发送 COOKIE 。默认情况下, COOKIE 不包括在 CORS 请求之中,设置为 true,即表示服务器明确许可, COOKIE 可以包含中跨域请求中,一起发送给服务器。这个值也只能设置为 true ,如果服务器不要浏览器发送 COOKIE,删除该字段即可。需要注意的是,当前端通过设置 xhr.withCredentials = true 允许携带COOKIE信息时, Access-Control-Allow-Origin 就不能设置为 * ,必须指定明确的、与请求网页一致的域名。同时,COOKIE依然遵循同源策略,只有用服务器域名设置的COOKIE才会上传,其他域名的COOKIE并不会上传。
  3. Access-Control-Expose-Headers : 该字段可选。 CORS 请求时, XMLHttpRequest 对象的 getResponseHeader() 字段只能拿到6个基本字段: Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma 。如果想要拿到这6个值之外的值,就必须在 Access-Control-Expose-Headers 指定。

c、非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方式是 PUT、DELETE,或者 Content-Type 字段类型是 application/json。非简单请求的 CORS请求,会在正式通信之前,增加一次 HTTP 查询请求,称为预检请求(preflight)。

ca64ab55db45c3f189386bf0c4d70a17.png
预检请求

预检请求用的请求方法是 OPTIONS。下图是该预检请求对应的正式请求:

42e3aed75c063fc945e9ca7b602c750a.png
预检请求对应正式请求信息

3、基于filter的跨域实现

这种实现方式较为简单,判断允许跨域访问后在 Response 头信息中添加 Access-Control-Allow-Origin、Access-Control-Allow-Method等字段信息。代码如下:

public class CorsFilter extends OncePerRequestFilter {private static Logger log &#61; LoggerFactory.getLogger(CorsFilter.class);private static List whiteList &#61; new ArrayList<>();//跨域白名单static {whiteList.add("http://mall.yjc.jd.com");whiteList.add("http://jshopx.jd.com");whiteList.add("http://mall.yao.jd.com");whiteList.add("http://yao-shop.jd.com");}&#64;Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//请求的地址String originUrl &#61; request.getHeader("origin");//查看是否在白名单里面boolean isAllow &#61; whiteList.contains(originUrl);if (isAllow) {response.setHeader("Access-Control-Allow-Origin", originUrl);response.setHeader("Access-Control-Allow-Credentials", "true");response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");response.setHeader("Access-Control-Max-Age", "1800");//30分钟response.setHeader("Access-Control-Allow-Headers", "x-requested-with, content-type");filterChain.doFilter(request, response);}else {//对于非白名单域名的请求&#xff0c;不予进行访问&#xff0c;不然还会进入到controller方法中执行对应的逻辑return;}}
}

4、&#64;CrossOrigin注解

Spring 从4.2版本后开始支持 &#64;CrossOrigin 注解实现跨域&#xff0c;这在一定程度上简化了我们实现跨域访问的开发成本&#xff0c;在需要跨域访问的方法或者类上加上这个注解便大功告成。但在不知晓其原理的情况下使用该注解跨域出了问题将无从下手解决。以下是 &#64;CrossOrigin 的一些基础知识&#xff0c;部分设计到 Spring 框架加载 Bean 的源码。

1e118450bbcd44caf7a2ac0981c83944.png
&#64;CrossOrigin
  1. String[] origins: 允许来源域名的列表&#xff0c;例如 &#39;www.jd.com&#39;&#xff0c;匹配的域名是跨域预请求 Response 头中的 &#39;Access-Control-Aloow_origin&#39; 字段值。不设置确切值时默认支持所有域名跨域访问。
  2. String[] allowedHeaders: 跨域请求中允许的请求头中的字段类型&#xff0c; 该值对应跨域预请求 Response 头中的 &#39;Access-Control-Allow-Headers&#39; 字段值。 不设置确切值默认支持所有的header字段&#xff08;Cache-Controller、Content-Language、Content-Type、Expires、Last-Modified、Pragma&#xff09;跨域访问。
  3. String[] exposedHeaders: 跨域请求请求头中允许携带的除Cache-Controller、Content-Language、Content-Type、Expires、Last-Modified、Pragma这六个基本字段之外的其他字段信息&#xff0c;对应的是跨域请求 Response 头中的 &#39;Access-control-Expose-Headers&#39;字段值。
  4. RequestMethod[] methods: 跨域HTTP请求中支持的HTTP请求类型&#xff08;GET、POST...&#xff09;&#xff0c;不指定确切值时默认与 Controller 方法中的 methods 字段保持一致。
  5. String allowCredentials: 该值对应的是是跨域请求 Response 头中的 &#39;Access-Control-Allow-Credentials&#39; 字段值。浏览器是否将本域名下的 COOKIE 信息携带至跨域服务器中。默认携带至跨域服务器中&#xff0c;但要实现 COOKIE 共享还需要前端在 AJAX 请求中打开 withCredentials 属性。
  6. long maxAge: 该值对应的是是跨域请求 Response 头中的 &#39;Access-Control-Max-Age&#39; 字段值&#xff0c;表示预检请求响应的缓存持续的最大时间&#xff0c;目的是减少浏览器预检请求/响应交互的数量。默认值1800s。设置了该值后&#xff0c;浏览器将在设置值的时间段内对该跨域请求不再发起预请求。

Spring对支持跨域访问的请求所做的操作&#xff1a;

c7b72e9f97ebff5f4674d8d203d083c6.png
图1、DefaultCorsProcessor#handlerInternal设置response的header
5b8008f47c0413961db009ba57355682.png
图2、DefaultCorsProcessor#processRequest处理请求&#xff0c;判断是否支持跨域

问题: 图2中&#xff0c;判断是否需要往 response 头中 set 相关值的依据是 config &#61;&#61; null&#xff0c;这里不禁好奇&#xff0c;CorsConfiguration 有何作用&#xff1f;在哪里初始化&#xff1f;以下将揭晓。

ec42027cca783d41010e4fdab7a847c7.png
图3、CorsConfiguration

看到这些属性是不是有点眼熟&#xff0c;对的&#xff0c;就是对应 &#64;CrossOrigin 注解的字段信息。

CorsConfiguration 的初始化过程涉及到 Spring 对 bean 的加载过程&#xff0c;以下为一些关键步骤的截图。

6aaf8de3562c08116755e430a6baab1e.png
图4、AbstractAutowireCapableBeanFactory#invokeInitMethods初始化bean
7b4a13f1a181b77892f0cdb78b0fc35d.png
图5、AbstractHandlerMethodMapping#initHandlerMethods 判断bean是否有&#64;Controller等注解
9bcb22b195854fdd942f8a7fa1ae6504.png
图6、关键步骤&#xff0c;AbstractHandlerMethodMapping#register
1564afacb85de12855e72f8dbe7e12b6.png
图7、根据注解信息初始化CorsConfiguration, RequestMappingHandlerMapping#initCorsConfiguration

处理跨域/非跨域请求的过程&#xff1a;

DispatcherServlet#doService() --> DispatcherServlet#doDispatch() --> HttpRequestHandlerAdapter#handle() --> PreFlightHandler#handleRequest() --> DefaultCorsProcessor#processRequest() --> DefaultCorsProcessor#handleInternal()

5、总结&#xff1a;

a、注解方式与过滤器方式适用场景&#xff1a;

过滤器方式适合于大范围的控制跨域&#xff0c;比如某个controller类的所有放大全部支持某个或几个具体的域名跨域访问的情形。而对于细粒度的跨域控制&#xff0c;比如一个 controller 类中 methodA 支持域名 originA 跨域访问&#xff0c; methodB 支持域名 originB 跨域访问的情况&#xff0c;当然过滤器方式也能实现&#xff0c;但适用注解的方式能轻松很多&#xff0c;尤其是上述情况比较多的情形。

b、&#64;CrossOrigin 注解是基于拦截器还是过滤器实现的&#xff1f;

答案是都不是。由图6内容可知&#xff0c;在初始化 bean 的时候有在一个 ConcurrentHashMap 中保存 url 与与其对应的跨域对象 CorsConfiguration &#xff08;该 url 不支持跨域访问时该对象为空&#xff09;&#xff0c;图2 在处理请求的时候会 get url对应的 CorsConfiguration 对象对象不为空的情况下&#xff0c;执行 handleInternal 为 Response 头添加对应的字段信息。这个过程是在 DispatcherServlet 执行 doService() 方法过程中进行的&#xff0c;并不涉及到过滤器或者拦截器。虽然有一个 CorsFilter 的类&#xff0c;但 debug 发现&#xff0c;并不会经过这个过滤器。

c、适用过程中遇到的问题&#xff1a;

在正确配置注解或者添加过滤器的情况下&#xff0c;仍然提示跨域失败。后来排查问题是由于项目未完全前后端分离且登陆拦截器对于需要登陆而未登陆的请求直接重定向到登陆页&#xff0c;这就导致跨域访问一些需要经过登陆拦截器的接口时预请求被重定向至登陆页而跨域失败。处理方式也很简单&#xff0c;放开登陆拦截&#xff0c;在接口中进行判断并返回相应的实体对象。



推荐阅读
  • 本文详细介绍了JSP(Java Server Pages)的九大内置对象及其功能,探讨了JSP与Servlet之间的关系及差异,并提供了实际编码示例。此外,还讨论了网页开发中常见的编码转换问题以及JSP的两种页面跳转方式。 ... [详细]
  • Python + Pytest 接口自动化测试中 Token 关联登录的实现方法
    本文将深入探讨 Python 和 Pytest 在接口自动化测试中如何实现 Token 关联登录,内容详尽、逻辑清晰,旨在帮助读者掌握这一关键技能。 ... [详细]
  • 搭建Jenkins、Ant与TestNG集成环境
    本文详细介绍了如何在Ubuntu 16.04系统上配置Jenkins、Ant和TestNG的集成开发环境,涵盖从安装到配置的具体步骤,并提供了创建Windows Slave节点及项目构建的指南。 ... [详细]
  • 软件工程课堂测试2
    要做一个简单的保存网页界面,首先用jsp写出保存界面,本次界面比较简单,首先是三个提示语,后面是三个输入框,然 ... [详细]
  • 本文将详细介绍通过CAS(Central Authentication Service)实现单点登录的原理和步骤。CAS由耶鲁大学开发,旨在为多应用系统提供统一的身份认证服务。文中不仅涵盖了CAS的基本架构,还提供了具体的配置实例,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 微信小程序中实现位置获取的全面指南
    本文详细介绍了如何在微信小程序中实现地理位置的获取,包括通过微信官方API和腾讯地图API两种方式。文中不仅涵盖了必要的准备工作,如申请开发者密钥、下载并配置SDK等,还提供了处理用户授权及位置信息获取的具体代码示例。 ... [详细]
  • 云函数与数据库API实现增删查改的对比
    本文将深入探讨使用云函数和数据库API实现数据操作(增删查改)的不同方法,通过详细的代码示例帮助读者更好地理解和掌握这些技术。文章不仅提供代码实现,还解释了每种方法的特点和适用场景。 ... [详细]
  • 使用PHP实现网站访客计数器的完整指南
    本文详细介绍了如何利用PHP构建一个简易的网站访客统计系统。通过具体的代码示例和详细的解释,帮助开发者理解和实现这一功能,适用于初学者和有一定经验的开发人员。 ... [详细]
  • 深入解析RDMA中的队列对(Queue Pair)
    本文将详细探讨RDMA架构中的关键组件——队列对(Queue Pair,简称QP),包括其基本概念、硬件与软件实现、QPC的作用、QPN的分配机制以及用户接口和状态机。通过这些内容,读者可以更全面地理解QP在RDMA通信中的重要性和工作原理。 ... [详细]
  • 本文探讨了如何利用HTML5和JavaScript在浏览器中进行本地文件的读取和写入操作,并介绍了获取本地文件路径的方法。HTML5提供了一系列API,使得这些操作变得更加简便和安全。 ... [详细]
  • Django Token 认证详解与 HTTP 401、403 状态码的区别
    本文详细介绍了如何在 Django 中配置和使用 Token 认证,并解释了 HTTP 401 和 HTTP 403 状态码的区别。通过具体的代码示例,帮助开发者理解认证机制及权限控制。 ... [详细]
  • 本文详细探讨了Java中的ClassLoader类加载器的工作原理,包括其如何将class文件加载至JVM中,以及JVM启动时的动态加载策略。文章还介绍了JVM内置的三种类加载器及其工作方式,并解释了类加载器的继承关系和双亲委托机制。 ... [详细]
  • 本文详细介绍了如何正确配置Java环境变量PATH,以确保JDK安装完成后能够正常运行。文章不仅涵盖了基本的环境变量设置步骤,还提供了针对不同操作系统下的具体操作指南。 ... [详细]
  • 请看|间隔时间_Postgresql 主从复制 ... [详细]
  • 本文详细探讨了在服务器上运行的PostgreSQL数据库出现'内存不足'错误的具体情况,并提供了一系列有效的解决策略。通过本文,读者将能够更好地理解这一常见问题及其背后的原理。 ... [详细]
author-avatar
手机用户2502901613
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有