背景
由于http协议是无状态的,互联网中为了区分用户以及保护用户信息,所以产生了会话管理。目前主要的会话管理实现有两种:
- session:基于服务器存储来认证会话
- token:基于校验token来认证会话
本文主要讲第二种token的实现方案JWT
JWT介绍
JWT全称为JSON Web Tokens。从它的名称可以看出这是一种基于json的互联网通信认证方案,一个很常见的JWT像下面这样。
1 | 复制代码eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c |
注意看一下它被两个.
号分隔成了三段,第一段是header, 第二段是payload, 第三段是signature。
所以整体的形式是:
1 | css复制代码header.payload.signature |
JWT构成
header
header是一段base64Url编码的字符串。它的原始内容是json,通常包含两部分内容。第一部分是使用的签名算法,一般可选的有HS256(HMAC-SHA256) 、RS256(RSA-SHA256) 、ES256(ECDSA-SHA256);第二部分是固定的类型,直接就是”JWT”。
比如:
1 | json复制代码{ |
以上json通过base64Url编码就形成了第一段header。
payload
payload 同样是一段base64Url编码的字符串,一般是用来包含实际传输数据的。payload段官方提供了7个字段可以选择。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
上面这些字段都不是必选的,除此之外,由于这是载荷段,用户可以添加自定义的字段。实际场景中的一个payload例子:
1 | json复制代码{ |
signature
signature是签名字段。它是使用header中声明的签名算法,并使用一个secret(秘钥),对base64Url编码的header json 和 base64Url编码 payload json进行签名后的数据。伪代码如下
1 | json复制代码HMACSHA256( |
注意secret必须保存在服务端,不能泄露。
最后将三段内容拼接起来header + “.” + paylod + “.” + signatrue, 就是一个完整的JWT token了。
JWT使用与原理
生成了jwt token之后,接下来就是如何使用了。一般来说,客户端会在通信的时候放在http的请求头
Authrization字段中,形式如下:
1 | json复制代码Authorization: Bearer <token> |
当然,这不是强制要求,你也可以把它放在一个自定义的头字段中。说实话我之前就对这个前缀Bearer感到好奇,为什么要加这么一个字符串?因为这个Bearer造成我取出header之后还要截取才能拿到token。后来发现有一个具体的RFC文档RFC6750对此作了约定,然而看完这个RFC文件我依然没有理解。最终我得出的一个结论是:某些框架可能是按这个协议实现的,如果你使用现成框架提取token,最好还是按约定的形式来传输;如果是自己写轮子提取的,可以无视。 另外Postman快捷添加鉴权信息也是默认的Authorization: Bearer <token>
。
服务端拿到token之后,根据同样的算法,将header 和 payload签名之后与signature比对来确认token的有效性。如果比对通过,就取出payload中的用户数据进行后续操作,如果不通过就认证失败。
值得注意的是,有许多朋友认为JWT token的信息都是加密的,实际上这种观点是错误的。除了signature是哈希散列值,header和payload都是可以直接解码的,前面我专门加粗标注了编码就是为了引起大家的注意,随便一个合格的token,不需要secret,使用base64Url 就能解码出header和payload看出里面的数据。token校验的过程也是一个验签的过程,而不是解密的过程。
JWT与session比较
JWT的优点
- 认证信息保存在token中,不需要服务端存储,节约资源
- 传输放置在请求头中,天然支持跨域携带,不存在cors问题
- 支持分布式、集群,无扩展问题
- 不需要cookie支持,所以不存在csrf(跨站请求伪造)问题
JWT的不足
- token一经签发,即使用户登出,有效期内还是能使用,有一定安全风险。(可以通过减少token有效期并配合refresh token来减小风险,后续有时间再细讲)
- token放在请求头,如果payload数据放太多的话,会导致token过长,影响包传输效率。(所以尽量少放自定义数据,我一般放个userId就够了)
session的缺点
- 一般存储在内存中,用户量大时,占用计算机资源
- 对于分布式、集群应用来说,需要引入组件处理,如: redis。且redis挂了可能导致整个系统认证不可用
- 基于cookie实现,用户有禁用cookie的可能
- 基于cookie实现,所以有csrf的问题,需要处理
session的优点
- 框架支持友好,很多框架直接set、get就行了。
- 登出即可失效sessionID
JWT结合springboot实践
这里提供一个快捷的springboot使用jwt完成认证的方案,详细的实现可以参考这个项目: 体验, bytemall
定义JWT工具类
pom中导入jwt包
1 | json复制代码<!-- jwt --> |
定义工具类
1 | java复制代码@Component |
定义登录注解
1 | java复制代码@Target(ElementType.PARAMETER) |
增加一个自定义ArgumentResolver
1 | java复制代码@Component |
将自定义的resolver 配置到mvcConfiguration
1 | java复制代码@Configuration |
controller中使用
登录
1 | java复制代码@RestController |
使用
1 | java复制代码// 在具体的路由函数中使用定义的注解就可以了 |
总结
套用一个装逼的词,会话管理没有银弹! 无论是session还是JWT都有各自优缺点,正确的做法是根据实际的业务场景需求,选择合适的方案。JWT,你学废了吗?
本文转载自: 掘金