egg对cookies,session和redis的配置操作(四)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 默认情况下,当用户请求没有导致 Session 被修改时,框架都不会延长 Session 的有效期,但是在有些场景下,我们希望用户如果长时间都在访问我们的站点,则延长他们的 Session 有效期,不让用户退出登录态。框架提供了一个 renew 配置项用于实现此功能,它会在发现当用户 Session 的有效期仅剩下最大有效期一半的时候,重置 Session 的有效期。

一、egg对cookies的操作


HTTP 请求都是无状态的,但是我们的 Web 应用通常都需要知道发起请求的人是谁。为了解决这个问题,HTTP 协议设计了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上(浏览器也会遵循协议,只在访问符合 Cookie 指定规则的网站时带上对应的 Cookie 来保证安全性)。


通过 ctx.cookies,我们可以在 controller 中便捷、安全的设置和读取 Cookie。


class HomeController extends Controller {  
 async add() {  
 const ctx = this.ctx;  
 let count = ctx.cookies.get('count');  
 count = count ? Number(count) : 0;  
 ctx.cookies.set('count', ++count);  
 ctx.body = count;  
 }  
 async remove() {  
 const ctx = this.ctx;  
 ctx.cookies.set('count', null);  
 ctx.status = 204;  
 }  
}


ctx.cookies.set(key, value, options)


设置 Cookie 其实是通过在 HTTP 响应中设置 set-cookie 头完成的,每一个 set-cookie 都会让浏览器在 Cookie 中存一个键值对。在设置 Cookie 值的同时,协议还支持许多参数来配置这个 Cookie 的传输、存储和权限。


  • {Number} maxAge: 设置这个键值对在浏览器的最长保存时间。是一个从服务器当前时刻开始的毫秒数。


  • {Date} expires: 设置这个键值对的失效时间,如果设置了 maxAge,expires 将会被覆盖。如果 maxAge 和 expires 都没设置,Cookie 将会在浏览器的会话失效(一般是关闭浏览器时)的时候失效。


  • {String} path: 设置键值对生效的 URL 路径,默认设置在根路径上(/),也就是当前域名下的所有 URL 都可以访问这个 Cookie。


  • {String} domain: 设置键值对生效的域名,默认没有配置,可以配置成只在指定域名才能访问。


  • {Boolean} httpOnly: 设置键值对是否可以被 js 访问,默认为 true,不允许被 js 访问。


  • {Boolean} secure: 设置键值只在 HTTPS 连接上传输,框架会帮我们判断当前是否在 HTTPS 连接上自动设置 secure 的值。


除了这些属性之外,框架另外扩展了 3 个参数的支持:


  • {Boolean} overwrite:设置 key 相同的键值对如何处理,如果设置为 true,则后设置的值会覆盖前面设置的,否则将会发送两个 set-cookie 响应头。


  • {Boolean} signed:设置是否对 Cookie 进行签名,如果设置为 true,则设置键值对的时候会同时对这个键值对的值进行签名,后面取的时候做校验,可以防止前端对这个值进行篡改。默认为 true。


  • {Boolean} encrypt:设置是否对 Cookie 进行加密,如果设置为 true,则在发送 Cookie 前会对这个键值对的值进行加密,客户端无法读取到 Cookie 的明文值。默认为 false。


在设置 Cookie 时我们需要思考清楚这个 Cookie 的作用,它需要被浏览器保存多久?是否可以被 js 获取到?是否可以被前端修改?


默认的配置下,Cookie 是加签不加密的,浏览器可以看到明文,js 不能访问,不能被客户端(手工)篡改。


  • 如果想要 Cookie 在浏览器端可以被 js 访问并修改:


ctx.cookies.set(key, value, {  
 httpOnly: false,  
 signed: false,  
});


  • 如果想要 Cookie 在浏览器端不能被修改,不能看到明文:


ctx.cookies.set(key, value, {  
 httpOnly: true, // 默认就是 true  
 encrypt: true, // 加密传输  
});


注意:


  1. 由于浏览器和其他客户端实现的不确定性,为了保证 Cookie 可以写入成功,建议 value 通过 base64 编码或者其他形式 encode 之后再写入。

  1. 由于浏览器对 Cookie 有长度限制限制所以尽量不要设置太长的 Cookie。一般来说不要超过 4093 bytes。当设置的 Cookie value 大于这个值时,框架会打印一条警告日志。


ctx.cookies.get(key, options)


