最近的这几篇文章都与身份认证有关,前边我们说到的CAS只是实现单点登录(SSO)的一种方式,本文将提到的JWT(JSON Web Token)是当前最流行的身份认证方式之一,也能实现单点登录。

#基于Cookie/服务端的认证 身份认证是Web应用中极重要的一个部分,Cookie和基于服务器的身份验证(Session)一直都是最简单而常见的解决方案

在传统的基于Cookie/服务端方案中,用户的每个请求中携带Cookie,服务端据此识别用户。

这种方式可能存在很多问题:

  • 它在跨域场景表现并不好
  • 很难提供对移动端的支持
  • 存在跨站点脚本攻击(XSS)的风险
  • 很难在两个使用不同语言编写的web框架间共享session数据。

#基于Token的认证 在API被广泛使用的今天(广泛到不少应用后端仅需提供API就好了),为API设计更合适的身份认证解决方案就变得十分必要,OAuth 2.0和JSON Web Token(JWT)是这些解决方案中最流行的

基于Token的认证,除了能回避Cookie存在的问题,还有许多额外的优点:

  • 无状态:由于Token已经自包含了所有的用户信息,所以无须再存储Session, 服务端易于扩展。
  • 内容分发:易于实现CDN,将静态资源分布到CDN上,某些CDN服务商提供了基于Token验证的安全服务,服务器仅提供API
  • 解耦:无须被绑定在一个特定的验证方案。作为独立的会话系统,利于实现分布式
  • 基于标准:JWT作为Token的标准已经被广泛的接受。主流语言都有相应支持JWT标准的工具包

#JWT

###组成 JWT是一个轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。规范的细节参考jwt规范

一个JWT实际上就是一个字符串,看起来大概是这样的

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg

JWT是一段被base64url编码过的字符序列(去除了尾部的“=”),并用点号分隔。它由三部分组成,头部(Header)、载荷(Payload)与签名(Signature)。

###Header Header 包含了一些元数据,至少会表明 token 类型以及 签名方法。比如

1
2
3
4
{
      "typ" : "JWT",
      "alg" : "HS256"
}

type: 必需。token 类型,JWT 表示是 JSON Web Token. alg: 必需。token 所使用的签名算法,可用的值在这里有规定。

###Payload Payload部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,节选一些:

  • iss: The issuer of the token,token 是给谁的
  • sub: The subject of the token,token 主题
  • exp: Expiration Time。 token 过期时间,Unix 时间戳格式
  • iat: Issued At。 token 创建时间, Unix 时间戳格式
  • jti: JWT ID。针对当前 token 的唯一标识

除了规定的字段外,可以包含其他任何 JSON 兼容的字段。

Payload 示例:

1
2
3
4
5
6
{
    "iss": "example.com",
    "exp": 1435055117,
    "user_id": 1,
    "foo": "bar"
}

Payload加密后是安全可信的,所以我们可以将JWT设计为自说明、无状态的!

###Signature 签名主要用于验证 token 是否有效,是否被篡改。 签名时可以加密算法进行签名,比如 HMAC SHA-256:

1
2
content = base64url_encode(Header) + '.' + base64url_encode(Payload)
signature = hmacsha256.hash(content)

默认情况下Payload的内容并没有被加密,可以通过 base64url_decode(text) 的方式解码得到.如果要包含敏感信息的话,记得先将 Payload 的内容进行加密.

可以使用http://jwt.io来调试

安全性

上边说到默认情况下Payload的内容并没有被加密,所以你如果有加密需求,请使用pycrypto或是cryptography来加密。jwt的签名目的不是加密而是防止篡改.

#demo ##pyjwt 我们来看一个Python实现的案例:padilla/pyjwt

1
2
3
4
5
6
7
:::text
>>> import jwt
>>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256')
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg'

>>> jwt.decode(encoded, 'secret', algorithms=['HS256'])
{'some': 'payload'}

从中我们可以发现,加解密的过程只在服务端发生(secret只有服务端存在),故而客户端拿到的是加密数据,其中携带的Payload是安全的,无法篡改。

####Django实现 django-rest-framework-jwt

#附录:Base64 前边提到JWT使用base64来编码它的内容

Base64是一种编码,并不是一种加密过程。

关于Base64,维基百科已经说的很清楚了

Base64是一种基于64个可打印字符来表示二进制数据的表示方法,由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示…在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符

Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据。包括MIME的email、在XML中存储复杂数据。

编码后的数据比原始数据略长,为原来的4/3

举个例子,编码Man

base64.png

#参考 * jwt.io * 基于Token的认证和基于声明的标识 * JSON Web Token (JWT) 简介 * 八幅漫画理解使用JSON Web Token设计单点登录系统 * jpadilla/pyjwt * Why JWTs Suck as Session Tokens * learn-json-web-tokens