Node.js 上 Token 鉴权常用的是 passport,它可以自定义校验策略,但如果你是用 express 框架,又只是解析 JWT 这种简单需求,可以尝试下 express-jwt 这个中间件。
关于 JWT
JWT 全称 JSON Web Token,是代替传统 session 认证的解决方案。其原理是服务端生成一个包含用户唯一标识的 JSON 对象,颁发给客户端。客户端请求需要权限的接口时,只要把这个 JSON 再原样发回给服务端,服务器通过解析就可识别用户。
它通常是这个样子:
1
| eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
这个 JSON 对象通过 .
分成三段,包含了请求头(加密算法)、负载信息(如 userId、过期时间),还有通过服务端密钥生成的签名来保证不被篡改。
这种机制使服务端不再需要存储 Token,因此是非常轻量的用户认证方案。并且对于微服务这种需要不同服务间共用 Token 的跨域认证,JWT 是目前的首选。
关于 express-jwt
express-jwt 是 Node.js 的一个开源库,由 ID 认证服务提供商 auth0 开发,是专用于 express 框架下解析 JWT 的中间件。
它使用非常简单,而且会自动把 JWT 的 payload 部分赋值于 req.user
,方便逻辑部分调用。
开始使用
安装
加入中间件
1 2 3 4 5 6 7
| const expressJwt = require('express-jwt')
app.use(expressJwt({ secret: 'secret12345' }).unless({ path: ['/login', '/signup'] }))
|
生成 Token
生成 Token 的方式依然使用 jsonwebtoken
,比如将下列代码加入到登录接口的返回部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const jwt = require('jsonwebtoken')
app.post('/login', function (req, res) { const token = 'Bearer ' + jwt.sign( { _id: user._id, admin: user.role === 'admin' }, 'secret12345', { expiresIn: 3600 * 24 * 3 } ) res.json({ status: 'ok', data: { token: token } }) })
|
获取解析内容
当收到带 Token 的请求,如果解析成功,就可以在路由回调里通过 req.user
来访问:
1 2 3 4 5
| app.get('/protected', function (req, res) { if (!req.user.admin) return res.sendStatus(401) res.sendStatus(200) })
|
req.user
实际就是 JWT 的 payload 部分:
1 2 3 4 5 6
| { _id: '5dbbc7daaf7dfe003680ba39', admin: true, iat: 1572587484, exp: 1573192284 }
|
解析失败
如果解析失败,会抛出 UnauthorizedError
,可以通过后置中间件来捕获:
1 2 3 4 5
| app.use(function (err, req, res, next) { if (err.name === 'UnauthorizedError') { res.status(401).send('invalid token') } })
|
修改结果字段
默认解析结果会赋值在 req.user
,也可以通过 requestProperty
来修改:
1 2 3 4
| app.use(expressJwt({ secret: 'secret12345', requestProperty: 'auth' }))
|
允许无 Token 请求
当接口允许不带 Token 和带 Token 两种状态的访问时(比如文章详情登录后判断点赞),可以通过 credentialsRequired: false
来对无 Token 请求不进行解析和抛出异常。
1 2 3 4
| app.use(expressJwt({ secret: 'secret12345', credentialsRequired: false }))
|
自定义解析
默认情况下,express-jwt 是从请求 Headers 的 Authorization
字段来获取 Token 并解析。
通过 getToken
也可以自定义一些解析逻辑,比如使用其他 Header 字段,自定义抛出异常等:
1 2 3 4 5 6 7 8 9 10 11 12
| app.use(expressJwt({ secret: 'secret12345', credentialsRequired: false, getToken: function fromHeaderOrQuerystring (req) { if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') { return req.headers.authorization.split(' ')[1] } else if (req.query && req.query.token) { return req.query.token } return null } }))
|
作废 Token
在 JWT 机制中,由于 Token 通常不进行存储,如果想作废某一条 Token,一般都是通过被动的方式。
常用的方式是建立某个字段的黑名单(比如 TokenId),对所有 Token 进行过滤,express-jwt 专门提供了回调来处理这种情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const expressJwt = require('express-jwt') const blacklist = require('./blacklist')
let isRevokedCallback = function(req, payload, done){ let issuer = payload.iss let tokenId = payload.jti
blacklist.getRevokedToken(issuer, tokenId, function(err, token){ if (err) { return done(err) } return done(null, !!token) }) }
app.use(expressJwt({ secret: 'secret12345', isRevoked: isRevokedCallback }))
|
更多用法可以查看 官方文档。