Web安全策略之Secure Cookie

发布时间 2023-07-06 00:57:55作者: 漠然0408丶

背景

提到cookie想必大家都不陌生吧,用来维护http对话身份的一个属性。提到了身份那就一定涉及到安全问题,如果别人拿到了cookie呢。在web应用中传输主流就是http协议,但是http又是不安全的,所以我们需要给cookie颁发一个属性那就是Secure属性,这样的cookie只能在https协议中传递。

http协议中的cookie

在裸奔的http协议传输过程中cookie是暴漏的,从中间拦截是可以看到cookie的。

https协议中的cookie

如果颁发了了ssl协议,在ssl下传输,cookie则不会暴漏出来。

Secure属性的使用

Secure三种设置方式

  • true 只能通过https传递,如果是http场景则cookie不能被设置成功
  • false 关闭该属性
  • 'auto' 通过请求响应自动设置对应的值,当http与https共存的时候(https系统一般不允许http协议传递,但是http系统是可以允许https协议)一旦设置成true(Secure)后续http将丢失cookie

nodejs生成cookie配置(express)

import session from 'express-session';

app.use(
  session({
    name: `${process.env.SESSION_NAME}`,
    secret: `${process.env.SERVER_SESSION_SECRETKEY}`, 
      client: sessionRedisClient,
    }),
    saveUninitialized: false, 
    resave: false, 
    cookie: {
      maxAge: 24 * 60 * 60 * 1000, 
      sameSite: 'lax',
    }, 
  })
);
app.listen(process.env.PORT || 3000, () => {
  console.log('http server');
});

如果在http协议中设置secure属性会有什么效果呢

cookie: {
      maxAge: 24 * 60 * 60 * 1000, 
      sameSite: 'lax',
      secure: true, // 新增配置
    }, 

我们会发现session id没有生成,结局就是登陆失败。且其他属性均提示⚠️(如下图)
结论如果设置了secure属性,cookie将不会传输。
截屏2023-07-06 00.32.09.png
我们为了验证secure的有效性,我们可以将node server变成https服务器

var server = https.createServer(
  {
    key: fs.readFileSync(path.join(__dirname, '../../server.key')),
    cert: fs.readFileSync(path.join(__dirname, '../../server.crt')),
  },
  app
);
server.listen(process.env.PORT || 3000, () => {
  console.log('https server');
});

截屏2023-07-06 00.35.35.png
响应头颁发cookie成功了,且有session id(skey).到这里就结束了吗?并没有,因为实际生产环境与本地环境并不一样,所以不妨模拟一下生产环境。
线上环境https,由于线上环境node server还是http协议,所以我们要把node server改成http协议传输,通过Nginx反向代理的形式来访问我们的server。

结构

client => reverse proxy(nginx) => server

Nginx配置

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    # HTTPS server
    #
    server {
       listen       9527 ssl;
       server_name  192.168.31.43;

       ssl_certificate /Users/deqi.han/frontend_project/Auto_Test/server.crt;
       ssl_certificate_key /Users/deqi.han/frontend_project/Auto_Test/server.key;

       ssl_session_cache    shared:SSL:1m;
       ssl_session_timeout  5m;

       ssl_ciphers  HIGH:!aNULL:!MD5;
       ssl_prefer_server_ciphers  on;

       location / {
            root   /Users/deqi.han/frontend_project/Auto_Test/build/;
           index  index.html;
       }
       location /infpf/ {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;  
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass  http://127.0.0.1:3000;
       }
    }
}

再次登陆
截屏2023-07-06 00.38.50.png
我们会发现session id(skey)又丢了!!!没错登陆又失败了,这是为什么?
我们打印一下请求信息
Screenshot 2023-07-06 at 00.08.51.png
会发现进来的请求是http请求,且ip是node server的ip。没错这里就是原因,当我们设置了secure属性之后,先不说ip来源出问题http协议就是没办法传输的!!!如何解决?

设置代理信任

app.set('trust proxy', 1);

再次登陆
截屏2023-07-06 00.41.29.png
一切又正常了~打印信息
Screenshot 2023-07-06 at 00.09.21.png
我们会发现请求协议变成了https,ip也是客户端ip了~,cookie又能传输了。
最终配置文件

import session from 'express-session';

app.set('trust proxy', 1);
app.use(
  session({
    name: `${process.env.SESSION_NAME}`,
    secret: `${process.env.SERVER_SESSION_SECRETKEY}`, 
      client: sessionRedisClient,
    }),
    saveUninitialized: false, 
    resave: false, 
    cookie: {
      maxAge: 24 * 60 * 60 * 1000, 
      sameSite: 'lax',
    }, 
  })
);

讲到这里我想小伙伴应该了解了该属性如何使用了吧~