1. 什么是JSON Web Token
JSON Web Token是一个开放的、行业规范(RFC7519)的方法,用于通信双方进行可靠的数据传输。它规定了一种紧凑的数据格式,将要传输的数据以JSON对象方式的组织,然后进行编码和签名,转化成为Token。
JSON Web Token包含三部分信息数据,
Header.Payload.Signature
其中,
- Header:数据头部,声明Token类型和加密算法(比如HMAC SHA256或者RSA)
- Payload:数据的信息体,包含了要传输的信息体
- Signature:数据的数字签名,用于对数据进行校验来源可靠性和不被篡改
在三部分中,header/payload都是公开信息,JSON对象数据格式,使用Base64方式进行编码,这意味着header/payload可以被任何人进行解码和阅读,所以不要在header/payload放入敏感信息。
一个Token样例为,
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
其中,
Header | Base64编解码 | {"alg":"HS256","typ":"JWT"} |
Payload | Base64编解码 | {"sub":"1234567890","name":"John Doe","admin":true} |
Signature | HS256签名算法 | HS256(eyJhb……RydWV9, secret) |
注:签名算法中的eyJhb……RydWV9为Base64编码后的Header.Payload
2. 应用场景
JWT一般用在如下两个场景,
- 认证:这是最常用JWT应用场景,用于用户登录后,后端服务器颁发一个带有时效性的JWT token给前端应用,然后前端应用在后续的请求中使用该Token进行访问后端资源。
- 信息交换:由于JWT对数据进行了签名,信息不能被篡改,可以可靠地在通信双方进行数据信息交换。
下图简单描绘了JWT在认证场景的使用流程,
使用方法
JWT的使用方法包括:签发、解码、校验,
- 签发:用于对信息进行签名,然后颁发给使用方,供后续进行认证和信息交换
- 解码:对JWT的信息体解码,获取信息,一般用在不进行校验的场合,比如在浏览器端
- 校验:对JWT的校验,确认信息的来源可靠性和不被篡改、时效不过期
目前已经有如下语言的支持,
- Java
- Node.js
- JavaScript
- Microsoft .NET、.NET RT(Windows 8.1 and Windows Phone 8.1)
- Python
- Perl
- Ruby
- Go
- Lua
- Scala
- Object-C
- Swift
- PHP
等等。
下面主要介绍JavaScript/Node.Js/Java三种语言对Jwt的签发、校验、解码的代码实现,这三种语言分别代表着web客户端/web服务器端/后端服务。
1) JavaScript
下面使用jwt-decode类库在web客户端(浏览器端)对token进行解码操作。
点击这里下载jwt-token.js脚本。
在HTML页面中引入上面的JavaScript脚本,下面为简单的解码演示,
<script type="text/javascript" src="jwt-token.js"></script>
<script>
let jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ";
let payload = jwtToken.split('.')[1];
let payloadInfo = JSON.parse(jwt.base64urldecode(payload));
for (let key in payloadInfo) {
console.log(key + ": " + payloadInfo[key]);
}
</script>
2) Node.js
下面使用Node Auth0类库在nodejs的web服务器端进行token的签发和校验
安装命令,从npm中安装jsonwebtoken
npm install jsonwebtoken
签发
let jwt = require('jsonwebtoken'); let token = jwt.sign({name: 'John'}, 'secret', { algorithm: 'HS256', expiresIn: '1d', issuer: 'pphh'});
校验
let jwt = require('jsonwebtoken'); let info = jwt.verify(token, 'secret'); console.log(info.name) // John
解码
let info = jwt.decode(token, {complete: true}); console.log(info.header); console.log(info.payload);
3) Java
下面使用Java Auth0类库来进行token的签发和校验。
在Maven项目中添加依赖项,
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
签发
try { Algorithm algorithm = Algorithm.HMAC256("secret"); String token = JWT.create() .withIssuer("auth0") .sign(algorithm); } catch (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTCreationException exception){ //Invalid Signing configuration / Couldn't convert Claims. }
校验
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 (UnsupportedEncodingException exception){ //UTF-8 encoding not supported } catch (JWTVerificationException exception){ //Invalid signature/claims }
解码
String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; try { DecodedJWT jwt = JWT.decode(token); } catch (JWTDecodeException exception){ //Invalid token }
更多语言和JWT类库使用,请参考官方网站和样例。
最佳实践
- 在JWT信息体Payload中不要包括敏感信息,若一定要,可以尝试使用JSON Web Encryption 。
- 一定要添加Token过期时间expiration time (exp claim) ,用于时效性检查。
- 不建议以无签名算法的方式(签名算法为none)来颁发JWT token,这个有安全风险,会导致攻击者绕过某些JWT类库的校验,具体可以查看该资料。
- 建议使用HTTPS来传输JWT token信息,在最大程度保证信息在传输过程中的安全性,防止被中间人截取。
- 确保签名密钥只能让颁发者和使用者知道,尽可能使用非对称加密算法来颁发Token(比如RSA算法)。
- 如果担心重放攻击(replay attacks),可以尝试设置jti声明(JWT ID Claim),该声明将使得每个JWT token拥有唯一的ID,使其可以只使用一次,可以用来防止重放攻击。
- 在浏览器端,token可以存放到sessionStorage/localStorage,但注意XSS(跨站点脚本)攻击;还有一种方法是token放到了Cookie中,建议为其加上HttpOnly和Secure标记,HttpOnly保证JavaScript无法获取该Cookie,而Secure标记将保证该Cookie信息只能通过Https传输,但注意Cookie的方式可能会引起CSRF(跨站请求伪造)攻击。
演示项目
在admin demo项目代码中有一一使用上述的JWT token颁发、校验、解码方法,
- 在前端项目admin-front中,通过jwt-decode解析JWT token来获取登录用户账号。
- 在前端项目admin-front中,在配置文件webpack.config.js中有提供/test/auth接口,通过jsonwebtoken颁发JWT token。
- 在后端项目admin-server中,在UserService中,通过com.auth0.jwt.JWT来颁发JWT token。
- 在后端项目admin-server中,在JwtFilter中,通过com.auth0.jwt.JWT来校验用户登录token。
具体可以获取项目代码查看。
admin demo代码仓库地址:https://gitee.com/pphh/simple-admin,可以通过如下git clone命令获取仓库代码,
git clone git@gitee.com:pphh/simple-admin.git
参考资料
- JWT官方网站:https://jwt.io/
- JWT RFC技术文档:https://tools.ietf.org/html/rfc7519
- 如何在浏览器端保存JWT信息:Where to Store your JWTs – Cookies vs HTML5 Web Storage
- 如何正确的使用JWT: Use JWT The Right Way
- Nodejs的JWT开源实现:https://github.com/auth0/node-jsonwebtoken
- Java语言的JWT开源实现:https://github.com/auth0/java-jwt