前言
前几天在写微信小程序的时候,发现微信小程序不支持Cookie导致我的Session无法正常获取,查阅了资料后,找到了之前用过的jwt,之前没怎么写过,写篇博客来记录下。
跨域认证的问题
互联网服务离不开用户认证。一般流程是下面这样。
1、用户向服务器发送用户名和密码。
2、服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
3、服务器向用户返回一个 session_id,写入用户的 Cookie。
4、用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。
通俗来讲
把一段用户信息和密匙一起加密,作为签名,然后穿给前端,前端每次请求都带上token,后端接到token用密匙去验证是不是自己签出来的信息
什么是JWT
JSON Web Token其实就是一个包含认证数据的JSON
由三部分组成,并按.分隔
- Header (头部)
- Payload (负载)
- Signature (签名)
写成一行 就是这样
Header.Payload.Signature
Header
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。
Header 通常由两部分组成
alg属性表示签名的算法(algorithm)默认是 HMAC SHA256(写成 HS256)还有比如说 HMAC、SHA256、RSA
typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
1 | { |
之后,将上面的 JSON 对象使用 Base64URL 算法转成字符串,作为JWT的第一部分。
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
当然,除了官方的字段,你随便方什么都可以
1 | { |
这里要注意的是,JWT默认是不加密的,所有人都可以看到你的内容,所以说不要把信息放在这个地方.
这个 JSON 对象也要使用 Base64URL 算法转成字符串。
Signature
第三部分 base64 解密出来大约是这样子
1 | )4'7�6-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l |
非常重要,是签名Signiture, 服务器会验证这个以防伪造. 因为JWT其实是明文传送, 任何人都能篡改里面的内容. 服务端通过验证签名, 从而确定这个JWT是自己生成的.
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
1 | HMACSHA256( |
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。
就是这个样子
1 | int signiture = ("{alg:HS512."typ": "JWT"}{exp:1495176357,"UserId": "00001","admin": true}" + key).hashCode(); |
附上签名之后
1 | {"alg":"HS512"}{"exp":1495176357,"username":"admin"} ')4'7�6-DM�(�H6fJ::$c���a4�~tI2%Xd-�$nL(l |
为了方便复制和使用, 通常我们都是把JWT用base64编码之后放在http的header里面, 并且每一次呼叫api都附上这个JWT, 并且服务器每次也验证JWT是否过期
实现
扯完了原理之后,就要来讲讲怎么实现JWT,这里我参考了
JonTian大佬的代码 稍微改动了下,适合自己的程序。
后端
如图是我的目录结构,security包
源代码在这里
这里我允许了 /login/* 和 /msg/* 无需验证直接可以访问
这里设置了密匙和Header头的标识
1 | String jwt = JwtUtil.generateToken(userT.getStuId()); |
使用如下代码即可添加userid进jwt
1 | @RequestHeader(value = ROLE) String userId |
用RequestHeader来接受参数,这里的用户信息已经经过jwt验证
前端
在前端http请求中加入header头 Authorization
这里我拿uni-app 举个栗子
在登陆成功后保存token到vuex
储存方法
在请求的时候加入header头
1 | 'Authorization': this.token //添加token |
优点
- 在分布式的情况下无需在服务器共享session
- 前后端分离微信小程序无法使用cookie的问题
参考
JWTIO
Using JWT with Spring Security OAuth
springboot-jwt
Spring Boot用3个class轻松实现JWT (一), 保护你的RESTful API
Spring Boot用3个class轻松实现JWT (二) 鉴权, 给JWT添加业务信息
Spring Boot用3个class轻松实现JWT (三) 鉴权, 和Spring Security集成在一起
JonTian大佬的代码