cors 前后端分离跨域问题_前后端分离之CORS跨域访问踩坑总结

前言

前后端分离的开发模式越来越流行,目前绝大多数的公司与项目都采取这种方式来开发,它的好处是前端可以只专注于页面实现,而后端则主要负责接口开发,前后端分工明确,彼此职责分离,不再高度耦合,但是由于这种开发模式将前后端项目分开来独立部署,所以将必不可免的会碰到跨域问题.

什么是跨域

跨域指的是浏览器不能执行其他网站的脚本.它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制.目前所有的浏览器都实行同源策略,所谓的同源指的是

协议相同

域名相同

端口相同

如何解决

常见的解决方案有JSONP和CORS等多种方式,这里我们采用了CORS的方式来统一处理项目中的跨域问题.

cors是一种w3c标准,全称是"跨域资源共享(Cross-origin resource sharing)".它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了同源使用的限制.下面重点来看下我们在SpringBoot项目中使用cors处理跨域时所遇到的问题.

首先创建一个WebMvcConfig类继承自WebMvcConfigurerAdapter类并覆写其中的addCorsMappings方法

/**

* 允许跨域访问

*

* @param registry

*/

@Override

public void addCorsMappings(CorsRegistry registry) {

registry.addMapping("/**").allowedOrigins("*")

.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")

.allowedHeaders("Accept", "Origin", "X-Requested-With", "Content-Type",

"Last-Modified", "device", "token")

.exposedHeaders("Set-Cookie")

.allowCredentials(true).maxAge(3600);

}

接着我们在之前搭建好的Vue项目环境中创建一个vue文件,用来向springboot项目发起跨域请求.

Message is: {{ message }}

export default {

data () {

return {

message: 'This is my from'

}

},

methods:{

get:function(){

$.ajax({

url:'http://localhost:8080/win/api/test/cors',//测试接口

type:'post',

beforeSend:(xhr) => {

xhr.setRequestHeader("token", "web_session_key-082ba2a3-7d8e-407c-8bd0-2fbc430b0dbf");

xhr.setRequestHeader("device","APP");

},

success:function(data){

console.log(data);

}

});

}

}

}

然后启动前端vue项目

npm run dev

打开浏览器输入http://localhost:8081/点击页面中的测试按钮发起接口调用,注意vue工程的端口为8081,springboot工程的端口为8080,不符合同源规则所以访问后端接口时会出现跨域.此时打开chrome控制台会发现

image.png

通过chrome控制台查看接口请求的headers信息是这样的

image.png

可以看到这个接口的Request Method为OPTIONS,而不是我们在ajax代码中所设置的POST,仔细看控制台报的异常Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.可以发现其中的关键字preflight(预检请求),这个其实就是问题的所在了,再次google了一下cors的相关知识,才知道原来浏览器将cors请求分为两类即简单请求和非简单请求.

简单请求

只要同时满足以下条件就属于简单请求

请求方法是以下三种方法之一:GET POST HEAD

Http的头信息不超出以下几种字段:Accept Accept-Language Content-Language Last-Event-ID Content-Type 只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

非简单请求

其他的请求皆属于非简单请求.

注意在cors定义中,如果头信息中的Content-Type不设置,则默认值为json/application,如果Content-Type值不为application/x-www-form-urlencoded,multipart/form-data或text/plain,都被视为非简单请求,即预检请求.

主要问题描述

浏览器对这两种请求的处理是不一样的.

如果是简单请求的话,一次完整的请求过程是不需要服务端预检的,直接响应回客户端,而非简单请求则浏览器会在发送真正请求之前先用OPTIONS发送一次预检请求preflight request,从而获知服务端是否允许该跨域请求,当服务器确认允许之后,才会发起真正的请求.那么前面所出现的异常很明显就是由这个preflight request导致的了,回头看代码可发现我们在发起ajax调用时往请求头里面塞了两个自定义的header参数token和device,所以此次调用属于非简单请求,并触发了一次预检请求,由于我们服务端使用了shiro权限认证框架,通过拦截用户请求头中传过来的token信息来判定客户端是否为非法调用,而Preflight请求过程中并不会携带我们自定义的token信息到服务器,这样服务器校验就永远也无法通过了,就算是合法的登录用户也会被拦截.

