不说话,装高手。
Maintain silence and pretend to be an experta
页面域名是 AAA,请求的接口地址是 BBB。这就是跨域,就是跨越也不同的域名来请求资源
这是一种只在浏览器环境中才会发生的现象,浏览器会阻止给和页面地址不同的域名发送请求,这跟语言无关,这是浏览器下的一个安全策略
Nginx 解决跨域
前端请求接口还是使用网页的域名,使用 nginx 转发到其他服务端的地址,配置文件中添加大概如下代码
server {
listen 80;
server_name your_domain.com;
location /api {
# 允许跨域请求的域名,* 表示允许所有域名访问
add_header 'Access-Control-Allow-Origin' '*';
# 允许跨域请求的方法
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
# 允许跨域请求的自定义 Header
add_header 'Access-Control-Allow-Headers' 'Origin, X-Requested-With, Content-Type, Accept';
# 允许跨域请求的 Credential
add_header 'Access-Control-Allow-Credentials' 'true';
# 预检请求的存活时间,即 Options 请求的响应缓存时间
add_header 'Access-Control-Max-Age' 3600;
# 处理预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
}
# 其他配置...
}
后端解决,这里我以 Nestjs 为例
全局启用 CORS
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 启用CORS
app.enableCors();
await app.listen(3000);
}
bootstrap();
使用 @UseCors 装饰器为特定路由启用 CORS
import { Controller, Get, UseCors } from '@nestjs/common';
@Controller('user')
export class CatsController {
@Get()
@UseCors({
origin: '<http://example.com>',
})
getList() {
// ...
}
}
通过中间件开设置更复杂的 CORS
import { Injectable, NestMiddleware, HttpStatus, RequestMethod} from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { isDevEnv } from 'src/app.environment';
@Injectable()
export class CorsMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const getMethod = (method) => RequestMethod[method];
const origins = req.headers.origin;
const origin = (Array.isArray(origins) ? origins[0] : origins) || '';
// 允许通过请求的域名
const allowedOrigins = ['http://127.0.0.1:3002'];
// 允许通过请求的方法
const allowedMethods = [
RequestMethod.GET,
RequestMethod.HEAD,
RequestMethod.PUT,
RequestMethod.PATCH,
RequestMethod.POST,
RequestMethod.DELETE,
];
// 允许通过请求的请求头字段
const allowedHeaders = [
'Authorization',
'Origin',
'No-Cache',
'X-Requested-With',
'If-Modified-Since',
'Pragma',
'Last-Modified',
'Cache-Control',
'Expires',
'Content-Type',
'X-E4M-With',
'Sentry-Trace',
'Baggage',
'x-access-token',
];
if (!origin || allowedOrigins.includes(origin) || isDevEnv) {
res.setHeader('Access-Control-Allow-Origin', origin || '*');
}
res.header('Access-Control-Allow-Credentials', 'true');
res.header('Access-Control-Allow-Headers', allowedHeaders.join(','));
res.header(
'Access-Control-Allow-Methods',
allowedMethods.map(getMethod).join(','),
);
res.header('Access-Control-Max-Age', '1728000');
res.header('Content-Type', 'application/json; charset=utf-8');
// 处理预请求
if (req.method === getMethod(RequestMethod.OPTIONS)) {
if (req.method === 'OPTIONS') {
return res.status(204).send();
} else {
next();
}
return res.sendStatus(HttpStatus.NO_CONTENT);
} else {
return next();
}
}
}
通过网关处理跨域
Java 中的 SpringCloud Gateway 可以通过修改配置文件或者通过 CorsWebFilter 过滤器来实现跨域
在 Gateway 服务的 application.yml 文件中,添加如下配置,确保允许 Options 请求,因为浏览器在进行跨域请求是会先发送一个 Options 请求来验证是否允许跨域
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://localhost:8081"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
CorsWebFilter 过滤器
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
// 这里仅为了说明问题,配置为放行所有域名,生产环境请对此进行修改
config.addAllowedOriginPattern("*");
// 放行的请求头
config.addAllowedHeader("*");
// 放行的请求类型,有 GET, POST, PUT, DELETE, OPTIONS
config.addAllowedMethod("*");
// 暴露头部信息
config.addExposedHeader("*");
// 是否允许发送 Cookie
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
通过框架处理跨域
一般来说 Vue、**React **等前端框架都会帮我们把这个问题处理好
Vue2.0 vue.config.js
module.exports = {
devServer:{
proxy:{
'/api':{ // /api表示拦截以/api开头的请求路径
target: 'http://localhost:3000/api/', //跨域的域名
changeOrigin:true,//是否开启跨域
pathRewrite:{ // 重写路径
'^/api':'' // 把/api变为空字符
}
}
}
}
}
Vue3.0 vite.config.js
server: {
host: "127.0.0.1", // 开发服务器的地址
port: 8000, // 开发服务器的端口号
proxy: {
"/api": {
target: "https://www.360.com", // 目标地址
changeOrigin: true, // 是否换源
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
JSONP跨域方案(一般很少用,列举两种记录一下)
原生 js 实现
// 前端
<script>
var script = document.createElement('script')
script.src = 'http://www.domain2.com:8081/user?userid=1001'
document.body.append(script)
var handlerRes = function(res) {
console.log(res)
}
// 发送到服务端
handlerRes("JSONP跨域脚本")
</script>
// nodejs 后端
const express = require('express')
const ser = express()
ser.get('/user', (req, res) => {
var id = req.query.userid
console.log(id) // 打印:1001
// 发送到客户端
res.send('handlerRes(' + id +')')
})
ser.listen(8081, () => {
console.log('create3')
})
JQuery Ajax 实现
$.ajax({
url: 'http://www.domain2.com:8080/login',
type: 'get',
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "handleCallback", // 自定义回调函数名
data: {}
});
关闭浏览器的安全策略
这里以谷歌浏览器 Chrome 为例
mac
寻找合适位置创建一个 myChromeDevData目录,在终端中执行一下命令,会自动调起关闭安全策略的浏览器
open -n /Applications/Google\ Chrome.app/ --args --disable-web-security --user-data-dir=/Users/Documents/myChromeDevData
windows
寻找合适位置创建一个 myChromeDevData目录,新建一个 chrome 快捷图标,右键属性,
在目标处最后面加入
--disable-web-security --user-data-dir=D:\tmp\myChromeDevData --user-data-dir
跨域问题在我的理解中最好是在网关层、代理层解决因为越靠前覆盖范围就越大,解决跨域问题就越容易,优先级:网关层 > 代理层 > 应用层
为什么开发环境不产生跨域
这边排除那些前后端一起干的同学,因为他们本地都是 localhost,自然不会产生跨域
另外前端开发时本地页面地址一般都是 localhost,接口地址肯定是其他的,为什么不会跨域?
这个问题在上面已经说过,我们平时开发所用的脚手架已经帮我们默默处理掉了跨域,那自然不会产生跨域
前端可以处理掉跨域。那我打包部署时部署一样就好了?
因为前端项目构建打包后,得到的只有一堆静态资源,他不像以前的模版引擎例如 jsp、php、ejs 等,需要依赖服务器来跑,我们的构建产物只需要一个 nginx 来提供静态文件的访问能力就行
为什么会发 option 请求
这是浏览器的安全策略,如果浏览器从来都没有请求过 BBB 域名,他怎么知道页面当前域名在不在BBB 域名的名单中,所以就有 option 请求,一个不带任何数据的请求,就是问一下 BBB 域名的服务器,我有什么权限,能否反问你
为什么我们在网站中可以加载各种域名的图片,他们不跨域吗?
图片也会触发跨域,只不过对于图片这类资源限制没那么严格,现在可以通过 Content-Security-Policy
内容安全政策响应标头来控制给定页面加载的资源