阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 跨域请求的解决方案

跨域请求的解决方案

作者:陈川 阅读数:6784人阅读 分类: Node.js

跨域请求的解决方案

跨域请求是前后端分离开发中常见的问题。浏览器出于安全考虑,限制了不同源之间的资源访问。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:是否允许发送Cookie
  • maxAge:预检请求缓存时间

手动设置响应头

如果不使用中间件,可以手动设置响应头:

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