- 问题复现
- 跨域现象
- 什么是跨域
- 为什么要跨域
- 同源策略
- 什么是同源策略
- 为何存在同源策略
- 解决跨域问题
- 其他解决跨域问题的方案
- JSONP
- CORS 跨域资源共享
- 前端设置
- 服务器设置
- 反向代理
- 参考文档
今天登录ant design pro时,报出登录失败的问题,于是打开控制台,如下图所示:
错误信息为Access to XMLHttpRequest at 'http://127.0.0.1:8088/user/login' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
翻译成中文是从源“http://127.0.0.1:8000”访问“http://127.0.0.1:8088/user/login”处的 XMLHttpRequest 已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查 :请求的资源上不存在“Access-Control-Allow-Origin”标头。
这个问题简单来说即是跨域问题,但为什么会存在跨域问题呢?
跨域现象跨域报错的原因:请求是跨域的,并不一定会报错。普通的图片请求,css文件请求是不会报错的
跨域请求出现错误的条件: 浏览器同源策略&& 请求是ajax类型
什么是跨域指的是浏览器不能执行其他网站的脚本,它是由浏览器的同源策略造成的,是浏览器对 javascript 施加的安全限制。
比如,为了满足从域名A下的一个页面(一般ajax或CORS请求等)获取域名B下的一个资源,但是浏览器的同源策略的限制默认并不允许这样的事情发生,所以需要跨域。
为什么要跨域有一些情况下,我们是需要跨域访问的。比如,程序员开发网站,就有可能需要在本地访问服务器的数据。再比如某公司的A页面(a.superjson.com)有可能需要获取B页面(b.superjson.com)。
同源策略 什么是同源策略上文提到了同源策略,那什么是同源策略呢?
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。同源策略是一种关键的安全机制,它限制一个源加载的文档或脚本如何与另一个源的资源进行交互。
同源策略要求协议(protocol)、域名(host)、端口号(port),都必须相同,其中一个不同都会产生跨域。比如,http://www.superjson.com:8000/中的协议是http、主域名是superjson、子域名是www、端口号是8000。
以URL1和URL2为例子,来说明是否为同源:
| URL1 | URL2 | 说明 | 是否同源 |
|---|---|---|---|
| http://www.superjson.com/a.js | http://www.superjson.com/123/b.js | 协议、域名、端口均相同 | 同源 |
| http://www.superjson.com/a.js | https://www.superjson.com/b.js | 协议不同,域名和端口相同 | 非同源 |
| http://www.superjson.com/a.js | http://superjson.com/b.js | 域名不同,协议和端口相同 | 非同源 |
| http://www.superjson.com:80/a.js | http://www.superjson.com:8080/b.js | 端口不同,域名和协议和相同 | 非同源 |
假如,你刚刚在网银输入账号密码,查看了自己还有1万块钱,紧接着访问一些不规矩的网站,这个网站可以访问刚刚的网银站点,并且获取账号密码,那后果可想而知。
所以,从安全的角度来讲,同源策略是有利于保护网站信息的。
解决跨域问题我后端使用的是spring boot框架,所以,我可以配置如下代码:
package com.superjson.superjsonmanager.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static com.superjson.superjsonmanager.constants.CorsConfigConstant.*;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
// 添加映射路径
registry
.addMapping(ADD_MAPPING)
// 是否发送Cookie
.allowCredentials(ALLOW_CREDENTIALS)
// 设置放行哪些原始域 SpringBoot2.4.4下低版本使用.allowedOrigins("*")
.allowedOriginPatterns(ALLOWED_ORIGIN_PATTERNS)
// 放行哪些请求方式
.allowedMethods(
new String[] {
ALLOWED_METHODS_GET, ALLOWED_METHODS_POST, ALLOWED_METHODS_PUT, ALLOWED_METHODS_DELETE
})
// 或者放行全部
.allowedMethods(ALLOW_ALL)
// 放行哪些原始请求头部信息
.allowedHeaders(ALLOW_ALL)
// 暴露哪些原始请求头部信息
.exposedHeaders(ALLOW_ALL);
}
}
package com.superjson.superjsonmanager.constants;
public class CorsConfigConstant {
public static final String ADD_MAPPING = "
public static final String ALLOWED_ORIGIN_PATTERNS = "http://127.0.0.1:[8000,1234]";
public static final String ALLOWED_METHODS_GET = "GET";
public static final String ALLOWED_METHODS_POST = "POST";
public static final String ALLOWED_METHODS_PUT = "PUT";
public static final String ALLOWED_METHODS_DELETE = "DELETE";
public static final String ALLOW_ALL = "*";
}
其他解决跨域问题的方案
JSONP
原理是:有些标签 script、img、link、iframe ... 这些标签不存在跨域请求的限制,就是利用这个特点解决跨域问题。
JSONP 是服务器与客户端跨源通信的常用方法:
- 优点:简单适用,兼容性好(可以兼容低版本IE),
- 缺点:只支持 get 请求,不支持 post 请求,导致数据不安全
核心思想:网页通过添加一个
- jQuery提供的JSONP处理方式
$.ajax({
url: 'http:/www.superjson.com:8000/login',
type: 'get',
dataType: 'jsonp',// 设置请求方式为 jsonp
jsonpCallback: 'handleCallback',// 自定义回调函数名
data: {}
})
- Vue.js
this.$http.jsonp('http://www.superjson.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then(res => {
//
});
CORS 跨域资源共享
CORS 跨域资源共享,即Cross-Origin Resource Sharing。它新增的一组 HTTP 首部字段允许服务器其声明那些来源请求有权访问哪些资源,就是它允许浏览器向其声明了 CORS 的栈进行跨域请求。
这种方式最主要的特点就是会在响应的 HTTP 首部增加 Access-Control-Allow-Origin 等信息,从而判定那些资源站可以进行跨域请求,还有几个其他相关的 HTTP 首部进行更加精细化的控制,最主要的还是 Access-Control-Allow-Origin 。
CORS 与 JSONP 对比来说又是比较明显,JSONP 只支持 GET 方式,而且 JSONP 并不符合处理业务的正常流程。采用 CORS 的方式,前端编码与正常非跨域请求没有什么不同。
- 客户端发送请求(ajax):
在真正的发送跨域请求之前会发送一个试探性请求(OPTIONS),服务器接收到 OPTIONS请求之后,做一个处理,返回成功,表示可以发送跨域请求,再发送真正的跨域请求
服务端设置相关的头信息(需要处理试探性请求OPTIONS)
- 带 cookie 跨域请求:前后端都需要进行设置
根据 xhr.withCredentials 字段判断是否带有 cookie
- 原生 ajax
var xhr = new XMLHttpRequest();// ie8/9 需用 window.XDomainRequest 兼容
// 前端设置是否带 cookie
xhr.withCredentials = true;
xhr.open('post', 'http://www.superjson.com:8000/index', true);
xhr.setRequestHeader('Content-ype', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText)
}
};
- 原生 ajax
$.ajax({
url: 'http://www.superjson.com:8000/index',
type: 'get',
data: {},
xhrFields: {
withCredentials: true// 设置前端是否带 cookie
},
crossDomain: true// 会让请求头中包含跨域的额外信息,但不会含 cookie
});
- vue-resource
Vue.http.options.credentials = true
- axios
axios.defaults.withCredentials = true服务器设置
服务器端对于 CORS 的支持,主要是通过设置 Access-Control-Allow-Origin 来进行的。如果浏览器检测到相应的设置,就可以允许 Ajax 进行跨域访问:
// 不使用*,自动适配跨域域名,避免携带Cookie时失效
String origin = request.getHeader('Origin');
if (StringUtils.isNotBlank(origin)) {
response.setHeader('Access-Control-Allow-Origin', origin);
}
// 自适应所有自定义头
String headers = request.getHeader('Access-Control-Request-Headers');
if (StringUtils.isNotBlank(headers)) {
response.setHeader('Access-Control-Allow-Headers', headers);
response.setHeader('Access-Control-Expose-Headers', headers);
}
// 允许跨域的请求方法类型
response.setHeader('Access-Control-Allow-Methods', '*');
// 预检命令(OPTIONS)缓存时间,单位:秒
response.setHeader('Access-Control-Max-Age', '3600');
// 明确许可客户端发送Cookie,不允许删除字段即可
response.setHeader('Access-Control-Allow-Credentials', 'true');
反向代理
既然不能跨域请求,那么我们不跨域就可以了,通过在请求到达服务器前部署一个服务,将接口请求进行转发,这就是反向代理。通过一定的转发规则可以将前端的请求转发到其他的服务。
通过反向代理我们将前后端项目统一通过反向代理来提供对外的服务,这样在前端看上去就跟不存在跨域一样。
反向代理麻烦之处就在原对Nginx等反向代理服务的配置,在目前前后端分离的项目中很多都是采用这种方式。
proxyTable: {
'/api': {
target:'http://api.douban.com/v2', // 你请求的第三方接口
changeOrigin:true, // 在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,这样服务端和服务端进行数据的交互就不会有跨域问题
pathRewrite:{ // 路径重写,
'^/api': '' // 替换target中的请求地址,也就是说以后你在请求http://api.douban.com/v2/XXXXX这个地址的时候直接写成/api即可。
}
}
},
参考文档
- http://www.manongjc.com/detail/29-cpaozffgzyhvbjc.html