由于 HTTP 请求中的 Cookie 是在一个 header 中传输过来的,通过框架提供的这个方法可以快速的从整段 Cookie 中获取对应的键值对的值。上面在设置 Cookie 的时候,我们可以设置 options.signedoptions.encrypt 来对 Cookie 进行签名或加密,因此对应的在获取 Cookie 的时候也要传相匹配的选项。


  • 如果设置的时候指定为 signed,获取时未指定,则不会在获取时对取到的值做验签,导致可能被客户端篡改。


  • 如果设置的时候指定为 encrypt,获取时未指定,则无法获取到真实的值,而是加密过后的密文。


如果要获取前端或者其他系统设置的 cookie,需要指定参数 signedfalse,避免对它做验签导致获取不到 cookie 的值。


ctx.cookies.get('frontend-cookie', {  
 signed: false,  
});


Cookie 秘钥


由于我们在 Cookie 中需要用到加解密和验签,所以需要配置一个秘钥供加密使用。在 config/config.default.js


module.exports = {  
 keys: 'key1,key2',  
};


keys 配置成一个字符串,可以按照逗号分隔配置多个 key。Cookie 在使用这个配置进行加解密时:


  • 加密和加签时只会使用第一个秘钥。


  • 解密和验签时会遍历 keys 进行解密。


如果我们想要更新 Cookie 的秘钥,但是又不希望之前设置到用户浏览器上的 Cookie 失效,可以将新的秘钥配置到 keys 最前面,等过一段时间之后再删去不需要的秘钥即可。


二、egg的Session操作


Cookie 在 Web 应用中经常承担标识请求方身份的功能,所以 Web 应用在 Cookie 的基础上封装了 Session 的概念,专门用做用户身份识别。


框架内置了 Session插件,给我们提供了 ctx.session 来访问或者修改当前用户 Session 。


class HomeController extends Controller {  
 async fetchPosts() {  
 const ctx = this.ctx;  
 // 获取 Session 上的内容  
 const userId = ctx.session.userId;  
 const posts = await ctx.service.post.fetch(userId);  
 // 修改 Session 的值  
 ctx.session.visited = ctx.session.visited ? (ctx.session.visited + 1) : 1;  
 ctx.body = {  
 success: true,  
 posts,  
 };  
 }  
}


Session 的使用方法非常直观,直接读取它或者修改它就可以了,如果要删除它,直接将它赋值为 null:


ctx.session = null;


需要 特别注意 的是:设置 session 属性时需要避免以下几种情况(会造成字段丢失,详见 koa-session 源码)


  • 不要以 _ 开头


  • 不能为 isNew


// ❌ 错误的用法
ctx.session._visited = 1; // --> 该字段会在下一次请求时丢失
ctx.session.isNew = 'HeHe'; // --> 为内部关键字, 不应该去更改
// ✔️ 正确的用法
ctx.session.visited = 1; // --> 此处没有问题


Session 的实现是基于 Cookie 的,默认配置下,用户 Session 的内容加密后直接存储在 Cookie 中的一个字段中,用户每次请求我们网站的时候都会带上这个 Cookie,我们在服务端解密后使用。Session 的默认配置如下:


exports.session = {  
 key: 'EGG_SESS',  
 maxAge: 24 * 3600 * 1000, // 1 天  
 httpOnly: true,  
 encrypt: true,  
};


可以看到这些参数除了 key 都是 Cookie 的参数,key 代表了存储 Session 的 Cookie 键值对的 key 是什么。在默认的配置下,存放 Session 的 Cookie 将会加密存储、不可被前端 js 访问,这样可以保证用户的 Session 是安全的。


扩展存储


Session 默认存放在 Cookie 中,但是如果我们的 Session 对象过于庞大,就会带来一些额外的问题:


  • 前面提到,浏览器通常都有限制最大的 Cookie 长度,当设置的 Session 过大时,浏览器可能拒绝保存。


  • Cookie 在每次请求时都会带上,当 Session 过大时,每次请求都要额外带上庞大的 Cookie 信息。


框架提供了将 Session 存储到除了 Cookie 之外的其他存储的扩展方案,我们只需要设置 app.sessionStore 即可将 Session 存储到指定的存储中。


// app.js  
module.exports = app => {  
 app.sessionStore = {  
 // support promise / async  
 async get (key) {  
 // return value;  
 },  
 async set (key, value, maxAge) {  
 // set key to store  
 },  
 async destroy (key) {  
 // destroy key  
 },  
 };  
};


sessionStore 的实现我们也可以封装到插件中,例如 egg-session-redis 就提供了将 Session 存储到 redis 中的能力,在应用层,我们只需要引入 egg-redisegg-session-redis 插件即可。


