Token的身份验证流程

 https://ninghao.net/blog/2834

参考以上博客

Token :令牌

传统身份验证方式

HTTP协议是没有状态的,它并不知道是谁访问的应用。如果将用户看成是客户端,客户端使用用户名还有密码通过了身份验证 ,但下次再回这个客户端发请求时,还需要再验证。

解决方案:当用户请求登录时,验证通过,则在服务器端生成一条记录,在这个记录里声明一下登录的用户是谁,【存在session里】然后把这条记录的ID号发给客户端,客户端收到以后把这个ID号存储在Cookie里,下次这个用户再向服务器发送请求的时候,可以带着这个Cookie,这样服务器会验证这个Cookie里的信息,看看能不能在服务端里找到对应的记录,如果可以,则说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

以上用到的就是Session,我们需要在服务器端存储为登录的 用户生成的Session,这些Session可能会存储在内存,磁盘,或者数据库里。我们需要在服务端定期去清理过期的Session。

基于Token的身份验证方法

使用基于Token的身份验证方法,在服务端不需要存储用户的登录记录。

流程:

  1. 客户端使用用户名和密码请求登录
  2. 服务端收到请求,去验证用户名和密码
  3. 验证成功后,服务端会签发一个Token,再把这个Token发送给客户端
  4. 客户端收到Token以后可以把它存储起来,比如放在Cookie里或者Local Storage里
  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的Token
  6. 服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据

JWT (JSON Web Token)

实施Token验证的方法有很多,还有一些标准方法,如JWT,jot

表示JSON Web Tokens。JWT标准的Token有三个部分:

  • header(头部)
  • payload(数据)
  • signature(签名)

中间用点分隔开,并且都会使用Base64编码,Token如下:

Header(头部)

每个JWT token里面都有一个header,也就是头部数据。里面包含了使用的算法,这个JWT是不带签名或者加密的。

处理JWT token:

头部里面包含的东西可能会根据JWT的类型所变化,如一共加密的JWT里面要包含使用的加密算法。唯一再头部里面要包含的就是alg属性,如果是加密JWT,alg属性值就是使用的签名或者解密的用的算法。如果是未加密的JWT,alg属性未none。

例子:

{
"alg":"HS256"
}

以上例子就是JWT用的算法是HS256。以上内容用base64url形式编码一下,就为:

eyJhbGciOiJIUzI1NiJ9

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":"hecate.info",
"exp":"1438955445",
"name":"hecate",
"admin":true
}
使用base64url编码以后就变成了如下:

eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ

Signature(签名)

JWT 的最后   一部分是 Signature ,这部分内容有三个部分,先是用 Base64 编码的 header.payload ,再用加密算法加密一下,加密的时候要放进去一个 Secret ,这个相当于是一个密码,这个密码秘密地存储在服务端。

  • header
  • payload
  • secret


const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
HMACSHA256(encodedString, 'secret');

处理完成以后看起来如:

SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后这个在服务端生成并且要发送给客户端的Token看起来如:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

客户端收到这个Token以后把它存储下来,下回向服务端发送请求的时候就带着这个Token。服务端收到这个Token,然后进行验证,通过以后就会返回给客户端想要的资源。

签发与验证JWT

在应用里实施使用基于JWT这种Token的身份验证方法,可先找一个签发与验证JWT的功能包。

 

准备项目

准备一个简单的 Node.js 项目:

cd ~/desktop
mkdir jwt-demo
cd jwt-demo
npm init -y

安装签发与验证 JWT 的功能包,我用的叫 jsonwebtoken,在项目里安装一下这个包:

npm install jsonwebtoken --save

签发 JWT

在项目里随便添加一个 .js 文件,比如 index.js,在文件里添加下面这些代码:

const jwt = require('jsonwebtoken')

// Token 数据
const payload = {
  name: 'wanghao',
  admin: true
}

// 密钥
const secret = 'ILOVENINGHAO'

// 签发 Token
const token = jwt.sign(payload, secret, { expiresIn: '1day' })