解决的办法

既然已经知道原因了,那么自然解决这个问题的办法就是交给后端了,在后端检测到该请求为预检请求时,不让它继续往下走(也可以返回一个2xx的状态码),直接告诉浏览器此次跨域请求可以继续,很明显过滤器符合我们的要求,我们来把之前的addCorsMappings方法去掉,重写这块代码,在网上查了很久,尝试了很多种方法,得出来的结论大致有如下两种方式:

第一种方案采用过滤器的机制

@Bean

public FilterRegistrationBean corsFilter() {

return new FilterRegistrationBean(new Filter() {

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

String method = request.getMethod();

// this origin value could just as easily have come from a database

response.setHeader("Access-Control-Allow-Origin", "*");

response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS");

response.setHeader("Access-Control-Max-Age", "3600");

response.setHeader("Access-Control-Allow-Credentials", "true");

response.setHeader("Access-Control-Allow-Headers", "Accept, Origin, X-Requested-With, Content-Type,Last-Modified,device,token");

if ("OPTIONS".equals(method)) {//检测是options方法则直接返回200

response.setStatus(HttpStatus.OK.value());

} else {

chain.doFilter(req, res);

}

}

public void init(FilterConfig filterConfig) {

}

public void destroy() {

}

});

}

第二种方案

1.创建一个类MyCorsRegistration继承自CorsRegistration

public class MyCorsRegistration extends CorsRegistration {

public MyCorsRegistration(String pathPattern) {

super(pathPattern);

}

@Override

public CorsConfiguration getCorsConfiguration() {

return super.getCorsConfiguration();

}

}

2.然后在WebMvcConfig类中增加一个方法里来处理跨域问题.

@Bean

public FilterRegistrationBean filterRegistrationBean() {

// 对响应头进行CORS授权

MyCorsRegistration corsRegistration = new MyCorsRegistration("/**");

corsRegistration.allowedOrigins("*")

.allowedMethods(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name(),

HttpMethod.PUT.name(), HttpMethod.OPTIONS.name())

.allowedHeaders("Accept", "Origin", "X-Requested-With", "Content-Type",

"Last-Modified", "device", "token")

.exposedHeaders(HttpHeaders.SET_COOKIE)

.allowCredentials(true)

.maxAge(3600);

// 注册CORS过滤器

UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();

configurationSource.registerCorsConfiguration("/**", corsRegistration.getCorsConfiguration());

CorsFilter corsFilter = new CorsFilter(configurationSource);

return new FilterRegistrationBean(corsFilter);

}

其实第二种方案本质上也是过滤器的机制,看源码可知CorsFilter继承自spring的过滤器OncePerRequestFilter,来看看它的doFilterInternal方法

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

if (CorsUtils.isCorsRequest(request)) {

CorsConfiguration corsConfiguration = this.configSource.getCorsConfiguration(request);

if (corsConfiguration != null) {

boolean isValid = this.processor.processRequest(corsConfiguration, request, response);

if (!isValid || CorsUtils.isPreFlightRequest(request)) {

return;

}

}

}

filterChain.doFilter(request, response);

}

代码大致意思是先判断是否为cors请求再判断是否预检请求,符合条件则直接return了.

我们来看看最后的请求结果,控制台再也没有报错了,浏览器一共发送了两次接口请求,第一次是OPTIONS请求,第二次是POST请求.

OPTIONS请求:

image.png

POST请求:

image.png

从上面第一个图中可以看到这个预检请求主要携带的请求header信息如下(并没有携带我们自定义的header信息,第二个图中可以清楚的看到我们自定义的header信息了):

Access-Control-Request-Headers:device,token 真实请求携带的Header中的信息

Access-Control-Request-Method:POST 我真实请求的方法是什么

Origin:http://localhost:8081 告诉服务器我的域名