// plugin.js  
exports.redis = {  
 enable: true,  
 package: 'egg-redis',  
};  
exports.sessionRedis = {  
 enable: true,  
 package: 'egg-session-redis',  
};


注意:一旦选择了将 Session 存入到外部存储中,就意味着系统将强依赖于这个外部存储,当它挂了的时候,我们就完全无法使用 Session 相关的功能了。因此我们更推荐大家只将必要的信息存储在 Session 中,保持 Session 的精简并使用默认的 Cookie 存储,用户级别的缓存不要存储在 Session 中。


Session 实践


修改用户 Session 失效时间


虽然在 Session 的配置中有一项是 maxAge,但是它只能全局设置 Session 的有效期,我们经常可以在一些网站的登陆页上看到有 记住我 的选项框,勾选之后可以让登陆用户的 Session 有效期更长。这种针对特定用户的 Session 有效时间设置我们可以通过 ctx.session.maxAge= 来实现。


const ms = require('ms');  
class UserController extends Controller {  
 async login() {  
 const ctx = this.ctx;  
 const { username, password, rememberMe } = ctx.request.body;  
 const user = await ctx.loginAndGetUser(username, password);
// 设置 Session  
 ctx.session.user = user;  
 // 如果用户勾选了 `记住我`,设置 30 天的过期时间  
 if (rememberMe) ctx.session.maxAge = ms('30d');  
 }  
}


延长用户 Session 有效期


默认情况下,当用户请求没有导致 Session 被修改时,框架都不会延长 Session 的有效期,但是在有些场景下,我们希望用户如果长时间都在访问我们的站点,则延长他们的 Session 有效期,不让用户退出登录态。框架提供了一个 renew 配置项用于实现此功能,它会在发现当用户 Session 的有效期仅剩下最大有效期一半的时候,重置 Session 的有效期。


// config/config.default.js  
module.exports = {  
 session: {  
 renew: true,  
 },  
};


三、egg+egg-redis操作redis


1、egg项目中执行


npm i egg-redis --save


2、在// config/plugin.js扩展里设置


exports.redis: {
 enable: true,
 package: 'egg-redis',
},


3、在config/config.default.js里面设置


//config/config.default.js
config.redis = {
      // 单个数据库用client
      client: {
          port: 6379,
          host: '127.0.0.1',
          password: null,
          db: 0,
      }
     // 使用多个数据库连接
      clients: {
          db0: {
            port: 6379,
            host: '127.0.0.1',
            password: null,
            db: 0,
          },
          db1: {
              port: 6379,
              host: '127.0.0.1',
              password: null,
              db: 1,
          }
      }
  }


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1月前
|
NoSQL Java Redis
Springboot从2.x升级到3.x以后redis默认配置调整
Springboot从2.x升级到3.x以后redis默认配置调整
46 0
|
2月前
|
NoSQL Redis
若依管理系统去掉Redis相关配置
若依管理系统去掉Redis相关配置
|
2月前
|
NoSQL Redis 数据安全/隐私保护
Docker中Redis的安装与配置
本文主要讲解如何在Docker环境中搭建Redis环境,并进行相关配置
245 5
Docker中Redis的安装与配置
|
1月前
|
NoSQL Linux Redis
Linux系统中安装redis+redis后台启动+常见相关配置
Linux系统中安装redis+redis后台启动+常见相关配置
|
2月前
|
NoSQL Redis Docker
在docker中安装redis,并且阿里云服务器配置
在docker中安装redis,并且阿里云服务器配置
183 1
|
26天前
|
存储 NoSQL Java
Redis 数据结构操作入门
Redis 数据结构操作入门
15 0
|
1月前
|
NoSQL 关系型数据库 MySQL
Docker安装详细步骤及相关环境安装配置(mysql、jdk、redis、自己的私有仓库Gitlab 、C和C++环境以及Nginx服务代理)
Docker安装详细步骤及相关环境安装配置(mysql、jdk、redis、自己的私有仓库Gitlab 、C和C++环境以及Nginx服务代理)
206 0
|
1月前
|
缓存 NoSQL 数据库
[Redis]——数据一致性,先操作数据库,还是先更新缓存?
[Redis]——数据一致性,先操作数据库,还是先更新缓存?
|
1月前
|
存储 NoSQL Redis
如何在Python中操作Redis数据库
如何在Python中操作Redis数据库
29 0
|
1月前
|
NoSQL 算法 数据管理
【Redis】Redis配置参数详解:优化过期删除机制
【Redis】Redis配置参数详解:优化过期删除机制
69 0

热门文章

最新文章