// 输出签发的 Token
console.log(token)

非常简单,就是用了刚刚为项目安装的 jsonwebtoken 里面提供的 jwt.sign 功能,去签发一个 token。这个 sign 方法需要三个参数:

  1. playload:签发的 token 里面要包含的一些数据。
  2. secret:签发 token 用的密钥,在验证 token 的时候同样需要用到这个密钥。
  3. options:一些其它的选项。

在命令行下面,用 node 命令,执行一下项目里的 index.js 这个文件(node index.js),会输出应用签发的 token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzM5MDYsImV4cCI6MTUyOTEyMDMwNn0.DctA2QlUCrM6wLWkIO78wBVN0NLpjoIq4T5B_2WJ-PU

上面的 Token 内容并没有加密,所以如果用一些 JWT 解码功能,可以看到 Token 里面包含的内容,内容由三个部分组成,像这样:

// header
{
  "alg": "HS256", 
  "typ": "JWT"
}

// payload
{
  "admin": true, 
  "iat": 1529033906, 
  "name": "wanghao", 
  "exp": 1529120306
}

// signature
DctA2QlUCrM6wLWkIO78wBVN0NLpjoIq4T5B_2WJ-PU

假设用户通过了某种身份验证,你就可以使用上面的签发 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 数据。

执行:

node ~/desktop/jwt-demo/index.js

输出:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzQ3MzMsImV4cCI6MTUyOTEyMTEzM30.swXojmu7VimFu3BoIgAxxpmm2J05dvD0HT3yu10vuqU

invalid signature

注意输出了一个 invalid signature ,表示 Token 里的签名不对,这是因为我们组长 verify 方法提供的密钥并不是签发 Token 的时候用的那个密钥。这样修改一下:

jwt.verify(token, secret, (error, decoded) => { ...

再次运行,会输出类似的数据:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzUzODYsImV4cCI6MTUyOTEyMTc4Nn0.mkNrt4TfcfmP22xd3C_GQn8qnUmlB39dKT9SpIBTBGI

{ name: 'wanghao', admin: true, iat: 1529035386, exp: 1529121786 }

RS256 算法

默认签发还有验证 Token 的时候用的是 HS256 算法,这种算法需要一个密钥(密码)。我们还可以使用 RS256 算法签发与验证 JWT。这种方法可以让我们分离开签发与验证,签发时需要用一个密钥,验证时使用公钥,也就是有公钥的地方只能做验证,但不能签发 JWT。

在项目下面创建一个新的目录,里面可以存储即将生成的密钥与公钥文件。

cd ~/desktop/jwt-demo
mkdir config
cd config

密钥

先生成一个密钥文件:

ssh-keygen -t rsa -b 2048 -f private.key

公钥

基于上面生成的密钥,再去创建一个对应的公钥:

openssl rsa -in private.key -pubout -outform PEM -out public.key

签发 JWT(RS256 算法)

用 RS256 算法签发 JWT 的时候,需要从文件系统上读取创建的密钥文件里的内容。

const fs = require('fs')

// 获取签发 JWT 时需要用的密钥
const privateKey = fs.readFileSync('./config/private.key')

签发仍然使用 jwt.sign 方法,只不过在选项参数里特别说明一下使用的算法是 RS256:

// 签发 Token
const tokenRS256 = jwt.sign(payload, privateKey, { algorithm: 'RS256' })

// 输出签发的 Token
console.log('RS256 算法:', tokenRS256)

验证 JWT(RS256 算法)

验证使用 RS256 算法签发的 JWT,需要在文件系统上读取公钥文件里的内容。然后用 jwt 的 verify 方法去做验证。

// 获取验证 JWT 时需要用的公钥
const publicKey = fs.readFileSync('./config/public.key')

// 验证 Token
jwt.verify(tokenRS256, publicKey, (error, decoded) => {
  if (error) {
    console.log(error.message)
    return
  }
  console.log(decoded)
})

发表评论

电子邮件地址不会被公开。 必填项已用*标注