JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种
紧凑(compact)
且自包含(self-contained)
的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对对JWT进行签名。
尽管可以对JWT进行加密以在各方之间提供保密性,但我们将重点关注已签名的令牌。签名的令牌可以验证其中包含的声明的完整性,而加密的令牌则隐藏其他方的声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是对其进行签名的一方。
通俗的讲,JWT是一种安全标准。基本思路就是用户提供用户名和密码给认证服务器,服务器验证用户提交信息的合法性;如果验证成功,会产生并返回一个Token(令牌),用户可以使用这个Token访问服务器上受保护的资源。
传统的session认证

在互联网服务中,授权认证一般流程如下:
- 用户向服务器发送用户名和密码。
- 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
- 服务器向用户返回一个 session_id,写入用户的 Cookie。
- 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
- 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。
以上就是传统的session认证流程,基于session的认证会使应用本身很难得到扩展,随着客户端用户的增加,独立的服务器已无法承载更多的用户,如果是采用服务器集群,或者是跨域的服务导向架构,这时候基于session认证应用的问题就会暴露出来。
基于session认证的问题
- Session: 客户端用户经过应用认证之后,应用都要在服务端做一次记录,以方便用户下次请求的授权,通常而言session保存在服务端的文件或数据库中,随着认证用户的增多,服务端的开销会明显增大。
- 扩展性: 用户认证之后,服务端做认证记录,这意味着用户下次请求授权还必须要请求在这台服务器上,这样在分布式的应用上,相应的限制了负载均衡的能力。这也意味着限制了应用的扩展能力。
- CSRF: 因为是基于cookie来进行用户识别的,如果被截获,用户就会很容易受到跨站请求伪造的攻击。
JWT 的原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2018年7月1日0点0分"
}
以后,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名(详见后文)。
服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
基本概念
紧凑(compact)
由于Token尺寸较小,Token可以通过URL,POST参数或HTTP请求头发送。 另外,尺寸越小意味着传输速度越快。
自包含(self-contained)
有效载荷(Payload)包含有关用户的所有必需信息,避免了多次查询数据库。
JWT使用场景
授权(Authorization)
这是使用JWT的最常见方案。 一旦用户登录,每个后续请求都将包含JWT,从而允许用户访问该令牌允许的路由,服务和资源。 单点登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。
信息交换(Information Exchange)
JWT是在服务器之间安全地传输信息的一种好方法。 因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确定发件人是他们所说的人。 此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否被篡改。
JWT的构成

它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了三行。
JWT 的三个部分依次如下:
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行,如下所示:
Header.Payload.Signature(xxxxx.yyyyy.zzzzz)

下面将依次介绍这三个部分。
Header
Header 部分是一个 JSON 对象,通常由两部分组成:Token的类型(即JWT)和所使用的签名算法,例如HMAC SHA256或RSA。
{
"alg": "HS256",
"typ": "JWT"
}
将上面的 JSON 对象使用 Base64URL 算法(详见后文)转成字符串,构成了第一部分:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。
然后将其进行Base64URL加密转换成字符串,得到JWT的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
Signature
Signature 部分是对前两部分的签名,防止数据篡改。该签证信息由三部分组成:
- header(Base64URL加密后的字符串)
- payload(Base64URL加密后的字符串)
- secret(该密钥只有服务器知道,不能泄露给用户)
这个部分需要Base64URL加密后的header和Base64URL加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。公式如下:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
// 示例代码
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret');
生成签名如下:
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
JWT 的使用
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer ' + token
}
})
服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

JWT 的特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 在不加密的情况下,不能将敏感数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
- 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
这篇文章目前没有评论