keac's Bolg.

jwt SrpingBoot

字数统计: 1.5k阅读时长: 5 min
2019/06/22 Share

前言

前几天在写微信小程序的时候,发现微信小程序不支持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

JWT编码

Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子。

Header 通常由两部分组成

alg属性表示签名的算法(algorithm)默认是 HMAC SHA256(写成 HS256)还有比如说 HMAC、SHA256、RSA

typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

之后,将上面的 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
2
3
4
5
{
"exp":1495176357,
"UserId": "00001",
"admin": true
}

这里要注意的是,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
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

算出签名以后,把 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大佬的代码

原文作者:keacwu

原文链接:http://www.loongten.com/2019/06/22/jwt/

发表日期:June 22nd 2019, 1:43:10 pm

更新日期:June 22nd 2019, 4:22:58 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 前言
  2. 2. 跨域认证的问题
  3. 3. 通俗来讲
  4. 4. 什么是JWT
    1. 4.1. Header
    2. 4.2. Payload
    3. 4.3. Signature
  5. 5. 实现
    1. 5.1. 后端
    2. 5.2. 前端
  6. 6. 优点
  7. 7. 参考