jwt_jjwt使用和原理

jwt_jjwt使用和原理

jwt_jjwt使用教程

jwt介绍

Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。

JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT 是目前最流行的跨域认证解决方案,本文介绍它的原理和用法。

官网:https://jwt.io/

jjwt介绍

JSON Web Token (JWT) 是一种用于在网络应用间安全传输信息的开放标准。jjwt 是一个 Java 实现的 JWT 库,提供了创建、解析和验证 JWT 的功能。

数据结构

file
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。

JWT 的三个部分依次如下:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

写成一行为:Header.Payload.Signature

1、Header

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

{
  "alg": "HS256",
  "typ": "JWT"
}

上面代码中,alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT

最后,将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串,从而形成JWT的第一部分。

2、 Payload

令牌的第二部分是负载,其中包含声明。声明是关于实体(通常是用户)和附加元数据的声明。存在三种类型的声明:reserved、public和private声明。

Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。

1)保留声明:这些是一组预定义的声明,这些声明不是强制性的,而是推荐的,旨在提供一组有用的、可互操作的声明。JWT 规定了7个官方字段,供选用,如:

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

请注意,声明名称只有三个字符,因为 JWT 是紧凑的

2)公共声明:这些可以由使用 JWT 的人随意定义。但是为了避免冲突,它们应该在 IANA JSON Web Token Registry 中定义或定义为包含抗冲突命名空间的 URI。

3)私人声明:这些自定义声明是为了在同意使用它们的各方之间共享信息而创建的。

