Token
什么是Token?
在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般我们所说的的token大多是指用于身份验证的token
为什么使用token?
我们需要每次都知道当前请求的人是谁,但是又不想每次都让他提交用户名和密码,这时就需要有一个等同于用户名密码也能够标识用户身份的东西,即—token
基于Token的身份验证方法(步骤)
- 客户端使用用户名和密码请求登录
- 服务端收到请求,验证登录是否成功
- 验证成功后,服务端会返回一个Token给客户端,反之,返回身份验证失败的信息
- 客户端收到Token后把Token用一种方式存储起来,如( cookie / localstorage / sessionstorage / 其他 )
- 客户端每次发起请求时都会将Token发给服务端
- 服务端收到请求后,验证Token的合法性,合法就返回客户端所需数据,反之,返回验证失败的信息
JWT(Json Web Tokens)
生成Token的解决方案有许多,常用的一种就是 Json Web Tokens
JWT标准的Tokens由三部分组成:
- header:包含token的类型和加密算法
- payload:包含token的内容
- signature:通过密钥将前两者加密得到最终的token
这三部分中间使用 " . " 分隔开,并且都会使用Base64编码方式编码,如下
eyJhbGc6IkpXVCJ9.eyJpc3MiOiJCIsImVzg5NTU0NDUiLCJuYW1lnVlfQ.SwyHTf8AqKYMAJc
header
header 部分主要是两部分内容,一个是 Token 的类型,另一个是使用的算法,比如下面类型就是 JWT,使用的算法是 Hash256
头部里包含的东西可能会根据 JWT 的类型有所变化,比如一个加密的 JWT 里面要包含使用的加密的算法。唯一在头部里面要包含的是 alg 这个属性,如果是加密的 JWT,这个属性的值就是使用的签名或者解密用的算法。如果是未加密的 JWT,这个属性的值要设置成 none
{
"typ": "JWT",
"alg": "HS256"
}
payload
Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:
- iss:Issuer,发行者
- sub:Subject,主题
- aud:Audience,观众
- exp:Expiration time,过期时间
- nbf:Not before
- iat:Issued at,发行时间
- jti:JWT ID
比如下面这个 Payload ,用到了 iss 发行人,还有 exp 过期时间这两个标准字段。另外还有两个自定义的字段,一个是 name ,还有一个是 admin
{
"iss": "ninghao.net",
"exp": "1438955445",
"name": "wanghao",
"admin": true
}
signature
JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端
- header
- payload
- secret
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');
处理完成以后看起来像这样:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. //header
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ. //payload
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc //signature
客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个 Token
服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源
签发与验证 JWT
在应用里实施使用基于 JWT 这种 Token 的身份验证方法,你可以先去找一个签发与验证 JWT 的功能包。无论你的后端应用使用的是什么样的程序语言,系统,或者框架,你应该都可以找到提供类似功能的包
实例演示
使用node.js实现jwt验证
准备项目
准备一个简单的 Node.js 项目
安装签发与验证 JWT 的功能包,我用的叫 jsonwebtoken,在项目里安装一下这个包
cnpm install jsonwebtoken -S
签发 JWT
在项目里随便添加一个 .js 文件,比如 index.js,在文件里添加下面这些代码
const jwt = require('jsonwebtoken')//const定义的变量不可以修改,而且必须初始化
// Token 数据
const payload = {
name: 'guoxiansheng',
admin: true
}
// 密钥
const secret = 'STUDYJWT' // 这是加密的key(密钥或私钥)
// 签发 Token
const token = jwt.sign(payload, secret, { expiresIn:60*60*24})//expiresIn设置为24小时过期
// 输出签发的 Token
console.log(token)
非常简单,就是用了刚刚为项目安装的 jsonwebtoken 里面提供的 jwt.sign 功能,去签发一个 token。这个 sign 方法需要三个参数:
- playload:签发的 token 里面要包含的一些数据
- secret:签发 token 用的密钥,在验证 token 的时候同样需要用到这个密钥
- options:一些其它的选项
在命令行下面,用 node 命令,执行一下项目里的 index.js 这个文件(node index.js),会输出应用签发的 token:
C:\Users\Administrator\Desktop\work\JWT>node index.js //执行的命令
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3VveGlhbnNoZW5nIiwiYWRtaW4iOnRydWUsImlhdCI6MTU1MDQ4NDMzOSwiZXhwIjoxNTUwNTcwNzM5fQ.UqlYyQK4wzw5DtHlr2TupI4Ja2fU6tIF5DhASDLoBwM
上面的 Token 内容并没有加密,所以如果用一些 JWT 解码功能,可以看到 Token 里面包含的内容,内容由三个部分组成,像这样:
// header
{
"alg": "HS256", //对称算法HS256 签名和验证的时候都会用同一个密码
"typ": "JWT"
}
// payload
{
name: 'guoxiansheng',
admin: true,
iat: 1550484653, //生成Token的时间
exp: 1550571053 //Token的过期时间
}
// signature
UqlYyQK4wzw5DtHlr2TupI4Ja2fU6tIF5DhASDLoBwM
假设用户通过了某种身份验证,你就可以使用上面的签发 Token 的功能为用户签发一个 Token。一般在客户端那里会把它保存在 Cookie 或 LocalStorage 里面
用户下次向我们的应用请求受保护的资源的时候,可以在请求里带着我们给它签发的这个 Token,后端应用收到请求,检查签名,如果验证通过确定这个 Token 是我们自己签发的,那就可以为用户响应回他需要的资源
验证 JWT
验证 JWT 的用效性,确定一下用户的 JWT 是我们自己签发的,首先要得到用户的这个 JWT Token,然后用 jwt.verify 这个方法去做一下验证。这个方法是 Node.js 的 jsonwebtoken 这个包里提供的,在其它的应用框架或者系统里,你可能会找到类似的方法来验证 JWT。
打开项目的 index.js 文件,里面添加几行代码:
// 验证 Token
jwt.verify(token, 'bad secret', (error, decoded) => {
if (error) {
console.log(error.message)
return
}
console.log(decoded)
})
把要验证的 Token 数据,还有签发这个 Token 的时候用的那个密钥告诉 verify 这个方法,在一个回调里面有两个参数,error 表示错误,decoded 是解码之后的 Token 数据。
执行:
C:\Users\Administrator\Desktop\work\JWT>node index.js
输出:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3VveGlhbnNoZW5nIiwiYWRtaW4iOnRydWUsImlhdCI6MTU1MDQ5MDIyNywiZXhwIjoxNTUwNTc2NjI3fQ.1WMLfVytNgCXyjtok7oE4JincarM4vWXXtfxNtpPwK8
invalid signature
注意输出了一个 invalid signature ,表示 Token 里的签名不对,这是因为我们组长 verify 方法提供的密钥并不是签发 Token 的时候用的那个密钥。这样修改一下:
jwt.verify(token, secret, (error, decoded) => { ...
再次运行,会输出类似的数据:
C:\Users\Administrator\Desktop\work\JWT>node index.js
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZ3VveGlhbnNoZW5nIiwiYWRtaW4iOnRydWUsImlhdCI6MTU1MDQ5MDIxNCwiZXhwIjoxNTUwNTc2NjE0fQ.AxoDKLOFBpjmhRDHq5xkhqm8UNSOnTfW3oQOetfo10w
{ name: 'guoxiansheng',
admin: true,
iat: 1550490214,
exp: 1550576614 }
当然还可以采用RS256 算法签发验证Token
非对称算法RS256
签名和验证的时候用的不是同一个密码
注意事项
设置token过期时间
如果你使用expiresInMinutes来设置token的过期时间,很抱歉它会抛出如下异常
ValidationError: "expiresInMinutes" is not allowed
请使用expiresIn:以秒为单位或描述的时间跨度字符串表示rauchg / MS。
如:60,”2 days”,”10h”,”7d”
{expiresIn: 60} // 有效期60秒(没有时间单位以秒为准)
{expiresIn: "2 days"} // 有效期 2天 (后缀为时间单位)下面的类似
......
('1d') // 86400000
('10h') // 36000000
('2.5 hrs') // 9000000
('2h') // 7200000
('1m') // 60000
('5s') // 5000
('1y') // 31557600000
verify(验证)时返回的err的值
token过了有效期的时错误信息
"err": {
"name": "TokenExpiredError",
"message": "jwt expired", // token过了有效期
"expiredAt": "2016-11-07T03:31:25.000Z"
}
遇到伪造或无效的token时的错误信息
"err": {
"name": "JsonWebTokenError",
"message": "invalid token" // 伪造/无效的token
}
参考地址
https://ninghao.net/blog/2834
学习视频
https://ninghao.net/course/5018#toc