基于token的身份认证:JSON Web Token(附:Node.js项目示例)

前言

在大多数的应用里,身份验证是必须的。最近学习了基于token的身份验证,很多大型网站也在用,例如Facebook,twitter,Google,github。大家通常将token翻译为“令牌”。顾名思义,只有拿到这个令牌,你才能通关,才能去请求相应的数据或资源。

本文讲解了传统的和基于token的身份认证方式,并介绍了JWT相关知识,最后附上Node.js中使用jsonwebtoken、passport、passport-jwt模块获取和验证token的示例。

传统身份验证

HTTP 是一种没有状态的协议,也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端,客户端使用用户名和密码通过了身份验证,不过下回这个客户端再发送请求时候,还得再验证一下。

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

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


3619404-e006bb3a4ac14000.png
session.png

token身份验证

使用基于 Token 的身份验证方法,服务端不需要存储用户的登录记录。大概的流程如下:

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

JWT

从上文可知,token验证即:通过客户端保存数据,而服务器根本不保存会话数据,每个请求都被发送回服务器。JWT是实施 Token 验证的代表,读作:jot ,表示:JSON Web Tokens 。也可以称作JSON Web令牌

1. JWT的原则

JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户,如下所示。

{
  "UserName": "Ciger",
  "Role": "Admin",
  "Expire": "2019-02-18 20:15:56"
}

之后,当用户与服务器通信时,客户在请求中发回JSON对象。服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名

2. JWT的数据结构

JWT标准的Token有如下三个部分

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


    3619404-a2d9faf8bf9cd72e.jpg
    jwt.jpg

这三部分用点分隔开,并且使用Base64url编码。
Token看起来如下:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVjNjc3OTExMjdjYWEzMjFmNDAzOTc1MyIsIm5hbWUiOiJjY2MiLCJpYXQiOjE1NTAzMjAyNzcsImV4cCI6MTU1MDMyMzg3N30.a-CWZMdmtXnSJrYGUS6fbmqd4VzY6lPftDQ2A3UsK0w

Header

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

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

示例:

{
  “alg”:"HS256"
}

意思是这个JWT用的算法为HS256。上面的json内容经过Base64url编码后如下:

eyJhbGciOiJIUzI1NiJ9

Payload

Payload 里面是 Token 的具体内容,这些内容里面有一些是标准字段,你也可以添加其它需要的内容。下面是标准字段:

  • iss:Issuer,发行者
  • sub:Subject,主题
  • aud:Audience,观众
  • exp:Expiration time,过期时间
  • nbf:Not before
  • iat:Issued at,发行时间
  • jti:JWT ID

示例:

{
 "exp": "1438955445",
 "name": "Ciger",
 "admin": true
}

使用base64url编码后如下:

eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ

Signature

JWT 的最后一部分是 Signature ,这部分内容有三个部分,先是用 Base64Url 编码的 header.payload ,之后选择一个加密算法加密,将Secret作为参数放进去(这个Secret相当于一个密码,存储在服务端)

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

加密后如下:

SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

最后将header、payload、signature三部分组合起来便是一个要发送给客户端的token

eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

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

Node.js中使用Express结合Passport实现登陆认证

1. 首先安装

cnpm install jsonwebtoken
cnpm install passport-jwt passport

jsonwebtoken用于获取token,passport和passport-jwt用于验证token。passport是express框架中一个针对密码的中间件,而passport-jwt是一个针对jsonwebtoken的插件。

2. 得到token

在项目中添加一个.js文件,如users.js

const jwt = require('jsonwebtoken')

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

// 密钥
const secretOrKey = 'secret'

// 签发 Token
const token = jwt.sign(payload, secretOrKey , { expiresIn: '3600' })

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

jwt.sign()方法有三个参数:

  1. payload: token里面要包含的一些数据
  2. secret:签发token用的密钥,验证token时也要用到这个密钥
  3. options:一些其他选项,如过期时间expiresIn

在命令行下执行node users.js,就会输出应用签发的token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlLCJpYXQiOjE1MjkwMzM5MDYsImV4cCI6MTUyOTEyMDMwNn0.DctA2QlUCrM6wLWkIO78wBVN0NLpjoIq4T5B_2WJ-PU

2. 验证JWT

这里我们采用passport+passport-jwt来验证,你也可以采用其他方法验证,如jwt.verify

采用passport+passport-jwt方法验证,首先需要在入口文件中引入passport并初始化

入口文件-server.js

const passport = require('passport');
app.use(passport.initialize())

3. 对passport进行一些配置

在config文件夹下新建一个passport.js文件,然后在入口文件(server.js)中引入

require('./config/passport')(passport)

在passport.js中,引入以下模块:

  • passport-jwt
  • mongoose
  • keys.js
  • models/Users,js

具体代码如下:

const JwtStrategy = require('passport-jwt').Strategy,
   ExtractJwt = require('passport-jwt').ExtractJwt;
const mongoose = require('mongoose')
const User = mongoose.model('users')
const keys = require('./key')

const opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = keys.secretOrKey;

module.exports = (passport) => {
   passport.use(new JwtStrategy(opts, (jwt_payload, done) => {
            User.findById(jwt_payload.id)
           .then(user=>{
               if(user){
                   //此处会将user信息写入req.user
                   return done(null,user)
               }
               return done(null,false)
           }).catch(err=>{console.log(err)})
   }))
}

(passport.js配置信息复制粘贴修改下即可)

4. 验证token

router.get("/current",passport.authenticate("jwt",{session:false}),(req,res) => {
     res.json({
        req.user
    })

总结

token就像一个令牌,我们只有拿到这个令牌才能向服务器去请求用户的信息。Node.js中获取token可以用jsonwebtoken模块,验证token用passport和passport-jwt模块。对于密码可用bcrypt模块加密存储进数据库。

猜你喜欢

转载自blog.csdn.net/weixin_33737774/article/details/88157875