为一个自娱自乐的项目开发WebAPI的时候,我尝试使用了Token验证机制。
对于我这样一个菜鸟来说,Token的验证机制在我理解中十分简单:
先使用用户名和密码登录系统,服务器会返回一个字符串,约定好这个字符串就是系统出入的通行令牌
客户端之后除了登录操作的所有请求只要带上这个字符串,系统就会放行。
好,到现在就明白了,“Token”在当时的我看来只是一个字符串,我只需要保证这个字符串一人一个不重样即可。
于是我就使用了GUID(全局变量标识符)来充当Token。但是使用过程中就有了问题。这个Token是生成了而且不重样,可我怎么确立它和用户之间的对应关系?于是我想到把Token存到用户表中,假设用户带着Token访问更新用户信息的API,服务器拿到Token去查询用户表,进而找到对应的用户名。至此我就知道了这个Token是哪位用户的了。拿着这个用户名,就可以更新该用户的表项了。那么其它的API也是这个这样,首先第一件事要做的就是去查询数据库找到对应的用户名,才能进行其他的操作。
按照我的思路的话,这个方式虽然麻烦一点,但是还是能实现功能的。
另一个观于Token的常见要求是,它得能设置有效期,总不能一个Token用一辈子吧。于是我又往用户表加了一列过期时间,每次验证Token都要从数据库中拿出到期时间与当前时间做比较来判断是不是过期了。
JWT的基本概念
JWT(Json Web Token)本质上是一种Token的设计规范。用于实现Token机制的Token说到底也就是个字符串,重点是这个字符串该怎么写才会比较合理。我试过用GUID来充当这个Token实现简单的Token身份验证机制,完全没问题。可就是用起来有点别扭。而JWT就是一种更加合理的组织Token字符串的方式。
它使用JSON格式,总体分为三个大块:Header .Payload. Signature,分别承担各自的责任。
Header
{
"alg": "HS256",
"typ": "JWT"
}
Payload
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
整体结构为:
header (base64)+payload (base64)+Signature
有关那些Claim的说明官网上有详细介绍。
接下来从Signature出发看看可以挖到什么知识。↓
关于Signature
前两部分的处理都十分简单,关于Signature的处理有点让人眼花缭乱,特别是我这种对密码技术一无所知的小白。
关于这个最后一部分我有过一点误解:以为使用SHA-256进行处理以后就可以看做是对JWT加密了别人就看不到了。其实并不是这样的。
首先要声明,最后一部分“签名”并不是对Token进行加密处理。而HS256也不是一般意义上的加密函数,它的全称是
HMAC using SHA-256,这牵扯到的就多了。
要理解它首先要知道一下几个概念:
1. 单向散列函数:
单向散列函数也称信息摘要函数(Message Digest Function),哈希(Hash)函数或者杂凑函数。
它是其余两个概念的基础。现在不考虑具体实现,把这个概念当成一个黑箱。
输入:任意长度消息(可以是1bit 、1K、1M,甚至可以100G)
输出:一串固定长度的数据(散列值,也称消息摘要,指纹)
单项散列函数有如下几种特性:
- 根据任意长度消息得出的散列值长度是固定的。
- 散列值计算时间短
- 不同的消息有不同的散列值(如果两个不同消息的散列值相同,那就称为发生碰撞)
- 根据散列值无法还原消息(单向性,只能从消息到散列值,反之不成立)
关于单向散列函数的应用,很常见的一个就是文件的校验
图片中所使用的哈希函数是 SHA-1(也有使用MD5的)。有了它就看可以确定下载的文件是不是被动过手脚。因为软件在发布的时候会同时公布它散列值,正在下载的软件也可以计算散列值,两个散列值相同就说明是同一个软件。
常见的单项散列函数: |
---|
MD4(Message Digest 4) |
MD5(Message Digest 5) |
SHA-1 |
SHA-256 数字代表散列值长度为256bit |
SHA-384 |
SHA-512 |
2. MAC 消息认证码
MAC:Message Authentication Code 即消息认证码 它可以确认消息完整性并进行认证。
输入:消息+发送者以及接收者之间共享的密钥
注:与单项散列函数不同之处就是它多了一个密钥的参与
输出:固定长度的数据,称为MAC值
注:这就跟单向散列函数一样了
确切来说它指的是一种认证机制。这种机制有多种实现方法,单项散列函数就是其中之一。
使用单向散列函数(也称Hash函数)实现的消息认证码就称为 HMAC,其中H就是Hash的意思。
一次解决了单项散列函数虽然可以检测到篡改(完整性),但是却没有办法识别伪装的问题。
注:无法识别伪装是因为如果有第三者假装其中一方发消息,另一方根本无从知晓这个消息是不是对方发来的。
3.HMAC 哈希消息认证码
上面也提到了,HMAC就是使用了单项散列函数来构造消息认证码的一种方法(RFC2104)。根据它所使用的散列函数不同,就出现了如下这么多种 HMAC
HMAC 算法 | 备注 |
---|---|
HS256 | HMAC using SHA-256 |
HS384 | HMAC using SHA-384 |
HS512 | HMAC using SHA-512 |
注:使用消息认证码是无法保证消息的机密性的,它只能保证消息被正确的传送了。例如 传送的完整消息格式为 “123456”+“消息验证码”,消息验证码的作用就是在对方收到消息之后可以根据验证码来验证 “123456”是没有被修过的。至于机密性,那需要对“123456”进行加密,而这不关消息验证码什么事。
消息认证码有两个无法解决的问题:
- 对第三方证明
- 防止否认
数字签名可以解决上述问题,但是这与JWT 关系不大。
现在我明白了,被在JWT中被称为 “Signature 签名”的部分,其实是前两部分的消息认证码(MAC),官方称之为数字签名我觉的其实并不怎么准确。在JWT中密钥并不与客户端共享,其为服务器独有。这样一来只有服务器可以发Token,而客户端因为缺少密钥而无法伪造 Token。服务器会对每个请求里面的Token用密钥来算MAC值来验证这个Token是不是自己发出去的。
而且为了确保Token不是一直有效,还要加一些时间戳来限制
Reserverd claims | 说明 |
---|---|
exp(Expiration time) | 是一个时间戳,代表这个JWT的过期时间 |
nbf(Not Before) | 是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的 |
iat(Issued at) | 是一个时间戳,代表这个JWT的签发时间 |
jti(JWT ID) | 是JWT的唯一标识 |
这些时间戳可以让服务器知道这个Token有没有过期。
关于所谓JWT的安全性
对于Token的安全性,其实我觉得没什么好说的,Token实质上就是一个由服务器签发的无法伪造的令牌。JWT能够保证令牌无法伪造就已经足够安全了。至于什么“黑客拿到了令牌怎么办,要是有中间人劫持,拦截了怎么办”之类的安全问题,并不是JWT的锅,而是传输协议的锅。HTTP协议 明文传输信息而造成的安全问题,JWT能有什么责任?最简单的方法就是换HTTPS协议喽。
使用JWT来进行用户身份认证
C#中怎么生成JWT格式的Token
JWT在各个平台都有已经封装好的库可以使用
在.NET平台下有如下几个命名空间可以引用:
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
微软把这些类全都开源放在在Github上面可以供研究其实现过程
关于JWT的Claim
Reserverd claims | 说明 |
---|---|
iss(Issuser) | 代表这个JWT的签发主体 |
sub(Subject) | 代表这个JWT的主体,即它的所有人 |
aud(Audience) | 代表这个JWT的接收对象 |
exp(Expiration time) | 是一个时间戳,代表这个JWT的过期时间 |
nbf(Not Before) | 是一个时间戳,代表这个JWT生效的开始时间,意味着在这个时间之前验证JWT是会失败的 |
iat(Issued at) | 是一个时间戳,代表这个JWT的签发时间 |
jti(JWT ID) | 是JWT的唯一标识 |
哈希算法
- |alg 参数|Digital Signature or MAC 算法|
|:————————-:|:———————————————–:|
|HS256|HMAC using SHA-256 hash algorithm|
|HS384| HMAC using SHA-384 hash algorithm|
|HS512| HMAC using SHA- 5 12 hash algorithm|
|RS256|RSASSA using SHA-256 hash algorithm|
|RS384|RSASSA using SHA- 384 hash algorithm|
|RS512| RSASSA using SHA-512 hash algorithm|
|ES256|ECDSA using P-256 curve and SHA-256 hash algorithm|
|ES384| ECDSA using P- 384 curve and SHA-384 hash algorithm|
|ES512|ECDSA using P-521curve and SHA-512 hash algorithm|
|none| No digital signature or MAC value included|