跨域请求的解决方案
跨域请求的解决方案
跨域请求是前后端分离开发中常见的问题。浏览器出于安全考虑,限制了不同源之间的资源访问。Koa2作为Node.js的轻量级框架,提供了多种方式解决跨域问题。
CORS中间件
@koa/cors
是专门为Koa设计的CORS中间件,使用简单且功能强大:
const Koa = require('koa');
const cors = require('@koa/cors');
const app = new Koa();
app.use(cors({
origin: 'http://localhost:3000',
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization'],
exposeHeaders: ['X-Custom-Header'],
credentials: true,
maxAge: 3600
}));
配置项说明:
origin
:允许访问的源,可以是字符串、数组或函数allowMethods
:允许的HTTP方法allowHeaders
:允许的请求头exposeHeaders
:允许客户端访问的响应头credentials
:是否允许发送CookiemaxAge
:预检请求缓存时间
手动设置响应头
如果不使用中间件,可以手动设置响应头:
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', 'http://localhost:3000');
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
ctx.set('Access-Control-Expose-Headers', 'X-Custom-Header');
ctx.set('Access-Control-Allow-Credentials', 'true');
ctx.set('Access-Control-Max-Age', '3600');
if (ctx.method === 'OPTIONS') {
ctx.status = 204;
return;
}
await next();
});
代理服务器方案
前端开发时常用webpack-dev-server的proxy功能解决跨域:
// webpack.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
JSONP方案
虽然JSONP只支持GET请求,但在某些场景下仍然有用:
// 服务端
router.get('/api/jsonp', async (ctx) => {
const callback = ctx.query.callback;
const data = { message: 'Hello JSONP' };
ctx.body = `${callback}(${JSON.stringify(data)})`;
ctx.type = 'application/javascript';
});
// 客户端
function handleResponse(data) {
console.log(data.message);
}
const script = document.createElement('script');
script.src = 'http://localhost:3000/api/jsonp?callback=handleResponse';
document.body.appendChild(script);
WebSocket跨域
WebSocket不受同源策略限制,但服务端需要处理握手请求:
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (message) => {
console.log('received: %s', message);
ws.send('Server received your message');
});
});
复杂请求处理
对于PUT、DELETE等复杂请求,需要处理OPTIONS预检请求:
app.use(async (ctx, next) => {
if (ctx.method === 'OPTIONS') {
ctx.set('Access-Control-Allow-Origin', ctx.headers.origin);
ctx.set('Access-Control-Allow-Methods', 'PUT, DELETE');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header');
ctx.status = 204;
} else {
await next();
}
});
动态源配置
当需要根据请求动态设置允许的源时:
const allowedOrigins = ['http://localhost:3000', 'https://example.com'];
app.use(async (ctx, next) => {
const origin = ctx.headers.origin;
if (allowedOrigins.includes(origin)) {
ctx.set('Access-Control-Allow-Origin', origin);
ctx.set('Access-Control-Allow-Credentials', 'true');
}
await next();
});
带凭证的请求
处理需要发送Cookie的跨域请求:
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));
// 客户端需要设置
fetch('http://localhost:3000/api/data', {
credentials: 'include'
});
自定义中间件
创建可复用的跨域中间件:
function customCors(options = {}) {
const defaults = {
allowOrigin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE',
allowHeaders: '',
exposeHeaders: '',
credentials: false,
maxAge: 86400
};
const opts = { ...defaults, ...options };
return async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', opts.allowOrigin);
ctx.set('Access-Control-Allow-Methods', opts.allowMethods);
if (opts.allowHeaders) {
ctx.set('Access-Control-Allow-Headers', opts.allowHeaders);
}
if (opts.exposeHeaders) {
ctx.set('Access-Control-Expose-Headers', opts.exposeHeaders);
}
if (opts.credentials) {
ctx.set('Access-Control-Allow-Credentials', 'true');
}
if (ctx.method === 'OPTIONS') {
ctx.set('Access-Control-Max-Age', opts.maxAge.toString());
ctx.status = 204;
return;
}
await next();
};
}
生产环境配置
生产环境中更严格的CORS策略:
const isProduction = process.env.NODE_ENV === 'production';
app.use(cors({
origin: isProduction ? 'https://example.com' : 'http://localhost:3000',
credentials: true,
maxAge: isProduction ? 86400 : 3600
}));
错误处理
处理跨域请求中的错误:
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.set('Access-Control-Allow-Origin', 'http://localhost:3000');
ctx.status = err.status || 500;
ctx.body = {
error: err.message
};
}
});
性能优化
对于频繁的跨域请求,合理设置缓存:
app.use(cors({
origin: 'http://localhost:3000',
maxAge: 86400, // 24小时
methods: ['GET', 'POST'],
allowHeaders: ['Content-Type']
}));
安全考虑
限制跨域请求的安全策略:
app.use(cors({
origin: (ctx) => {
const validDomains = ['https://example.com', 'https://trusted-site.com'];
const requestOrigin = ctx.headers.origin;
if (validDomains.includes(requestOrigin)) {
return requestOrigin;
}
return false;
},
credentials: true
}));
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn