系统安全-JWT(JSON Web Tokens)

原文:https://www.jianshu.com/p/47b2d2f92add
作者:高广超
系统开发来讲,安全验证永远是最重要的,从最原始的session、cookie验证方式,到符合restful风格、满足前后端分离需求、启用https请求,各方面都在不断变化中。
概念
JWT是一种用于双方之间传递安全资讯的简洁的、URL安全的表述性宣告规范。JWT作为一个开放的标准( RFC 7519 ),定义了一种简洁的,自包含的方法用于通讯双方之间以Json物件的形式安全的传递资讯。因为数字签名的存在,这些资讯是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
简洁(Compact): 可以通过URL,POST引数或者在HTTP header传送,因为资料量小,传输速度也很快自包含(Self-contained):负载中包含了所有使用者所需要的资讯,避免了多次查询数据库JWT的主要应用场景
身份认证
在这种场景下,一旦使用者完成了登陆,在接下来的每个请求中包含JWT,可以用来验证使用者身份以及对路由,服务和资源的访问许可权进行验证。由于它的开销非常小,可以轻松的在不同域名的系统中传递,所有目前在单点登入(SSO)中比较广泛的使用了该技术。
资讯交换
在通讯的双方之间使用JWT对资料进行编码是一种非常安全的方式,由于它的资讯是经过签名的,可以确保传送者传送的资讯是没有经过伪造的。
JWT的结构
加密后jwt资讯如下所示,是由.分割的三部分组成,分别为Header、Payload、Signature。
其结构看起来是这样的
xxxxx.yyyyy.zzzzz
Header
Header包含两部分资讯,alg指加密型别,可选值为HS256、RSA等等,typ=JWT为固定值,表示token的型别。
{
"alg": "HS256",
"typ": "JWT"
}
Payload
Payload是指签名信息以及内容,一般包括iss (发行者), exp (过期时间), sub(使用者资讯), aud (接收者),以及其他资讯,详细介绍请参考官网。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
Signature则为对Header、Payload的签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
如何使用JWT?
在身份鉴定的实现中,传统方法是在服务端储存一个session,给客户端返回一个cookie,而使用JWT之后,当用户使用它的认证资讯登陆系统之后,会返回给使用者一个JWT,使用者只需要本地储存该token(通常使用local storage,也可以使用cookie)即可。
当用户希望访问一个受保护的路由或者资源的时候,通常应该在 Authorization 头部使用 Bearer 模式新增JWT,其内容看起来是下面这样:
Authorization: Bearer
因为使用者的状态在服务端的内存中是不储存的,所以这是一种 无状态 的认证机制。服务端的保护路由将会检查请求头 Authorization 中的JWT资讯,如果合法,则允许使用者的行为。由于JWT是自包含的,因此减少了需要查询数据库的需要。
JWT的这些特性使得我们可以完全依赖其无状态的特性提供资料API服务,甚至是建立一个下载流服务。因为JWT并不使用Cookie的,所以你可以使用任何域名提供你的API服务而不需要担心跨域资源共享问题(CORS)。

在jwt官网,可以看到有不同语言的实现版本,这里使用的是java版的jjwt。话不多说,直接看程式码,加解密都很简单:
/**
* 建立 jwt
* @param id
* @param subject
* @param ttlMillis
* @return
* @throws Exception
*/
public String createJWT(String id, String subject, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256 ;
long nowMillis = System. currentTimeMillis();
Date now = new Date( nowMillis);
SecretKey key = generalKey();
JwtBuilder builder = Jwts. builder()
.setId(id)
.setIssuedAt(now)
.setSubject(subject)
.signWith(signatureAlgorithm, key);
if (ttlMillis >= 0){
long expMillis = nowMillis + ttlMillis;
Date exp = new Date( expMillis);
builder.setExpiration( exp);
}
return builder.compact();
}
/**
* 解密 jwt
* @param jwt
* @return
* @throws Exception
*/
public Claims parseJWT(String jwt) throws Exception{
SecretKey key = generalKey();
Claims claims = Jwts. parser()
.setSigningKey( key)
.parseClaimsJws( jwt).getBody();
return claims;
}
加解密的key是通过固定字串转换而生成的;subject为使用者资讯的json字串;ttlMillis是指token的有效期,时间较短,需要定时更新。
这里要介绍的token重新整理方式,是在生成token的同时生成一个有效期较长的refreshToken,后续由客户端定时根据refreshToken来获取最新的token。浏览器与服务端之间建立sse(server send event)请求,来实现重新整理。