什么是 JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519).该 token 被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该 token 也可直接被用于认证,也可被加密。JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。它提供基于 JSON 格式的 Token 来做安全认证。

JWT 鉴权在 ThinkJS 中的实践

公共配置

/src/config/config.js

1
2
3
4
5
6
7
8
module.exports = {
// ...
jwt: {
secret: 'ConstOwn',
cookie: 'jwt-token',
expire: 30, // 秒
},
}

因为这三个参数在不同的位置会用到,为了统一管理我们提取到了公共的 config 中。

中间件配置

/src/config/middleware.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const jwt = require('koa-jwt')
const isDev = think.env === 'development'

module.exports = [
// ...
{
handle: jwt,
options: {
cookie: think.config('jwt')['cookie'],
secret: think.config('jwt')['secret'],
passthrough: true,
},
},
]

在 think.Controller 上使用

封装 base.js

通常情况下,Controller 中都继承于 base.js,我们可以把鉴权相关的方法封装在 base.js

src/controller/base.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const jsonwebtoken = require('jsonwebtoken')

module.exports = class extends think.Controller {
async __before() {
// 跨域处理
this.header('Access-Control-Allow-Origin', '*')
this.header('Access-Control-Allow-Headers', 'x-requested-with')
this.header('Access-Control-Request-Method', 'GET,POST,PUT,DELETE')
this.header('Access-Control-Allow-Credentials', 'true')
}
// 校验失败
authFail() {
this.json({
state: false,
message: '身份校验失败,请重新登录',
})
return false
}
// 校验
checkAuth() {
const token = this.ctx.headers['x-token']
const { secret } = this.config('jwt')
try {
var tokenObj = token ? jsonwebtoken.verify(token, secret) : {}
this.ctx.state.username = tokenObj.name
} catch (error) {
return this.authFail()
}
if (!tokenObj.name) {
return this.authFail()
}
this.updateAuth(token.name)
}
// 更新信息
updateAuth(userName) {
const userInfo = {
name: userName,
}
// 获取JWT的配置信息
const { secret, cookie, expire } = this.config('jwt')
const token = jsonwebtoken.sign(userInfo, secret, { expiresIn: expire })
this.cookie(cookie, token)
this.header('authorization', token)
return token
}
}

在业务逻辑中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const Base = require('../base.js')

module.exports = class extends Base {
__before() {}
indexAction() {
return this.json({ msg: 'login page' })
}

async loginAction() {
if (this.method === 'POST') {
// 获取post提交用户名和密码
const username = this.post('username')
const password = this.post('password')
// 查寻数据库member表,根据用户名查询用户信息
const user = await this.model('member')
.where({ username: username })
.find()
// 判断提交的密码是否与查询到的密码一致
if (user.password === this.verifyPassword(password)) {
// 完成登陆-更新token
const token = this.updateAuth(username)
this.json({ state: '登陆成功', token: token })
} else {
return this.json({ state: '登陆失败' })
}
}
}
logoutAction() {
this.updateAuth(null)
return this.success('退出登录成功')
}
// 加密验证
verifyPassword(password) {
return think.md5(
think.md5('ConstOwn') + think.md5(password) + think.md5('nwOtsnoC')
)
}
}

路由验证

如果进入路由需要验证权限的话,直接在 __before 中调用 checkAuth 方法即可:

1
2
3
4
5
6
module.exports = class extends Base {
__before() {
return this.checkAuth()
}
// ......
}

Logic 权限验证

/src/logic/jwt1.js

1
2
3
4
5
6
7
const { checkAuth } = think.Controller.prototype
module.exports = class extends think.Logic {
@checkAuth
userAction() {
// 正常的参数验证逻辑
}
}

这样一个验证就完成了! 如果该 Logic 中的所有 action 都需要进行验证,只需要给 __before 加 decorator 就可以了,其他的 action 就不用加了!