简介
JSON Web Token (JWT)是一种开放标准(RFC 7519),它作为 JSON 对象定义了一种紧凑和独立的方式,用于在各方之间安全地传输信息。这个信息可以被验证和信任,因为它是经过数字签名的。JWT可以使用秘钥(使用HMAC算法)或使用 RSA 或ECDSA的公钥/ 私钥 对进行签名。
尽管JWT可以通过加密以在各方之间提供保密安全,但我们将重心放在已签名的令牌上。签名的令牌可以验证其中包含的声明的完整性,而加密的令牌则会向其他方隐藏这些声明。当令牌使用公钥/私钥对签名时,签名还可以证明只有持有私钥的一方是签名的一方。
怎么使用JSON Web Token(JWT)?
以下是一些JSON WebToken使用的场景:
- 授权: 这是使用JWT最常见的场景。一旦用户登录,后续每个请求都将包括JWT,允许用户通过该令牌访问对应权限内的的路由、服务和资源。单点登录是目前广泛使用JWT的一个特性,因为它的开销很小,而且能够方便地跨域使用。
- 数据交换: JSON Web Token是在各方之间安全地传输信息的一种好方法。因为JWT可以被签名(例如,使用公钥/私钥对),所以您可以确定发送者是否是受信任的那个人。
JSON Web Token的内容构成
在其紧凑的形式中,JSON Web Token是由英文逗号(.)分隔的三个部分组成,它们是:
- 头
- 载荷
- 签名
因此,JWT形势通常如下所示:
aaaaa.bbbbb.ccccc
让我们分解一下不同的部分。
头
头通常由两部分组成:令牌的类型,即JWT,以及所使用的签名算法,如HMAC SHA256或RSA。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,该JSON是Base64Url 编码 的,以形成JWT的第一部分。
载荷
令牌的第二部分是有效载荷,它包含声明。声明是关于实体(通常是用户)和附加数据的声明。声明有三种类型:注册声明、公开声明和私人声明。
- 注册声明: 这些是一组预定义的声明要求,它们不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明要求。其中包括:iss(发行人)、exp(到期时间)、sub(主题)、aud(用户)和其他内容,请参考#section-4.1。
注意,声明名只有三个字符的长度,因为JWT是为了紧凑。
- 公开声明: 这些可以由使用JWT的人随意定义。但是为了避免冲突,应该在IANA JSON Web Token注册表()中定义它们,或者将它们定义为包含避免冲突名称空间的URI。
- 私人声明: 这些自定义声明是为了在同意使用它们的各方之间共享信息而创建的,它们既不是注册的声明,也不是公开的声明。
一个有效载荷的例子如下:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对有效载荷进行Base64Url编码,以形成JSON Web Token的第二部分。
请注意,对于已签名的令牌,该信息虽然受到保护,不会被篡改,但任何人都可以读懂。不要将机密信息放在JWT的有效载荷或头元素中,除非它被加密了。
签名
要创建签名部分,您必须获取已编码的头、已编码的有效负载、一个秘钥、头中指定的算法,并对其进行签名。
例如,使用HMAC SHA256算法时,签名的生成方式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在整个过程中没有被篡改,而且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发送方是它所受信任的那个人。
拼接到一起
输出的内容是用三个英文逗点分隔的Base64-URL字符串,它们可以很容易地在HTML和HTTP环境中传递,同时与基于xml的标准(如SAML)相比更紧凑。
下面展示了一个JWT,它对前面的头和有效载荷进行了编码,并使用秘钥对其进行了签名。
Json Web Token 示例
如果您想要使用JWT并将这些概念付诸实践,可以使用 jwt.io Debugger(#debugger-io)来解码、验证和生成JWT。
JWT在线工具
JSON Web Token如何工作?
在身份验证中,当用户使用他们的凭据成功登录时,将返回一个JSON Web Token。由于令牌是凭证,所以必须非常小心地防止出现安全问题。一般来说,您不应该保存token超过所需要的时间。
由于缺乏安全性,您也不应该在浏览器存储中存储敏感会话数据(#local-storage)。
当用户希望访问受保护的路由或资源时,用户代理应该发送JWT,通常在使用Bearer模式的Authorization头中发送。请求头的内容应该如下所示:
Authorization: Bearer <token>
在某些情况下,这可以是一种无状态授权机制。
服务器受保护的路由将在Authorization请求头中检查有效的JWT,如果存在,则允许用户访问受保护的资源。如果JWT包含必要的数据,则可能减少对数据库查询某些操作的需求,但情况并非总是如此。
如果令牌是在Authorization头中发送的,那么跨源资源共享(CORS)就不是问题,因为它不使用cookie。
下面的图表显示了JWT是如何被获取和使用来访问应用api或资源的:
JWT使用流程
1.应用程序或客户端向授权服务器发送授权请求。这是通过一个不同的授权流执行的。例如,一个典型的OpenID Connect()兼容的web应用程序将通过/oauth/authorize端点使用授权代码流(#CodeFlowAuth)。
2.授予授权后,授权服务器向应用程序返回一个访问令牌。
3.应用程序使用访问令牌访问受保护的资源(如API)。
请注意,对于已签名的令牌,令牌中包含的所有信息都将暴露给用户或其他方,即使他们无法更改它。这意味着您不应该在令牌中放置机密信息。
为什么使用JSON Web Token?
让我们来谈谈与简单Web令牌( SWT )和Security Assertion Markup Language Tokens (SAML)相比,JSON Web Tokens (JWT)的好处。
由于JSON比XML更简洁,因此在编码时它的大小也更小,这使得JWT比SAML更紧凑。这使得JWT成为在HTML和HTTP环境中进行传输最好的选择。
在安全方面,SWT只能通过使用HMAC算法的共享秘钥进行对称签名。但是,JWT和SAML令牌可以使用X.509证书形式的公钥/私钥对进行签名。
与签名JSON的简单性相比,使用XML数字签名在不引入隐藏的安全漏洞的情况下签名XML是非常困难的。
JSON解析器在大多数编程语言中都很常见,因为它们直接映射到对象。相反,XML没有一个自然的文档到对象映射。这使得使用JWT比使用SAML断言更容易。
关于使用,JWT是在互联网规模上使用的。这突出了JSON Web Token在多个平台上(尤其是移动平台)客户端处理的易用性。
编码的JWT和编码的SAML的长度比较
如果你想阅读更多关于JSON Web令牌的信息,甚至开始使用它们在你自己的应用程序中执行身份验证,请浏览到Auth0的JSON Web Token登陆页面()。
Java 环境下使用java-jwt对Json Web Token生成及验证
- 可用的算法
这个库使用以下算法实现了JWT验证和签名:
HS256
算法:HMAC256
描述:使用SHA-256进行HMAC的算法
RS512
算法:RSA512
描述:使用SHA-512进行RSASSA-PKCS1-v1_5算法
ES256
算法:ECDSA256
描述:使用曲线P-256和SHA-256进行ECDSA算法
ES256K
算法:ECDSA256
描述:使用曲线secp256k1和SHA-256进行ECDSA算法
ES384
算法:ECDSA384
描述:使用曲线P-384和SHA-384进行ECDSA算法
ES512
算法:ECDSA512
描述:使用曲线P-521和SHA-512进行ECDSA算法
- 引用
Java的Json Web Token类库推荐:
通过maven引入依赖:
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.15.0</version>
</dependency>
- 选择算法
该算法定义了如何对令牌进行签名和验证。在HMAC算法中可以使用密钥的原始值来实例化它,在RSA和ECDSA算法中可以使用密钥对或KeyProvider程序来实例化它。创建后,实例可用于令牌签名和验证操作。
当使用RSA或ECDSA算法并且只需要签名JWT时,您可以通过传递null来避免指定公钥。当您只需要验证JWT时,也可以使用私钥进行同样的操作。
使用静态密码或密钥:
//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");
//RSA
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);
注意:如何获取或读取密钥不在这个库的范围内。
- 创建并签名令牌
您首先需要通过调用JWT.create()来创建一个JWTCreator实例。使用构建器来定义令牌需要的自定义声明。最后获取字符串令牌调用sign()并传递Algorithm实例。
示例一使用HS256:
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
示例二使用RS256:
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
String token = JWT.create()
.withIssuer("auth0")
.sign(algorithm);
} catch (JWTCreationException exception){
//Invalid Signing configuration / Couldn't convert Claims.
}
如果不能将声明转换为JSON,或者签名过程中使用的Key无效,则会引发JWTCreationException的异常。
- 验证令牌
您首先需要通过调用JWT.require()并传递Algorithm实例来创建一个JWTVerifier实例。如果您需要令牌具有特定的声明值,请使用构建器来定义它们。方法 build ()返回的实例是可重用的,因此您只需要定义它一次,并使用它来验证不同的令牌。最后调用veriver .verify()传递令牌。
示例一使用HS256:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
Algorithm algorithm = Algorithm.HMAC256("secret");
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
示例一使用RS256:
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer("auth0")
.build(); //Reusable verifier instance
DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
//Invalid signature/claims
}
如果令牌无效签名或未满足声明要求,将引发JWTVerificationException的异常。