了解Cookie、Session、JWT
Cookie
是服务器在本地机器上存储的一小段文本信息,第一次登录后,由服务端通过响应报文向客户端浏览器发送一个叫做Set-Cookie的首部字段信息,客户端会把Cookie保存在本地。当 浏览器再次请求时,浏览器会自动将Cookie一同提交给服务器,服务器便可以通过Cookie识别用户身份。
仅适用cookie来保存用户登录信息,会有一些弊端
- 不安全,Cookie可能被截取篡改 ,因此不能存放敏感数据
- Cookie只能保存少量的数据,一般不超过4KB,Cookie 的数量也有限制,无法保存大量信息。
- Cookie可以被禁用
- 不允许跨域携带Cookie
Session
被称为“会话控制”,代表服务器与浏览器的一次会话过程,Session是一种服务器端的机制,Session 对象存储用户会话所需的属性,比如登录信息,存储在服务器端的内存、缓存比如Memcache、或者数据库里,并给每个客户端分配唯一的SessionId作为身份标识,客户端每次向服务器发请求的时候,都带上SessionId,服务器就知道这个请求来自于谁了。至于客户端怎么保存这个身份标识,可以有很多种方式,最常见的是采用 Cookie 的方式,类似下图。 使用Session结合Cookie的方式,实现了对客户端的请求的状态维持,但是由于Session存储在服务器上,如果 Web 服务器使用了负载均衡,那么下一个操作请求到了另一台服务器的时候Session会丢失,导致状态维持失效。一种办法是Session 数据持久化,写入数据库或别的持久层。还有办法是如果能让服务器不保持相关状态(即服务器无状态),就不会出现这个问题。
JWT(JSON Web Token)
Token
在了解JWT前,需要了解Token,Token是服务端生成的一串字符串,通常作为访问资源接口(API)时所需要的资源凭证。通过一次登录验证,服务器得到一个鉴权字符串Token,后续在浏览器和服务器交互的过程中,在请求头中带上Token传到服务端,服务端再获取Token进行校验,完成身份验证。
JWT
JWT是一种规范化的 Token,JWT包含三个部分: Header头部,Payload荷载和Signature签名。 由三部分生成JWT,三部分之间用“.”号做分割。
- Header
描述关于该 JWT 的最基本信息,例如其类型以及签名所使用的算法
- Payload
用来存放实际需要传递的数据,有以下官方字段,但不是强制的
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
- Signature
是对前两部分的签名,防止数据篡改 需要指定一个密钥,这个密钥放在服务端,不能泄露给用户。然后,使用 Header 里面指定的签名算法,按照公式产生签名
根据案例中的配置信息,最终产生的JWT如下
JWT的组成和生成方式,已经了解差不多,那么JWT的使用方式,可以看下面的图,非常直观地展示了一般使用JWT作为登录状态保持的实现方式。
注意
JWT 默认是不加密的,任何人拿到JWT字符串,都可以通过解码获取其中的内容,造成信息泄露,比如,我拿到前面生成的JWT中间Payload部分(也就是我们存用户信息的部分),通过base64解码,就可以直接拿到用户信息,包括id,name,email等。
-
所以未对JWT进行加密的情况下,不应该在JWT中存放一些敏感信息(非常重要),所以可以在获取Token后再次进行加密。
-
保护好签名密钥,不能对外泄露,因为一般签名算法类型使用的是公开的几种,非常容易猜中你使用的是哪一种,如果知道了签名密钥,就可以对JWT进行伪造和篡改,造成安全问题。
JWT相对于Cookie+Session的方式,有一些优点:
- 不依赖Cookie, JWT 只要客户端能够进行存储就能够使用,所以没有因为Cookie被禁用导致的问题。
- JWT的鉴权机制是无状态的,不需要在服务端去保留用户的认证信息或者会话信息,在负载均衡下,不需要考虑到另一台服务器时Session丢失的情况。
- 跨域认证,因为并不依赖 Cookie ,所以你可以使用任何域名提供你的 API 服务而不需要担心跨域资源共享问题(CORS)
代码实现
前面说了很多基础的知识,对Cookie,Session,JWT等登录验证的方式都做了了解。下面说下具体到我的博客后台系统中是怎么实现的,这里采用的是JWT的方式。
配置签名密钥
上一篇说到项目的配置项是放在.env中,通过@nestjs/config来读取的,在.env中增加一个配置项用于存放生成JWT令牌的密钥,这个密钥需要妥善保存。
登录接口
在用户模块的控制器中添加登录接口,在完成用户名密码的校验之后,调用自定义方法生成JWT并返回到前端。
创建JWT
注入ConfigService并读取Secret,借助jsonwebtoken这个三方库生成JWT,在JWT内容中保存了用户的id,并设置过期时间。
登录完成后浏览器拿到服务端给的JWT字符串,存在localstorage中,后面每次请求接口,都会在请求头中带上,一般放在authorization字段。
AuthMiddleware
接下来写一个通用中间件,在需要授权的路由处理前做用户信息认证,也就是在这里拿到前端请求头上携带的JWT,再获取Secret做认证,拿到JWT中的用户信息,验证通过则将用户信息放入当前请求对象中,便于在后面的上下文中使用。这样就完成了JWT登录状态保持的实现。
其他
方便后面获取用户id在业务代码中使用,可以使用nest中的自定义decorator,使代码十分简洁。