在HTTP接口调用的时候,服务端经常需要对调用方做认证,以保证安全性。一种常见的认证方式是使用JWT(Json Web Token),采用这种方式时,经常在header传入一个authorization字段,值为对应的jwt_token,或者也有图方便直接写在json中这种用法。本篇博文简单介绍一下jwt认证。本文大体翻译自jwt官网介绍,也参考了一些其他内容。
JWT
JSON Web Token (JWT) 是一个开源标准(RFC 7519),它定义了一种紧凑且自完备的方法用于在各参与方之间以JSON对象传递信息。以该种方式传递的信息已经被数字签名,因而可以被验证并且被信任。JWT既可以使用盐(secret)(HMAC算法)进行签名,也可以使用基于RSA/ECDSA算法的公钥/秘钥对进行签名。
紧凑翻译自compact,至于为什么这么翻译,看完你就懂了。
应用场景
-
认证:认证是JWT的最常用场景。只要用户完成登录,其随后的请求都会包含JWT,以允许用户访问经由当前JWT授权的路由、服务或者是资源。由于开销小且能够被简单应用在跨域访问上,JWT在分布式站点上所支持的单点登录(SSO)已经是当前它被广泛应用的一个特性。
-
信息交换:JWT是一种在各参与方之间安全传递信息的良好方法。由于JWT可以被签名(例:使用公钥/秘钥对),因而可用于确认发送者自称的身份。除此之外,由于signature使用header和payload进行计算,也可以验证内容没有被篡改。
JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
JWT结构
在紧凑形式下,Json Web Token由以下三部分组成:
- Header(头部)
- Payload(载荷)
- Signature(签名)
因此,一个典型的JWT形式如:,由两个点
.
间隔。
Header
典型的JWT header包含两个部分:(1) token类型,即JWT。(2) 所使用的签名算法,比如HMAC SHA256或者RSA。
例:
{
"alg": "HS256",
"typ": "JWT"
}
该JSON以Base64Url加密(可以被对称解密)后形成了JWT的第一部分:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload
JWT token的第二部分是包含了声明的payload,声明是一个实体的表述加上额外信息,一共有三种形式的声明:注册、共有和私有。
-
注册声明(建议但不强制):
- iss: jwt签发者
- sub: jwt使用者
- aud: jwt接收者
- exp: jwt过期时间,该时间必须大于签发时间
- nbf: 定义在某个时间之前,该jwt都是不可用的
- iat: jwt签发时间
- jti: jwt唯一身份标注,主要用于一次性token,从而避免重放攻击
(注:为了保持紧凑,注册声明都是三个字母)
-
公有声明:
公有声明可以加入任何信息,一般会添加用户相关信息或者业务需要的信息,但不建议添加敏感信息,因为该部分会在客户端解密。 -
私有声明:
私有声明是提供者和使用者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密,基本等同于明文信息。
定义一个payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后对其进行base64加密,得到JWT的第二部分:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
Signature
JWT的第三部分是签名信息,Signature由三部分组成:
- header(base64加密后)
- payload(base64加密后)
- secret (盐)
Signature需要将base64加密后的header和payload使用.
连接,然后通过header所使用的加密方式进行加盐(secret)组合加密,产生了jwt的第三部分。以使用HMAC SHA256算法为例,signature以如下方式产生:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Signature被用于验证信息在传输过程中没有被更改,或者在token以私钥加密的条件下,也可以验证JWT的发送者是否是其所自称的身份。
注:secret保存在服务端,jwt的签发也发生在服务端,secret即是用来进行jwt的签发和验证的。因为,它就是服务端的私钥,任何场景下都不应该被泄露出去。
如图是一个完整的jwt token示例:
JWT如何工作?
在认证(authentication)过程中,当用户使用凭据成功登录后,就会返回一个jwt token。因为jwt token是一种凭据,必须仔细关注其安全问题。一般而言,需要讲token的长度限制在需要的范围内。
也不应该在浏览器中存储敏感的session信息,因为这样缺乏安全。
每当用户希望访问一个被保护的路由或者资源,用户代理应当发送JWT,典型的用法是加上Authorization
header并使用Bear模式。Header的内容如下:
Authorization: Bearer <token>
在某些情况下,这是一种无状态授权方式。服务器的受保护路由会检查Authorization
header中的jwt token是否有效,如果该token是存在的,那么用将被允许访问受保护资源。如果JWT包含了必要数据,某些操作下的查库需要会被省去,尽管这种情况不是一定的。
注:如果采用HTTP headers发送jwt token,应该避免它的体积变得很大。一些服务器不接收大于8KB的header。如果你一定要在一个jwt token中嵌入大量信息,例如包含用户全部的授权信息,你可能需要其他解决方案,比如: Auth0 Fine-Grained Authorization
如果token使用Authorization
发送,跨源资源共享(Cross-Origin Resource Sharing, CORS)将不再是问题,因为jwt不需要使用cookies
。
下图展示了一个JWT token被获取并被用于访问API或者资源的过程:
- 应用或者客户端向授权服务器请求授权。该过程会通过一种不同的授权工作流。举个例子,一个典型的遵从OpenID Connect的web应用会使用authorization code flow通过
/oauth/authorize
终端进行请求。 - 授权被下发时,授权服务器向应用返回一个jwt权限token。
- 应用使用token访问被保护资源。
为什么要使用JWT?
基于cookie的认证,存在如下问题:
- CSRF:session基于cookie,如果cookie被截获,用户很容易收到跨站请求伪造的攻击。
基于session的认证,存在如下问题:
- 开销大:每个用户在认证之后,都要在服务端做一次记录,以方便该用户下次请求的鉴别。通常session保存在内存中,随着认证用户的增多,服务端存储session的开销显著增大。
- 扩展性低:用户认证记录存储在认证服务器的内存中,这意味着用户下次请求仍要访问这台服务器才能拿到授权。在分布式应用上,这限制了负载均衡的能力,进而限制了整个应用的扩展能力。
JWT是一种基于token的认证机制,它类似于HTTP协议一样是无状态的,不需要在服务端保留用户的认证信息或者会话信息。除此之外,基于token的鉴权机制不需要考虑用户在哪一台服务器登录,为应用扩展提供了便利。
接下来我们对比一下JWT和SWT(Simple Web Token)以及Security Assertion Markup Language Tokens (SAML),两者都是基于token的鉴权机制。
由于JSON没有XML那么冗余,被编码时体积也更小,因而JWT比SAML更紧凑。这使得JWT成为一个在HTML和HTTP环境下传递信息的最佳选择。
安全方面,SWT仅能被共享盐(shared secret)对称加密,但是JWT和SAML能被公钥/私钥对已X.509
证书签名。相较于对JSON进行签名的简洁,处理XML时在没有引入隐秘安全漏洞前提下进行数字签名是一件非常困难的事。
在众多编程语言中JSON解析器都是常见的,因为可以直接映射对象。相反,XML则没有一个自然的文档to对象映射。这使得JWT比SAML断言使用起来更加简单。
应用方面,JWT已经在互联网层面广泛应用。JSON Web token在众多平台上客户端侧的简单处理尤为出彩,尤其是移动端。
再补充一些jwt的优点:
- 由于json的通用性,JWT天然支持跨语言
- 由于payload的存在,JWT可以在自身存储一些业务逻辑需要的非敏感信息
- 由于JWT构成简单,字节占用小,便于传输
- 由于不需要在服务端保存会话信息,易于扩展
安全相关
安全方面应该注意的点:
- 不应该在jwt的payload存放敏感信息,因为客户端也可对称解密该部分信息,它相当于是暴露的。
- 保护好secret私钥,该私钥对于鉴权非常重要。
- 在允许的条件下,尽量使用https协议
参考文献
什么是 JWT – JSON WEB TOKEN
官网介绍:Introduction to JSON Web Tokens
基于jwt和session用户认证的区别和优缺点