配置跨域后,框架帮我们做了什么?
跨域问题
现在绝大多数公司的项目都是前后端分离的,前后端分离后势必会遇到跨域问题。如下图
继续debug发现,reponse为undefined,提示消息为Network Error。
所以当你和前端联调的时候一直请求失败,报网络错误,一般情况下是后端没有做跨域配置。
注意此时并不是后端没有收到请求,而是收到请求了,也返回结果了,但是浏览器将结果拦截了,并且报错。
同源策略
那么浏览器为什么会报错呢?
因为浏览器基于安全考虑而引入的同源策略
当协议+域名+端口三者都相同时,才不会产生跨域问题,即同源。此时才能读取到服务端的响应
当前url |
请求url |
是否跨域 |
---|---|---|
https://www.javashitang.com |
http://www.javashitang.com |
是,协议不同 |
https://www.javashitang.com |
http://book.javashitang.com |
是,域名不同 |
https://www.javashitang.com |
http://www.javashitang.com:8000 |
是,端口不同 |
为什么要有同源策略呢?
当然是为了安全起见,举个例子,以银行转账为例,看看你的钱是怎么没的
这就是著名的CSRF攻击(跨站请求伪造,当然还有很多其他方式),还有如果第5步不对请求的来源进行校验,那么你的钱已经被转走了
html页面中的如下三个标签是允许跨域加载资源的
- <img src=XXX>
- <link href=XXX>
- <script src=XXX>
如何解决跨域
虽然同源策略保证了安全,但一些合理的用途也会受到影响。解决跨域的方式有很多种,简单介绍2个
JSONP
JSONP主要是利用<script>标签将请求发送出去,来实现数据的加载,但这种方式有一个缺点,即只能支持GET请求,其他请求都不能支持,因为JSONP这种方式已经很少使用了,所以不做过多的介绍
CROS
非简单请求
在正式的跨域请求前,发送一个OPTIONS请求去询问服务器是否接受接下来的跨域请求,携带如下header
Origin:发起请求原来的域 Access-Control-Request-Method:将要发起的跨域请求方式(GET/POST/…) Access-Control-Request-Headers:将要发起的跨域请求中包含的请求头字段
服务器在返回中增加如下header来表明是否允许这个跨域请求。浏览器收到后进行检查如果不符合要求则不会发起后续请求
Access-Control-Allow-Origin:允许哪些域来访问(*表示允许所有域的请求) Access-Control-Allow-Methods:允许哪些请求方式 Access-Control-Allow-Headers:允许哪些请求头字段 Access-Control-Allow-Credentials:是否允许携带Cookie
简单请求
每次都要发送二次请求是不是很麻烦?所以做了优化
当请求方法是HEAD、GET、POST 并且请求头只有如下几个时,被定义为简单请求 Accept Accept-Language Content-Language Last-Event-ID Content-Type:(application/x-www-form-urlencoded、multipart/form-data、text/plain)
简单请求会在请求中加入Origin头,直接发起请求,不会先询问了。后端返回相应的header即可
Spring支持跨域
理解了跨域的本质,再看各种配置其实就是根据请求往reponse中增加header
利用Filter
配置如下Filter,CrossDomainFilter是对javax.servlet.Filter的封装,本质上是一个Filter。
可以看到我多返回了一个header,Access-Control-Max-Age,他表明了询问结果的有效期限,即在3600s之内浏览器可以不必再次询问
@Component
@WebFilter(filterName = "crossDomain", urlPatterns = "/*")
public class CrossDomainFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 此处可以进行白名单检测
if(CorsUtils.isCorsRequest(request)) {
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
response.setHeader("Access-Control-Allow-Methods", request.getHeader("Access-Control-Request-Method"));
response.setHeader("Access-Control-Max-Age", "3600");
}
// 是个OPTIONS请求,header已设好,不用执行后续逻辑,直接return
if(CorsUtils.isPreFlightRequest(request)) {
return;
}
filterChain.doFilter(request, response);
}
}
看一下用到的工具类
public abstract class CorsUtils {
// 请求中有 origin 这个header则返会true
public static boolean isCorsRequest(HttpServletRequest request) {
return (request.getHeader(HttpHeaders.ORIGIN) != null);
}
public static boolean isPreFlightRequest(HttpServletRequest request) {
return (isCorsRequest(request) && HttpMethod.OPTIONS.matches(request.getMethod()) &&
request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD) != null);
}
}
利用CorsRegistry
@Configuration
public class GlobalCorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径
registry.addMapping("/**")
// 允许的域
.allowedOrigins("*")
// 允许携带cookie
.allowCredentials(true)
// 允许的请求方式
.allowedMethods("GET","POST", "PUT", "DELETE")
// 允许的请求头
.allowedHeaders("*");
}
};
}
}
利用@CrossOrigin注解
支持更细粒度的配置,可以用法方法上或者类上
@RestController
@RequestMapping("resource")
@CrossOrigin({"http://127.0.0.1:8080"})
public class ResourceController
其他方式支持跨域
看到这你可能会产生疑问,我们的项目中没有跨域的配置啊,怎么还能支持跨域?那估计是把设置header这些活交给网关层来做了。
- 清除浮动(clearfix hack)
- Linux下环境变量配置方法梳理(.bash_profile和.bashrc的区别)
- 小程序火爆的因素
- Log4Net使用心得
- nginx通过https方式反向代理多实例tomcat
- Linux系统下yum镜像源环境部署记录
- 特斯拉vs凯迪拉克vs奔驰:三大汽车自动驾驶系统比拼
- Centos下添加静态路由(临时和永久有效)的操作记录
- python如何保证输入键入数字
- 微信小程序自定义数据分析试水
- 挂载银行前置机Ukey到windows server2012虚拟机的操作记录
- 文件上传速度查询方法
- “AS3.0高级动画编程”学习:第三章等角投影(上)
- su: 无法设置用户ID: 资源暂时不可用
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 直播系统定制,判断数据连接是否可用
- VS Code 编辑器入门指南上篇-核心概念与组件
- Python turtle库实现基本剖析
- python thinker canvas create_arc 使用详解
- Python3 实现单例设计模式
- Python3 实现建造者模式
- python 实现原型设计模式
- python 最简单的实现适配器设计模式
- python3 最基本且简单的实现组合设计模式
- python 实现装饰器设计模式
- 看得懂的外观设计模式 python3 实现
- 看得懂的设计模式 享元模式python3 最基本(简单)实现
- python3 最简单的实现 模版设计模式
- Qt 第二步 槽与信号(一) 实现点击按钮并弹窗
- python3 爬虫第一步 简单获取网页基本信息