基于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 。


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 (签名)


    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模块加密存储进数据库。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容