基于Token的身份验证——JSON Web Token

基于Token的身份验证——JSON Web Token

Token的维基百科

  • token的维基百科,可以了解Token的发展和定义。

Token的广泛应用

  • 现如今的很多大型网站,比如Facebook、Twitter、Google+以及Github等等,比起传统的身份验证方法,Token扩展性更强,也更安全点,非常适合用在Web应用或者移动应用上。
  • Token的在中文翻译中有”令牌”之意,意思就是,你只有拿着令牌,才能打开一道通往新世界的大门。

传统身份验证的方法

  • HTTP是一种没有状态的协议,也就是它并不知道是谁访问应用。这里可以把用户看作是客户端,客户端使用用户名(username)密码(password)通过了身份验证,但是这种方式再下次这个客户端再发送请求的时候,还需要再次验证一下。
  • 解决的方法就是,当用户请求登录的时候,如果没有问题(正确的用户名和密码),会在服务器端生成一条记录,这条记录可以说明一下登录的用户是谁,然后是把这条记录的ID号发送给客户端,客户端接收到这条记录的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

  • 实施Token验证的方法挺多的,还有一些标准方法,比如JWT,读作:jot,表示:JSON Web Tokens。JWT 标准的Token有三个部分:
    • header(头部)
    • payload(数据)
    • signature(签名)
  • 中间用点分隔开,并且都会使用Base64编码,所以真正的Token看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30.yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
  • 每个JWT token 里面都有一个header,也就是头部数据。里面包含了使用的算法,这个JWT是不是带标签名或者加密的。主要就是说明一下怎么处理这个JWT token。
  • 头部里包含的东西可能会根据JWT的类型有所变化,比如一个加密的JWT里面包含使用的加密的算法。唯一在头部里面要包含的是alg 这个属性,如果是加密的JWT,这个属性的值就是使用的签名或者解密的算法。如果是未加密的JWT,这个属性就设置为none。
  • 示例:
{
    "alg": "HS256"
}
  • 意思是这个JWT用的算法是HS256。上面的内容得用base64url的形式编码一下,所以就变成这样:
eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30

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": "fengjun.com",
 "exp": "145637890",
 "name": "JackDan",
 "admin": true
}
  • 使用base64url编码以后变成了这个样子:
eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30

Signature

  • JWT的最后一部分是Singature,这部分内容有三个部分,先是用Base64编码的header.payload,再用加密算法加密一下,加密的时候要放进去一个Secret,这个相当于是一个密码,这个密码秘密地存储在服务端。
  • header
  • payload
  • secret
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); 
HMACSHA256(encodedString, 'secret');
  • 处理完成之后看起来像这样:
yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
  • 最后这个在服务端生成并且要发送给客户端的Token看起来像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30.yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
  • 客户端收到这个Token以后把它存储下来,下回向服务端发送请求的时候就带着这个Token。服务端收到这个Token,然后进行验证,通过以后就会返回给客户端想要的资源。

代码验证——签发和验证JWT

  • 在应用里实施使用基于JWT这种Token的身份验证方法,你可以先去找一个签发与验证JWT的功能包。无论你的后端应用使用的是什么样的程序语言,系统,或者框架,你应该都可以找到提供类似功能的包。
  • 这里采用Node.js来进行代码验证。

准备项目

  • 准备一个简单的Node.js项目
cd testWorkspace
mkdir jwt_demo
cd jwt_demo
npm init -y
  • 效果图如下:

init

  • 安装签发与验证JWT的功能包,这里使用的是jsonwebtoken,在项目里面安装这个包。

  • 效果图如下:

jsonwebtoken_install

  • package.json效果图:

jsonwebtoken_package


签发JWT

  • 在项目里随便添加一个.js文件,比如index.js,在文件里添加下面这些代码:
const jwt = require('jsonwebtoken')

// token data token数据
const payload = {
    name: 'JackDan',
    admin: true
}

// secret 密钥
const secret = 'JUNJUNLOVEFENGFENG'

// 签发 token
const token = jwt.sign(payload, secret, {expiresIn: '36600days'})