然后是服务器端给我们返回的Preflight Response:

Access-Control-Allow-Credentials:true

Access-Control-Allow-Headers:Accept, Origin, X-Requested-With, Content-Type,Last-Modified,device,token

Access-Control-Allow-Methods:GET, HEAD, POST, PUT, DELETE, OPTIONS

Access-Control-Allow-Origin:*

Access-Control-Max-Age:3600

值得注意的是上面的Access-Control-Max-Age这个header,它告诉了服务器在多少秒内,不需要再发送预检请求,可以缓存该结果,即只发送真正的请求,不同浏览器有不同的上限.在Firefox中,上限是24h(即86400秒),而在Chrome中则是10min(即600秒).Chrome同时规定了一个默认值5秒.如果值为-1,则表示禁用缓存,每一次请求都需要提供预检请求,即用OPTIONS请求进行检测.它对完全一样的url的缓存设置生效,多一个参数也视为不同url.也就是说,如果设置了10分钟的缓存,在10分钟内,所有请求第一次会产生options请求,第二次以及第二次以后就只发送真正的请求了,这也是一个很有效的性能优化手段(另外注意下在chrome浏览器中如果开启了Disable cache,则表示本地不缓存,会导致每次请求都发一次预检测).

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/436913.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【转】WPF入门教程系列六——布局介绍与Canvas(一)

从这篇文章开始,我们将对WPF中的界面如何布局做一个较简单的介绍,大家都知道:UI是做好一个软件很重要的因素,如果没有一个漂亮的UI,功能做的再好也无法吸引用户使用,而且没有漂亮的界面,那么普通…

【OSG学习】学习方法

1. 环境准备 运行调试环境的准备参考我的另外一篇博客:【OSG学习】准备开发调试环境 运行调试环境准备比较麻烦,但是不复杂,需要耐心。但是可能很多人会被卡在这一步,后面我会专门提供直接可以使用的完整项目,方便大…

【转】Vue.js入门教程(二)在页面中引入vue的方式

第二章:安装和基础效果展示 页面中引入vue 因为我们的目标是在最短的时间之内学会vue的使用方法,所以我们不一定需要通过npm工程化进行安装,你直接用script在页面中引用也完全没有问题。 第一种引入方式,script直接引入&#xf…

空间注意力机制sam_Attention注意力机制介绍

什么是Attention机制Attention机制通俗的讲就是把注意力集中放在重要的点上,而忽略其他不重要的因素。其中重要程度的判断取决于应用场景,拿个现实生活中的例子,比如1000个人眼中有1000个哈姆雷特。根据应用场景的不同,Attention分…

【OSG】Examples

推荐内容 关于示例项目解析的内容推荐: OSG3.4内置Examples解析【目录】 下面是个人学习笔记。 1. Examples osgbillboard 这个项目很简单,就几个函数,而且很有意思。 osg::Billboard类是一个控制器,不管你怎么旋转漫游场景&a…

【转】页(page),用户控件(userControl),窗口(window)区别

欢迎加入BIM行业开发交流1群 群号:711844216 背景 大家在vs中新建wpf项目后,会发现在添加新建项时会出现下列三个选项 它们有什么区别呢? 区别: 页:通常用于网页窗口:通常一个桌面app只有一个主窗口用户控件&#…

python可以开发驱动吗_Python机器学习实践:测试驱动的开发方法

Python机器学习实践:测试驱动的开发方法作者:(美)马修柯克(Matthew Kirk) 著出版日期:2017年10月文件大小:30.91M支持设备:¥40.00在线试读适用客户端:言商书局iPad/iPhone客户端:下载…

关于typedef的正确理解

我对typedef的理解一直都是认为它和#define一样,分成三个部分,比如: #define A B但是遇到以下这种函数类型定义 typedef void (*funcName)(int);按照上面的理解,难道是要把void定义成(*funcName)(int)?显然不对。 这…

【转】WebSocket API总结

