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,则表示本地不缓存,会导致每次请求都发一次预检测).
cors 前后端分离跨域问题_前后端分离之CORS跨域访问踩坑总结相关推荐
- mysql单台跨数据库查询_在MySQL中怎样进行跨库查询?
在MySQL中跨库查询主要分为两种情况,一种是同服务的跨库查询:另一种是不同服务的跨库查询:它们进行跨库查询是不同的,下面就具体介绍这两种跨库查询. 一.同服务的跨库查询 同服务的跨库查询只需要在关联 ...
- 前后端分离项目如何部署_前后端分离项目,如何解决跨域问题?
跨域资源共享(CORS)是前后端分离项目很常见的问题,本文主要介绍当SpringBoot应用整合SpringSecurity以后如何解决该问题. 01 什么是跨域问题? CORS全称Cross-Ori ...
- 前后端开发的心得体会_前后端对接的思考及总结
说在前面的话 随着前端NodeJs技术的火爆,现在的前端已经非以前传统意义上的前端了,各种前端框架(Vue.React.Angular......)井喷式发展,配合NodeJs服务端渲染引擎,目前前端 ...
- 前后端开发的心得体会_从后端支撑岗位到前端渠道运营中心工作感想
1 8 月份,渠道攻坚战打响,做为电信的生力军,我奋不顾身 的冲在最前面,为电信生存而战,力求打赢这场战役. 从后端支撑岗位到前端渠道运营中心这一新的部门, 开展了 夜场促销活动, 设计炒店主题及手写 ...
- 产品运营 跨境支付_餐饮网店的运营 跨境支付哪个平台最好
看你的业务和主营区域,目前信用卡和借记卡都是在线交易中占主流的支付方式.然而内在许多国家,并不是所容有人都喜欢使用信用卡或借记卡在线上消费,原因有很多,也许是因为本身没有信用卡,也许是担忧信息泄露,又 ...
- excel制作跨职能流程图_如何绘制泳道图(跨职能流程图)
如何定义泳道图?泳道图也叫跨职能流程图,旨在展示工作流中每个步骤涉及的流程和职能部门.泳道流程图是一种特殊的图表可以展示出一个商业过程之间的关系,并展示为那个过程负责的功能板块(比如说部门).泳道流程 ...
- java中获取文本域内容_怎样读文件内容到文本域中(java SWT)
//写了段简单的代码提供你参考importjava.io.BufferedReader;importjava.io.File;importjava.io.FileInputStream;importj ...
- java后端需要注意的事项_【后端开发】Java中关于null的含义以及使用中要注意的事项...
下面小编就为大家带来一篇浅谈java中null是什么,以及使用中要注意的事项.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 1.null既不是对象也不是一种类型,它仅是 ...
- c# sha1签名 微信_微信公众号开发——微信JSSDK使用(踩坑)
这段时间有个工作,是要在移动端给地图上加上导航功能,找了一圈,最后决定使用微信JSSDK的'打开地理位置接口'来开发,也是着实踩了下坑啊,分享一下 微信JSSDK介绍 因为微信公众号的开通对于大部分开 ...
最新文章
- 剑指offer_第3题_从尾到头打印链表
- 节流函数(throttle)的原理
- 区块链BaaS云服务(14)华大BGI区块链“安全多方计算“
- 关于MySQL的各种总结
- 离散数学序关系与相容关系
- “新型冠状病毒国家科技资源服务系统”入选全球15项世界互联网领先科技成果...
- DEL: Open explorer from Console
- python趣味编程_戏说《西游记》之Python趣味编程:第四回 拜师学艺 破盘中之谜...
- Linux使用Jstack查看Java堆栈快照脚本
- 极路由 斐讯K2 Newifi 华硕固件 实现ipv6穿透方法
- ioi 赛制_编程大神IOI2019国家队第二名是什么概念?全球知名算法竞赛网站列中国选手第二名!...
- Java之ip地址存储的数据类型
- 消防应急照明和疏散指示系统
- 【leetcode】算法题记录(111-120)
- android bmob上传图片,Bmob+Android+ECharts 实现移动端数据上传与图表展示
- 文章总结:ASPLOS 2013 Architecture I
- 天堂2mysql_天堂2单机版 L2J-som-rotm 安装全过程及常用工具 一步到位
- Java并发编程之 Excutor
- HM中CU,TU的划分
- 【大学物理·静止电荷的电场】静电场的环路定理 电势
热门文章
- 今天觉得自己好像比较紧张
- UI Automation 简介
- Eclipse安装ADT失败解决办法
- [jQuery] jQuery函数
- Regular Expression
- 用WebORB实现flex + .net后台的Remoting
- web开发常用js功能性小技巧(转)
- Dijkstrala算法
- [Leedcode][JAVA][第76题][最小覆盖子串]滑动窗口]
- lua 调用文件中的函数调用_深入Lua:调用相关的指令