JJWT 旨在成为最易于使用和理解的库,用于在 JVM 和 Android 上创建和验证 JSON Web 令牌 (JWT) 和 JSON Web 密钥 (JWK)。
JJWT 是完全基于 JOSE 工作组 RFC 规范的纯 Java 实现:
RFC 7519:JSON Web 令牌 (JWT)
RFC 7515:JSON Web 签名 (JWS)
RFC 7516:JSON Web 加密 (JWE)
RFC 7517:JSON Web 密钥 (JWK)
RFC 7518:JSON Web 算法 (JWA)
RFC 7638:JSON Web 密钥指纹
RFC 9278:JSON Web 密钥指纹 URI
RFC 7797:JWS 未编码有效负载选项
RFC 8037:Edwards 曲线算法和 JWK
它由 Les Hazlewood 创建,并由贡献者社区支持和维护。
JJWT 是根据 Apache 2.0 许可证条款开源的。
PublicKey
toString()
安全性在所有 Java 7+ JDK 和 Android 上功能齐全
自动安全最佳实践和断言
易于学习和阅读的 API
方便易读的流畅界面,非常适合IDE自动补全快速编写代码
所有实现的功能完全符合 RFC 规范,并根据 RFC 指定的测试向量进行测试
稳定实现,经过近 1,700 次测试,测试代码覆盖率达到 100%。整个代码库中的每个方法、语句和条件分支变体都经过测试,并且需要在每个构建中传递。
使用所有标准 JWS 算法创建、解析和验证数字签名的紧凑型 JWT(又名 JWS):
标识符 | 签名算法 |
---|---|
| 使用 SHA-256 的 HMAC |
| 使用 SHA-384 的 HMAC |
| 使用 SHA-512 的 HMAC |
| 使用 P-256 和 SHA-256 的 ECDSA |
| 使用 P-384 和 SHA-384 的 ECDSA |
| 使用 P-521 和 SHA-512 的 ECDSA |
| 使用 SHA-256 的 RSASSA-PKCS-v1_5 |
| 使用 SHA-384 的 RSASSA-PKCS-v1_5 |
| 使用 SHA-512 的 RSASSA-PKCS-v1_5 |
| 使用 SHA-256 的 RSASSA-PSS 和使用 SHA-256 1的 MGF1 |
| 使用 SHA-384 的 RSASSA-PSS 和使用 SHA-384 1的 MGF1 |
| 使用 SHA-512 的 RSASSA-PSS 和使用 SHA-512 1的 MGF1 |
| 爱德华兹曲线数字签名算法2 |
1.运行时类路径中需要 Java 11 或兼容的 JCA 提供程序(如 BouncyCastle)。
2 .运行时类路径中需要 Java 15 或兼容的 JCA 提供程序(如 BouncyCastle)。
使用所有标准 JWE 加密算法创建、解析和解密加密的紧凑型 JWT(又名 JWE):
标识符 | 加密算法 |
---|---|
| AES_128_CBC_HMAC_SHA_256认证加密算法 |
| AES_192_CBC_HMAC_SHA_384认证加密算法 |
| AES_256_CBC_HMAC_SHA_512认证加密算法 |
| 使用 128 位密钥1 的AES GCM |
| 使用 192 位密钥1 的AES GCM |
| 使用 256 位密钥1 的AES GCM |
1 .运行时类路径中需要 Java 8 或兼容的 JCA 提供程序(如 BouncyCastle)。
获取JWE加密和解密密钥的所有密钥管理算法:
标识符 | 密钥管理算法 |
---|---|
| RSAES-PKCS1-v1_5 |
| 使用默认参数的 RSAES OAEP |
| 使用 SHA-256 的 RSAES OAEP 和使用 SHA-256 的 MGF1 |
| 使用 128 位密钥的默认初始值的 AES 密钥包装 |
| 使用 192 位密钥的默认初始值的 AES 密钥包装 |
| 使用 256 位密钥的默认初始值的 AES 密钥包装 |
| 直接使用共享对称密钥作为CEK |
| 使用 Concat KDF 的椭圆曲线 Diffie-Hellman 临时静态密钥协商 |
| ECDH-ES 使用 Concat KDF 和 CEK 包裹“A128KW” |
| ECDH-ES 使用 Concat KDF 和 CEK 包裹“A192KW” |
| ECDH-ES 使用 Concat KDF 和 CEK 包裹“A256KW” |
| 使用 128 位密钥1通过 AES GCM 进行密钥包装 |
| 使用 192 位密钥1通过 AES GCM 进行密钥包装 |
| 使用 256 位密钥1通过 AES GCM 进行密钥包装 |
| 具有 HMAC SHA-256 和“A128KW”包装1 的PBES2 |
| 具有 HMAC SHA-384 和“A192KW”包装1 的PBES2 |
| 具有 HMAC SHA-512 和“A256KW”包装1 的PBES2 |
1 .运行时类路径中需要 Java 8 或兼容的 JCA 提供程序(如 BouncyCastle)。
使用本机 Java Key
类型以所有标准 JWA 密钥格式创建、解析和验证 JSON Web 密钥 (JWK):
JWK 密钥格式 | Java Key 类型 | JJWT Jwk 型 |
---|---|---|
对称密钥 |
| |
椭圆曲线公钥 |
| |
椭圆曲线私钥 |
| |
RSA 公钥 |
| |
RSA 私钥 |
| |
XDH 私钥 |
| |
XDH 私钥 |
| |
EdDSA 公钥 |
| |
EdDSA 私钥 |
| |
1 .运行时类路径中需要 Java 15 或兼容的 JCA 提供程序(如 BouncyCastle)。
2 .运行时类路径中需要 Java 15 或兼容的 JCA 提供程序(如 BouncyCastle)。
超出规范的便利增强功能,例如
适用于任何大型 JWT 的有效负载压缩,而不仅仅是 JWE
声明断言(需要具体值)
使用兼容的 JSON 解析器(例如 Jackson)时声明 POJO 编组和解组
基于所需的 JWA 算法的安全密钥生成
还有更多……
非紧凑序列化和解析。
此功能可能会在未来版本中实现。欢迎社区贡献!
如果您在使用 JJWT 时遇到问题,请在提问之前先阅读本页上的文档。我们非常努力地确保 JJWT 的文档是健壮的、按目录分类的,并且每个版本都是最新的。
如果文档或 API JavaDoc 不够充分,并且您有可用性问题或对某些内容感到困惑,请在此处提出您的问题。然而:
请不要创建 GitHub 问题来提出问题。
我们使用 GitHub Issues 来跟踪需要更改 JJWT 设计和/或代码库的可操作工作。如果您有可用性问题,请在此处提出您的问题,如有必要,我们可以将其转换为问题。
如果创建的 GitHub 问题并不代表 JJWT 代码库的可操作工作,它将立即关闭。
如果您没有可用性问题并认为您有合法的错误或功能请求,请首先在此处讨论。请先快速搜索一下,看看是否存在与您相关的现有讨论,如有必要,请加入该现有讨论。
如果您想自己帮助修复错误或实现新功能,请在开始任何工作之前阅读接下来的“贡献”部分。
修复 JJWT 核心代码(文档、JavaDoc、拼写错误、测试用例等)以外的任何内容的简单 Pull 请求总是受到赞赏,并且很有可能被快速合并。请发送给他们!
但是,如果您想要或感觉需要更改 JJWT 的功能或核心代码,请在开始处理之前先启动新的 JJWT 讨论并讨论您所需的更改,然后再发出拉取请求。
如果您真诚且真正欣赏的拉取请求可能与项目的目标、设计期望或计划的功能不符,那么拒绝它将是一种耻辱。遗憾的是,我们过去不得不拒绝大型 PR,因为它们与项目或设计预期不同步 - 所有这些都是因为 PR 作者在制定解决方案之前没有先与团队联系。
因此,请先创建一个新的 JJWT 讨论进行讨论,然后我们可以轻松地将讨论转换为问题,然后查看是否(或如何)有必要提出 PR。谢谢你!
如果您想提供帮助,但不知道从哪里开始,请访问“需要帮助的问题”页面并选择其中的任何问题,我们将很乐意讨论并回答问题评论中的问题。
如果其中任何一个对您没有吸引力,不用担心!根据上述有关贡献拉取请求的注意事项,您想提供的任何帮助将不胜感激。如果您不确定,请先讨论或提出问题。 :)
JSON Web Token (JWT) 是一种通用的基于文本的消息传递格式,用于以紧凑且安全的方式传输信息。与普遍看法相反,JWT 不仅适用于在网络上发送和接收身份令牌 - 即使这是最常见的用例。 JWT 可用作任何类型数据的消息。
最简单形式的 JWT 包含两部分:
JWT 中的主要数据,称为payload
,以及
具有名称/值对的 JSON Object
,表示有关payload
和消息本身的元数据,称为header
。
JWT payload
绝对可以是任何东西 - 任何可以表示为字节数组的东西,例如字符串、图像、文档等。
但由于 JWT header
是 JSON Object
,因此 JWT payload
也可以是 JSON Object
是有意义的。在许多情况下,开发人员喜欢 JSON 格式的payload
,表示有关用户或计算机或类似身份概念的数据。当以这种方式使用时, payload
称为 JSON Claims
对象,该对象中的每个名称/值对称为claim
- “声明”中的每条信息都与身份有关。
虽然“声明”有关身份的某些信息很有用,但实际上任何人都可以做到这一点。重要的是,您可以通过验证这些声明来自您信任的人或计算机来信任这些声明。
JWT 的一个很好的功能是可以通过多种方式保护它们。 JWT 可以进行加密签名(使其成为我们所说的 JWS)或加密(使其成为 JWE)。这为 JWT 增加了一层强大的可验证性 - JWS 或 JWE 接收者可以通过验证签名或解密签名来高度相信它来自他们信任的人。正是这种可验证性功能使 JWT 成为发送和接收安全信息(例如身份声明)的良好选择。
最后,带有空格的 JSON 对于人类可读性来说很好,但它并不能成为一种非常有效的消息格式。因此,JWT 可以被压缩(甚至压缩)为最小表示形式 - 基本上是 Base64URL 编码的字符串 - 因此它们可以更有效地在网络上传输,例如在 HTTP 标头或 URL 中。
一旦有了payload
和header
,它们如何压缩以进行网络传输,最终的 JWT 实际是什么样子的?让我们使用一些伪代码来了解该过程的简化版本:
假设我们有一个带有 JSON header
和简单文本消息负载的 JWT:
标头
{ “alg”:“无” }
有效负载
智慧的真正标志不是知识而是想象力。
删除 JSON 中所有不必要的空格:
String header = ' {"alg":"none"} '
String payload = ' The true sign of intelligence is not knowledge but imagination. '
获取每个字节的 UTF-8 字节和 Base64URL 编码:
String encodedHeader = base64URLEncode( header . getBytes( " UTF-8 " ) )
String encodedPayload = base64URLEncode( payload . getBytes( " UTF-8 " ) )
使用句点(“.”)字符连接编码的标头和声明:
String compact = encodedHeader + ' . ' + encodedPayload + ' . '
最终连接的compact
JWT 字符串如下所示:
eyJhbGciOiJub25lIn0.VGhlIHRydWUgc2lnbiBvZiBpbnRlbGxpZ2VuY2UgaXMgbm90IGtub3dsZWRnZSBidXQgaW1hZ2luYXRpb24u。
这被称为“不受保护”的 JWT,因为不涉及安全性 - 没有数字签名或加密来“保护”JWT 以确保它不能被第 3 方更改。
如果我们想要对紧凑形式进行数字签名,以便至少可以保证在我们检测到数据的情况下没有人会更改数据,那么我们必须执行更多步骤,如下所示。
下一个示例将使用可能最常见的有效负载类型,而不是纯文本有效负载 - 包含有关特定身份的信息的 JSON 声明Object
。我们还将对 JWT 进行数字签名,以确保第三方无法在我们不知情的情况下对其进行更改。
假设我们有一个 JSON header
和一个声明payload
:
标头
{
"alg" : " HS256 "
}
有效负载
{
"sub" : " Joe "
}
在本例中, header
指示将使用HS256
(使用 SHA-256 的 HMAC)算法对 JWT 进行加密签名。此外, payload
JSON 对象有一个声明, sub
的值为Joe
。
说明书中有许多标准权利要求,称为注册权利要求, sub
(“主题”)就是其中之一。
删除两个 JSON 对象中所有不必要的空格:
String header = ' {"alg":"HS256"} '
String claims = ' {"sub":"Joe"} '
获取它们的 UTF-8 字节并对其进行 Base64URL 编码:
String encodedHeader = base64URLEncode( header . getBytes( " UTF-8 " ) )
String encodedClaims = base64URLEncode( claims . getBytes( " UTF-8 " ) )
使用句点字符“.”连接编码的标头和声明分隔符:
String concatenated = encodedHeader + ' . ' + encodedClaims
使用足够强的加密密钥或私钥以及您选择的签名算法(我们将在此处使用 HMAC-SHA-256),并对连接的字符串进行签名:
SecretKey key = getMySecretKey()
byte [] signature = hmacSha256( concatenated, key )
由于签名始终是字节数组,因此对签名进行 Base64URL 编码,并将其连接到带有句点字符“.”的concatenated
字符串。分隔符:
String compact = concatenated + ' . ' + base64URLEncode( signature )
就这样,最终的compact
字符串如下所示:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
这称为“JWS”——签名JWT 的缩写。
当然,没有人愿意在代码中手动执行此操作,更糟糕的是,如果出现任何错误,可能会引入严重的安全问题和弱点。因此,JJWT 的创建是为了为您处理所有这些:JJWT 完全自动化 JWS 的创建以及 JWS 的解析和验证。
到目前为止,我们已经看到了未受保护的 JWT 和加密签名的 JWT(称为“JWS”)。这两者固有的特点之一是,任何人都可以看到其中的所有信息 - 标头和有效负载中的所有数据都是公开可见的。 JWS 只是确保数据没有被任何人更改 - 它不会阻止任何人查看它。很多时候,这很好,因为其中的数据不是敏感信息。
但是,如果您需要在 JWT 中表示被视为敏感信息的信息(可能是某人的邮政地址、社会安全号码或银行帐号),该怎么办?
在这些情况下,我们需要一个完全加密的 JWT,简称为“JWE”。 JWE 使用加密技术来确保有效负载保持完全加密和经过身份验证,因此未经授权的各方无法查看其中的数据,也无法在不被发现的情况下更改数据。具体来说,JWE 规范要求使用关联数据算法的身份验证加密来完全加密和保护数据。
AEAD 算法的完整概述超出了本文档的范围,但以下是使用这些算法的最终紧凑型 JWE 的示例(换行符仅供阅读):
eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0。 6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ。 AxY8DCtDaGlsbGljb3RoZQ。 KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY。 U0m_YmjN04DJvceFICbCVQ
接下来我们将介绍如何在项目中安装 JJWT,然后我们将了解如何使用 JJWT 流畅的 API 而不是危险的字符串操作来快速安全地构建 JWT、JWS 和 JWE。
使用您最喜欢的 Maven 兼容构建工具从 Maven Central 提取依赖项。
如果您使用 JDK 项目或 Android 项目,依赖项可能会略有不同。
如果您正在构建(非 Android)JDK 项目,您将需要定义以下依赖项:
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt-api</ artifactId >
< version >0.12.6</ version >
</ dependency >
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt-impl</ artifactId >
< version >0.12.6</ version >
< scope >runtime</ scope >
</ dependency >
< dependency >
< groupId >io.jsonwebtoken</ groupId >
< artifactId >jjwt-jackson</ artifactId > <!-- or jjwt-gson if Gson is preferred -->
< version >0.12.6</ version >
< scope >runtime</ scope >
</ dependency >
<!-- Uncomment this next dependency if you are using:
- JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
It is unnecessary for these algorithms on JDK 15 or later.
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId> or bcprov-jdk15to18 on JDK 7
<version>1.76</version>
<scope>runtime</scope>
</dependency>
-->
dependencies {
implementation ' io.jsonwebtoken:jjwt-api:0.12.6 '
runtimeOnly ' io.jsonwebtoken:jjwt-impl:0.12.6 '
runtimeOnly ' io.jsonwebtoken:jjwt-jackson:0.12.6 ' // or 'io.jsonwebtoken:jjwt-gson:0.12.6' for gson
/*
Uncomment this next dependency if you are using:
- JDK 10 or earlier, and you want to use RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- JDK 10 or earlier, and you want to use EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- JDK 14 or earlier, and you want to use EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
It is unnecessary for these algorithms on JDK 15 or later.
*/
// runtimeOnly 'org.bouncycastle:bcprov-jdk18on:1.76' // or bcprov-jdk15to18 on JDK 7
}
Android 项目需要定义以下依赖项和 Proguard 排除项,以及可选的 BouncyCastle Provider
:
将依赖项添加到您的项目中:
dependencies {
api( ' io.jsonwebtoken:jjwt-api:0.12.6 ' )
runtimeOnly( ' io.jsonwebtoken:jjwt-impl:0.12.6 ' )
runtimeOnly( ' io.jsonwebtoken:jjwt-orgjson:0.12.6 ' ) {
exclude( group : ' org.json ' , module : ' json ' ) // provided by Android natively
}
/*
Uncomment this next dependency if you want to use:
- RSASSA-PSS (PS256, PS384, PS512) signature algorithms.
- EdECDH (X25519 or X448) Elliptic Curve Diffie-Hellman encryption.
- EdDSA (Ed25519 or Ed448) Elliptic Curve signature algorithms.
** AND ALSO ensure you enable the BouncyCastle provider as shown below **
*/
// implementation('org.bouncycastle:bcprov-jdk18on:1.76') // or bcprov-jdk15to18 for JDK 7
}
您可以使用以下 Android Proguard 排除规则:
-keepattributes 内部类 -keep 类 io.jsonwebtoken.** { *; } -keepnames 类 io.jsonwebtoken.* { *; } -keepnames 接口 io.jsonwebtoken.* { *; } -keep 类 org.bouncycastle.** { *; } -keepnames 类 org.bouncycastle.** { *; } -dontwarn org.bouncycastle.**
如果您想使用 JWT RSASSA-PSS 算法(即PS256
、 PS384
和PS512
)、EdECDH( X25512
或X448
)椭圆曲线 Diffie-Hellman 加密、EdDSA( Ed25519
或Ed448
)签名算法,或者您只是想确保您的 Android应用程序正在运行 BouncyCastle 的更新版本,您将需要:
取消注释 BouncyCastle 依赖项,如上面依赖项部分中所述。
将旧版 Android 自定义BC
提供程序替换为更新后的提供程序。
提供程序注册需要在应用程序生命周期的早期完成,最好在应用程序的主Activity
类中作为静态初始化块完成。例如:
class MainActivity : AppCompatActivity () {
companion object {
init {
Security .removeProvider( " BC " ) // remove old/legacy Android-provided BC provider
Security .addProvider( BouncyCastleProvider ()) // add 'real'/correct BC provider
}
}
// ... etc ...
}
请注意,上述 JJWT 依赖项声明都只有一个编译时依赖项,其余的都声明为运行时依赖项。
这是因为 JJWT 的设计使您仅依赖于明确设计供您在应用程序中使用的 API,而所有其他内部实现细节(可能会在没有警告的情况下更改)都被降级为仅运行时依赖项。如果您想确保 JJWT 的稳定使用和随着时间的推移进行升级,这是非常重要的一点:
JJWT 保证除 |
这样做是为了让您受益:非常小心地管理jjwt-api
.jar 并确保它包含您需要的内容并尽可能保持向后兼容,以便您可以在编译范围内安全地依赖它。运行时jjwt-impl
.jar 策略为 JJWT 开发人员提供了根据需要随时更改内部包和实现的灵活性。这有助于我们更快、更高效地实现功能、修复错误以及向您发布新版本。
大多数复杂性都隐藏在基于构建器的方便且可读的流畅界面后面,非常适合依靠 IDE 自动完成来快速编写代码。这是一个例子:
import io . jsonwebtoken . Jwts ;
import io . jsonwebtoken . security . Keys ;
import java . security . Key ;
// We need a signing key, so we'll create one just for this example. Usually
// the key would be read from your application configuration instead.
SecretKey key = Jwts . SIG . HS256 . key (). build ();
String jws = Jwts . builder (). subject ( "Joe" ). signWith ( key ). compact ();
那是多么容易啊!
在这种情况下,我们是:
构建一个 JWT,将注册的声明sub
(主题)设置为Joe
。我们那时
使用适合 HMAC-SHA-256 算法的密钥对 JWT进行签名。最后,我们是
将其压缩为最终的String
形式。签名的 JWT 称为“JWS”。
生成的jws
字符串如下所示:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKb2UifQ.1KP0SsvENi7Uz1oQc07aXTL7kpQG5jBNIybqr60AlD4
现在让我们验证 JWT(您应该始终丢弃与预期签名不匹配的 JWT):
assert Jwts . parser (). verifyWith ( key ). build (). parseSignedClaims ( jws ). getPayload (). getSubject (). equals ( "Joe" );
这里发生了两件事。之前的key
用于验证 JWT 的签名。如果无法验证 JWT,则会抛出SignatureException
(扩展JwtException
)。假设 JWT 已验证,我们解析声明并断言该主题设置为Joe
。您一定会喜欢那些充满冲击力的俏皮话代码!
笔记 | 类型安全 JWT:要获取类型安全的 |
但是如果解析或签名验证失败怎么办?您可以捕获JwtException
并做出相应的反应:
try {
Jwts . parser (). verifyWith ( key ). build (). parseSignedClaims ( compactJws );
//OK, we can trust this JWT
} catch ( JwtException e ) {
//don't trust the JWT!
}
现在我们已经快速入门了如何创建和解析 JWT,接下来让我们深入介绍 JJWT 的 API。
您可以按如下方式创建 JWT:
使用Jwts.builder()
方法创建JwtBuilder
实例。
可以根据需要设置任何header
参数。
调用构建器方法来设置有效负载内容或声明。
如果您想对 JWT 进行数字签名或加密,可以选择调用signWith
或encryptWith
方法。
调用compact()
方法来生成结果紧凑的JWT字符串。
例如:
String jwt = Jwts . builder () // (1)
. header () // (2) optional
. keyId ( "aKeyId" )
. and ()
. subject ( "Bob" ) // (3) JSON Claims, or
//.content(aByteArray, "text/plain") // any byte[] content, with media type
. signWith ( signingKey ) // (4) if signing, or
//.encryptWith(key, keyAlg, encryptionAlg) // if encrypting
. compact (); // (5)
JWT payload
可以是byte[]
内容(通过content
)或JSON 声明(例如subject
、 claims
等),但不能同时是两者。
可以使用数字签名 ( signWith
) 或加密 ( encryptWith
),但不能同时使用两者。
不受保护的 JWT :如果您不使用 |
JWT 标头是一个 JSON Object
,它提供有关内容、格式以及与 JWT payload
相关的任何加密操作的元数据。 JJWT 提供了多种设置整个标头和/或多个单独标头参数(名称/值对)的方法。
设置一个或多个 JWT 标头参数(名称/值对)的最简单且推荐的方法是根据需要使用JwtBuilder
的header()
构建器,然后调用其and()
方法返回JwtBuilder
进行进一步配置。例如:
String jwt = Jwts . builder ()
. header () // <----
. keyId ( "aKeyId" )
. x509Url ( aUri )
. add ( "someName" , anyValue )
. add ( mapValues )