NCU.卓越141.chenyuchao 一、检查 WebSocket 支持 var host "ws://localhost:8080/mychat"; var ws; if (WebSocket in window) { ws new WebSocket(host); } else if (MozWebSocket in window) { ws new MozWebSocket(host); } else { window.alert(&quo…

字节流转化为文件流_字节流转成字符串之后,在通过字符串转成字节流后的文件为什么会不一样?...

public static void main(String[] args) throws Exception {File sourceFile new File("/home/joy/图片/img1-lg.jpg");File tempFile new File("/home/joy/桌面/TempFile");saveTempFile(sourceFile, tempFile);String str byteToString(tempFile);Fi…

OpenGL基本运行模型

OpenGL是一种三维技术规范。 我们知道三维渲染场景需要实时计算大量数据。 这里我根据自己的经验总结出一句话: 计算机中,对性能要求高的功能模块,其运行原理必然是简单易行的! 有理由断言:OpenGL没那么难。 OpenGL工…

matlab绘制球面模型_MATLAB采用surf/surfc/surfl/surfnorm绘制球体

利用绘制球体sphere(n),展示了MATLAB中的surf,surfc,surfl,surfnorm,surface五个绘图命令。启动MATLAB,新建脚本(CtrlN),输入如下代码:close all; clear all; clcn20;[x,y,z]sphere(…

【转】浅谈TDD、BDD、ATDD、DDD的区别

四个开发模式意思: TDD:测试驱动开发(Test-Driven Development)BDD:行为驱动开发(Behavior Driven Development)ATDD:验收测试驱动开发(Acceptance Test Driven Development&#x…

【OSG】OSG运行模型

关于运行模型 OSG中的类很多,只看OSG代码,很难把各个类串联起来。 我们知道面向对象程序的运行模型是:对象对象间协作。 单纯看代码,多数情形下,只能了解程序中有哪些对象,而不知道它们是如何协作的&…

用姓名字段统计人数_基于 Wide amp; Deep 网络和 TextCNN 的敏感字段识别

数据治理 (Data Governance) [1]作为一种数据管理的重要一环,主要目的在于保证数据在整个生命周期内的高质量性。数据治理的核心包括:数据的可用性 (Availability),易用性 (Usability),一致性 (Consistency),完整性 (I…

【转】C# HttpWebRequest 异常时获取 HttpWebResponse 数据

使用 C# 的 HttpWebRequest 请求接口如果接口返回了 401 则会抛出异常,而 401 其实也有可能返回正常的响应数据,如何获取异常时的响应流? 解决方案 捕获 WebException 异常,通过 ex.Response 获取 HttpWebResponse 主要代码如下…

innodb下的mvcc_从InnoDB了解MVCC

原标题:从InnoDB了解MVCCMVCC全称是Multi-Version Concurrency Control,即多版本并发控制。这是种很常用的技术,现在几乎所有的关系数据库都支持它。平时它默默工作,像个透明人,似乎不用关心它的细节。但是当我们偶尔在…

【开箱即用】VMware Win7虚拟机下载

前言 在桌面软件开发中,特别是Qt开发过程中,通常需要测试目标软件在不同版本的Windows上是否能够正常运行,以提高软件的系统兼容性。 虽然微软在2020年正式停止对Windows 7系统的支持,并鼓励用户升级Win10,但是由于W…

【转】C# Stream篇(—) -- Stream基类

目录: 什么是Stream? 什么是字节序列? Stream的构造函数 Stream的重要属性及方法 Stream的示例 Stream异步读写 Stream 和其子类的类图 本章总结 什么是Stream? MSDN 中的解释太简洁了: 提供字节序列的一般视图 (我可不想这么理解…

【已解决】解决Win7安装VS2013/VS2015结束时报错“无法建立到信任根颁发机构的证书链”的问题

问题描述 最近在Win7虚拟机上上安装VS,等待许久之后,提示安装完成。但是完成界面报错: “无法建立到信任根颁发机构的证书链”。 而且错误还不少,如下图所示: 根据我的个人经验,证书问题并没有影响日常开…