{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。

同样的,这个 JSON 对象也要使用 Base64URL 算法转成字符串,从而形成JWT的第二部分。

3、 Signature

Signature 部分是对前两部分的签名,防止数据篡改。

首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

4、Base64URL

前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。

JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。

原理

file

解决了跨域认证的问题

互联网服务离不开用户认证。一般流程是下面这样。

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。
  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。

举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

  • 一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
  • 另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

JWT 的原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

{
  "姓名": "张三",
  "角色": "管理员",
  "到期时间": "2018年7月1日0点0分"
}

以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。

服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
(把数据从统一存储变成不存储只判断用户的是否正确)

常用 API

JWTS

Jwts类是一个工厂类,用于创建JWT接口的实例。使用这个工厂类可以有效地避免将代码紧密耦合到实现类上。

方法名 返回类型 描述
builder() JwtBuilder 返回一个新的JwtBuilder实例,可以进行配置并用于创建JWT的紧凑序列化字符串。
claims() Claims 返回一个新的Claims实例,用作JWT的主体部分。
claims(Map<String,Object> claims) Claims 返回一个新的Claims实例,并使用指定的名称/值对进行填充。
header() Header 创建一个适用于纯文本(未进行数字签名)JWT的新Header实例。如果之后将对JWT进行数字签名,请考虑使用jwsHeader()工厂方法。
header(Map<String,Object> header) Header 创建一个适用于纯文本(未进行数字签名)JWT的新Header实例,并使用指定的名称/值对进行填充。如果之后将对JWT进行数字签名,请考虑使用jwsHeader(Map<String,Object> header)工厂方法。
jwsHeader() JwsHeader 返回一个适用于进行数字签名的JWT(也称为’JWS’)的新JwsHeader实例。
jwsHeader(Map<String,Object> header) JwsHeader 返回一个适用于进行数字签名的JWT(也称为’JWS’)的新JwsHeader实例,并使用指定的名称/值对进行填充。
parser() JwtParser (已弃用)返回一个新的JwtParser实例,可以进行配置并用于解析JWT字符串。请使用parserBuilder()方法替代。
parserBuilder() JwtParserBuilder 返回一个新的JwtParserBuilder实例,可以进行配置以创建一个不可变/线程安全的JwtParser

注意:parser()方法将在1.0版本之前移除,推荐使用parserBuilder()方法进行替代。

参考资料:Class Jwts

JwtBuilder

Interface JwtBuilder 是一个用于构建 JWT(JSON Web Token)的接口。

方法名 返回类型 描述
JwtBuilder setHeader(Header header) JwtBuilder 设置 JWT 的头部。
JwtBuilder setHeader(Map<String,Object> header) JwtBuilder 设置 JWT 的头部。
JwtBuilder setHeaderParams(Map<String,Object> params) JwtBuilder 向 JWT 的头部添加参数。
JwtBuilder setHeaderParam(String name, Object value) JwtBuilder 向 JWT 的头部添加一个参数。
JwtBuilder setPayload(String payload) JwtBuilder 设置 JWT 的负载为纯文本(非 JSON)字符串。
JwtBuilder setClaims(Claims claims) JwtBuilder 将 JWT 的负载设置为 JSON Claims 实例。
JwtBuilder setClaims(Map<String,?> claims) JwtBuilder 将 JWT 的负载设置为通过指定的名称/值对填充的 JSON Claims 实例。
JwtBuilder addClaims(Map<String,Object> claims) JwtBuilder 将所有给定的名称/值对添加到 JWT 的 JSON Claims 中。
JwtBuilder setIssuer(String iss) JwtBuilder 设置 JWT 的 iss (issuer) 值。
JwtBuilder setSubject(String sub) JwtBuilder 设置 JWT 的 sub (subject) 值。
JwtBuilder setAudience(String aud) JwtBuilder 设置 JWT 的 aud (audience) 值。
JwtBuilder setExpiration(Date exp) JwtBuilder 设置 JWT 的 exp (expiration) 值。
JwtBuilder setNotBefore(Date nbf) JwtBuilder 设置 JWT 的 nbf (not before) 值。
JwtBuilder setIssuedAt(Date iat) JwtBuilder 设置 JWT 的 iat (issued at) 值。
JwtBuilder setId(String jti) JwtBuilder 设置 JWT 的 jti (JWT ID) 值。
JwtBuilder claim(String name, Object value) JwtBuilder 设置自定义的 JWT Claims 参数值。
JwtBuilder signWith(Key key) JwtBuilder 使用密钥的推荐签名算法对构建的 JWT 进行签名,生成 JWS。如果推荐的签名算法不满足需求,可以考虑使用 signWith(Key, SignatureAlgorithm) 方法。
JwtBuilder compact() String 构建 JWT 并将其序列化为紧凑、URL 安全的字符串,符合 JWT 紧凑序列化规则。

JWT 规定了7个官方字段,供选用,如:

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号

参考资料:Interface JwtBuilder

案例

下面是一个使用 jjwt 创建和解析 JWT 的简单示例:

// 创建 JWT
String secretKey = "mySecretKey";
String jwt = Jwts.builder()
                .setSubject("user123")
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();

// 解析 JWT
Claims claims = Jwts.parserBuilder()
                    .setSigningKey(secretKey)
                    .build()
                    .parseClaimsJws(jwt)
                    .getBody();
String subject = claims.getSubject();

注意事项

  1. 使用JJWT之前,需要导入三个依赖:jjwt-api, jjwt-impl, jjwt-jackson(或jjwt-gson)
  2. 选择合适的签名算法和密钥,以保证JWT的安全性和完整性
  3. 使用JwtBuilder构建JWT时,可以设置标准的或自定义的声明,也可以设置任意的JSON内容
  4. 使用JwtParser解析JWT时,可以指定一个Key或者一个KeyLocator来查找验证签名所需的密钥
  5. 除了签名,JJWT还支持加密(JWE),可以使用不同的加密算法和密钥管理算法来保护JWT的内容
  6. JJWT还支持JSON Web Key(JWK),可以用来生成,读取,转换和指纹化密钥

jjwt使用场景

在前后端项目中,jjwt用于用户认证服务
想过滤器一样,把所有需要用户认证的服务通过jjwt访问
file

补充资料: