关于withCredentials和CORS[项目笔记]

5,175 阅读4分钟

参考链接

koa2-cors设置允许指定单个域名、多个域名、所有域名跨域

当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域.

CORS 跨域资源共享

cross origin resource sharing (CORS,跨域资源共享) 是一种机制,它使用额外的HTTP 头来告诉浏览器 让运行在一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源。

cors配置[koa2]

允许所有域名跨域访问

const cors = require('koa2-cors');
app.use(cors()); 

指定单个域名跨域

app.use(
     cors({
         origin: function(ctx) { //设置允许来自指定域名请求
            return 'http://localhost:8080'; //只允许http://localhost:8080这个域名的请求
        },
         maxAge: 5, //指定本次预检请求的有效期,单位为秒。
        credentials: true, //是否允许发送Cookie
         allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法
        allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段
        exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
    })
);

设置多个域名可跨域

app.use(
     cors({
         origin: function(ctx) { //设置允许来自指定域名请求
            const whiteList = ['http://weipxiu.com','http://localhost:8081']; //可跨域白名单
            let url = ctx.header.referer.substr(0,ctx.header.referer.length - 1); 
             if(whiteList.includes(url)){
                 return url //注意,这里域名末尾不能带/,否则不成功,所以在之前我把/通过substr干掉了
            }
             return 'http://localhost::3000' //默认允许本地请求3000端口可跨域
        },
         maxAge: 5, //指定本次预检请求的有效期,单位为秒。
         credentials: true, //是否允许发送Cookie
         allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法
        allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段
        exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
    })
);

withCredentials在项目中遇到的问题

问题描述

在实际项目开发中,遇到这样一个问题:在用户登录成功后为用户设置cookie,当用户再次访问该网页时直接从cookie中获取用户信息。在项目中,我遇到的问题是,无法从cookie中获取到用户信息,获取到的值是undefined

登录成功后设置cookie

router.post('/smslogin', async(ctx,next) => {
    // 1. 获取数据
    const qqEmail = ctx.request.body.qqEmail;
    const pin = ctx.request.body.pin;
    console.log(sms)
    console.log(qqEmail,pin)
    // // 2. 验证验证码是否正确
    if (pin != sms) {
        ctx.body={err_code: 0, message: '验证码不正确!'};
        sms = ''
        return;
    }
    // 3. 查询数据
    let sqlStr = `SELECT * FROM users WHERE qqEmail = '${qqEmail}'`;
    let res = await query(sqlStr)
    if(res[0]){
      ctx.cookies.set('userId', res[0].userId, {
            domain: 'localhost', // 写cookie所在的域名
            path: '/', // 写cookie所在的路径
            maxAge: 2 * 60 * 60 * 1000, // cookie有效时长
            expires: new Date('2018-02-08'), // cookie失效时间
            httpOnly: false, // 是否只用于http请求中获取
            overwrite: false // 是否允许重写
        })

    }else{
      ctx.body={err_code:200,message:'请注册后登录!'}
    }
});

获取cookie中的数据

router.get('/userinfo',async(ctx,next) => {
    // 1.0 获取参数
    
    let userId = ctx.cookies.get('userId')  // 这里获取参数失败
    
    let res = await query(`SELECT * FROM users WHERE id = '${userId}'`)
    delete res[0].password
    ctx.body={
      success_code:200,
      message:res[0]
    }
});

问题分析

在同域的情况下,我们发送请求会默认携带当前域下的cookie,但是在跨域的情况下,默认是不会携带请求域下的cookie 的,比如http://domain-a.com 站点发送一个 http://api.domain-b.com/get 的请求,默认是不会携带 api.domain-b.com 域下的 cookie.

在项目中,情况是这样的localhost:8080站点取发送localhost:3000的请求,两者端口不一样,构成跨域,跨域默认是不会携带 localhost:3000 域下的 cookie.

问题解决

要解决这个问题,我们就要让这个跨域请求可以携带上cookie,做法很简单:

前端处理

在前端我是使用axios发起网络请求

axios.defaults.withCredentials = true

后端处理

在后端,我们也要否允许浏览器携带cookie访问进行设置,也就是说我们要将响应头的credentials设置为true

credentials: true, //是否允许发送Cookie

此外,我们还要对cors进行配置。我们不能允许所有域名进行跨域访问:

也就是说,我们对cors不能这样配置.

const cors = require('koa2-cors');
app.use(cors()); 

比较推荐的做法是

设置多个域名可跨域

app.use(
     cors({
         origin: function(ctx) { //设置允许来自指定域名请求
            const whiteList = ['http://weipxiu.com','http://localhost:8081']; //可跨域白名单
            let url = ctx.header.referer.substr(0,ctx.header.referer.length - 1); 
             if(whiteList.includes(url)){
                 return url //注意,这里域名末尾不能带/,否则不成功,所以在之前我把/通过substr干掉了
            }
             return 'http://localhost::3000' //默认允许本地请求3000端口可跨域
        },
         maxAge: 5, //指定本次预检请求的有效期,单位为秒。
         credentials: true, //是否允许发送Cookie
         allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法
        allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段
        exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
    })
);

至此,问题已经解决。

虽然问题已经解决了,我们还应该思考一下,在上面的情况中,为什么不能将cors设置为允许全部域名跨域?

以下是个人理解: 从安全方面考虑,将cors设置为允许全部域名跨域可以让我们在后端获取到cookie的值,后端处理是没有问题的,但是服务器考虑到安全性会拦截Access-Control-Allow-Origin: *的响应。