虽然做了很多项目,但是每每新项目时候跨域情况经常一而再,再而三的出现,并且可能并不是同一个问题,但都是跨域引起的,今天来归纳下跨域,一文彻底解决。
项目背景技术栈:react、vue、java、fetch、axios
主要为以下几点:
1、为什么跨域
2、好用的解决方式
3、options请求优化
认真读完这篇文章你将会收获:快速解决跨域问题能力、摆脱纠缠跨域是前端还是后端解决的问题。
一、为什么跨域?
首先要明确一点的是,跨域实际出现一般都是前后端分离场景,并且,跨域请求实际上是浏览器进行拦截的(浏览器的安全策略拦截)。你通过linux的curl去请求不同服务器的接口是怎么也不会遇到跨域。
跨域情况:
1、域名不同:http://a.com、http://b.com
2、协议不同:http、https
3、端口不同:http://a.com:8000、http://a.com:8001
注意:即使是同一个ip但是域名不同还是会跨域。例如:http://a.com的ip是192.168.1.1但是这两个还是会跨域。
二、跨域解决方式
有好几种:jsonp、nginx、cors。但是jsonp就不说了,老、烂、不好用浪费时间扯这个。
1、说下实际开发中用到的:cors
Cross-origin resource sharing 跨域资源共享。
后端设置允许跨域:需要设置请求返回的响应头,就是需要再过滤器里设置响应头,代码如下(跟我们后端要了代码哈哈):请注意,每一行都有单独的作用!
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest) req).getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE,PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Disposition,Origin, X-Requested-With, Content-Type, Accept,Authorization,id_token");
response.setHeader("Access-Control-Allow-Credentials","true");
response.setHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline'; script-src 'self'; frame-ancestors 'self'; object-src 'none'");
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-XSS-Protection", "1; mode=block");
chain.doFilter(req, res);
}
此时开发环境下如果前端是使用axios请求,并且没有自定义请求头则不需要设置任何代理就能正常请求了。如果你使用fetch还需要 mode: 'cors'来允许跨域。
如果你想跨域携带cookie,则需要自己在前端如下设置,看下下面两个请求库的如何设置的(默认下任何请求库都是不携带cookie,需要自己开启配置)。
前端设置跨域并允许携带cookie:
前端请求库一般有两种:流行框架下react或者vue使用axios、fetch这两者都可以,设置允许跨域的方式有点不一样。
原生fetch:
fetch('localhost:3000',{
/*允许携带cookies,默认情况没写这个是不会携带的*/
credentials: 'include',
/*允许跨域**/
mode: 'cors'
})
axios:
import axios from 'axios'
// 对所有 axios 请求允许携带cookie
axios.defaults.withCredentials = true;
// 对单独的 axios 请求允许携带cookie
axios.get('localhost:3000', {
withCredentials: true
})
2、不用cors解决跨域
- 本地开发环境:
如果不需要后端设置cors允许跨域,那么则需要前端自己配置本地代理了。在你的项目上设置代理,所有接口处前面加上/api用来识别做代理。
proxy: {
'/api': {
target: 'http://172.17.168.60:8000/',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
},
- 线上环境nginx:
location ^~ /api/ {
proxy_pass http://127.0.0.1:81/;
}
三、cors:简单请求、非简单请求,优化option请求
上面虽然有cors解决跨域,但是呢,cors会有一个情况,它会将请求分为简单请求和非简单请求。
满足以下几点是简单请求:
1、只限于get、post、head方法
2、请求头不超出以下字段(且没有其他自定义字段):
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
如果不满足以上其中之一,那就是非简单请求!
非简单请求会发送一个预检请求options,用来嗅探服务端是否允许非简单请求跨域访问资源。
- 注意:这个options请求是浏览器自己发出的!
有option时请求同个接口时会出现请求两次的情况,并option请求没有返回任何数据,还会导致请求变慢了,那么如何优化?
后端设置允许缓存时间来优化:
//加上这一行,用意为当下次请求碰到这个接口时候就会在这个时长内忽略option请求不再发起option请求。
response.setHeader("Access-Control-Max-Age", "3600"); //3600为时长
但这并不能完美解决,如果谷歌浏览器勾选了Disable cache按钮,这个效果将会失效。
添加自定义请求头跨域:
还有个特殊情况,就是当你添加了自定义请求头,例如再请求添加了'id_token'字段用来判断用户登录情况,或者其他字段(叫啥都行,只要是自定义的),那么你需要让后端加上去允许这个携带这个字段跨域。
response.setHeader(
"Access-Control-Allow-Headers",
"Content-Disposition,
Origin,
X-Requested-With,
Content-Type,
Accept,
Authorization,
id_token" //自定义请求头要加上去,否则还是会跨域,在任何情况下
);
注意:上面这个id_token
在使用ningx转发代理时候会导致请求投内容丢失情况。
原因:nginx不会识别"_"这个符号,默认情况下它会忽略,所以后端没接收到,也就是说请求时候没将这个id_token转发过去导致的。
解决办法有两种:
1、修改nginx配置
在nginx 的 http部分添加如下:
underscores_in_headers on; (默认 underscores_in_headers 为off)
2、修改这个字段,取消下划线
列如 把原来的id_token 换为 idToken