// 输出签发的 Token
console.log(token)
  • 非常简单,就是用了刚刚为项目安装的jsonwebtoken里面提供的jwt.sign功能,去签发一个token。这个sign方法需要三个参数:
    • 1、payload: 签发的token里面要包含的一些数据。
    • 2、secret: 签发token用的密钥,在验证token的时候同样需要用到这个密钥。
    • 3、options: 一些其他的选项。
  • 在命令行下面,用node命令,执行一下项目里的index.js这个文件(node index.js),会输出应用签发的token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQxMzcsImV4cCI6NDY5ODM3NDEzN30.yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
  • 上面的Token内容并没有加密,所以如果用一些JWT解码功能,可以看到Token里面包含的内容,内容由三个部分组成,像这样:
// header
{
    "alg": "HS256",
    "typ": "JWT"
}

// payload
{ 
    name: 'JackDan', 
    admin: true, 
    iat: 1536134993, 
    exp: 4698374993 
}

// signature
yuGHsgfJbg5ArbeVGKJENQOYuBsYFFLDbwiExkPSH_k
  • 假设用户通过某种身份验证,你就可以使用上面签发的Token的功能为用户签发一个Token。一般在客户端那里会把它保存在Cookie或者LocalStorage里面。
  • 用户下次向我们的应用请求受保护资源的时候,可以在请求里带着我们给它签发的这个Token,后端应用收到请求,检查签名,如果验证通过确定这个Token是我们自己签发的,那就可以为用户响应回他需要的资源。

验证JWT

  • 验证JWT的有效性,确定一下用户的JWT是我们自己签发的,首先要得到用户的这个JWT Token,然后用jwt.verify这个方法去做一下验证。这个方法是Node.js的jsonwebtoken这个包提供的,在其他的应用框架或者系统里,你可能会找到类似的方法来验证JWT。
  • 打开项目中的index.js文件,添加如下代码:
// 验证 Token
jwt.verify(token, secret, (error, decoded) => {
    if (error) {
        console.log(error.message)
        return
    }
    console.log(decoded)
})
  • 把要验证的Token数据,还有签发这个Token的时候用的那个密钥告诉verify这个方法,在一个回调里面有两个参数,error表示错误,decoded是解码之后的Token数据。
  • 执行:
C:\projects\testWorkspace\jwt_demo>node index.js
  • 输出数据:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiSmFja0RhbiIsImFkbWluIjp0cnVlLCJpYXQiOjE1MzYxMzQ5OTMsImV4cCI6NDY5ODM3NDk5M30.ELAlzPGVvjsK0kK1Yl1PArb0wS3860R6c7mqG-5M4eY
{ name: 'JackDan', admin: true, iat: 1536134993, exp: 4698374993 }

RS256算法

  • 默认签发还有验证Token的时候用的是HS256算法,这种算法需要一个密钥(密码)。我们还可以使用RS256算法签发与验证JWT。这种方法可以让我们分离开签发与验证,签发时需要用一个密钥,验证时使用公钥,也就是有公钥的地方可以做验证,但是不能做签发。
  • 在项目下面创建一个新的目录,里面可以存储即可将生成的密钥与公钥文件。
C:\projects\testWorkspace\jwt_demo>mkdir config
C:\projects\testWorkspace\jwt_demo>cd config
  • 密钥
  • 先生成一个密钥:
C:\projects\testWorkspace\jwt_demo\config>ssh-keygen -t rsa -b 2048 -f private.key
  • 公钥
  • 基于上面生成的密钥,再去创建一个对应的公钥:
C:\projects\testWorkspace\jwt_demo\config>openssl rsa -in private.key -pubout -outform PEM -out public.key
  • 效果图如下:

config


签发JWT(RS256算法)

  • 用RS256算法签发JWT的时候,需要从文件系统上读取创建的密钥文件里的内容。
const fs = require('fs')

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

RS256

  • 签发仍然使用jwt.sign方法,只不过在选项参数里特别说明一下使用的算法是RS256:
// 获取验证 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)
})
  • 效果图:

RS256_verify


代码地址

  • jwt_demo,个人验证代码的github地址。

JackDan Thinking

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章