记一次前后端分离使用 token 登录解决方案(node jwt + Vue axios)

前言

最近在帮校友写一个项目,欣音悦(以歌单歌单分享为主的音乐 webapp)。这是一个类似于 QQ 音乐的 webapp 目前还在开发中,其中歌曲数据和歌手数据来自于 QQ 音乐的接口,而歌单数据则存在本地数据库中。校友的需求是用户能添加歌单并分享,能听歌,能评论。所以就涉及用户登录状态保持,而我也是首次尝试使用后 token 方案进行登录处理,所以就写这篇博文记录下。


整理思路

  1. 在用户首次登录的时候,服务器会判断用户登录信息是否匹配(账户密码等),若匹配则生成一个包含相关用户信息的 token,并返回给前端,前端可以将这个 token 解析得到相关用户数据。
  2. 前端拿到 token 后,会将它存在 localStorage 或 Vuex 中。
  3. 而每次前端路由跳转时,判断 localStorage 或 Vuex 中是否存在 token,若存在则获取用户信息,改变登录状态。若不存在则跳转到登录组件。
  4. 设置一个 HTTP 拦截器,在每次请求中,请求头携带 token。
  5. 服务器判断请求头中是否存在 token ,若不存在或过期,返回 401 。
  6. 前端得到 401 则重定向到登录组件。

后端接口

在写这个项目的时候,服务器我采用的是 Node.js ,而 WEB 框架,则采用 Koa2 ,数据库则使用 Mongodb 。

生成 token ,需要一个模块 jsonwebtoken ,使用 npm 安装即可。

在服务器中处理用户信息相关接口的路由文件 users.js 中的登录接口,处理逻辑如下:

  1. 将用户 E-mail 和 password 从执行上下文提取出来后,与数据库中的信息进行匹配。
  2. 若成功,则生成 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
/**
* @router GET api/users/login
* @desc 登录接口地址 返回token
* @access 接口是公开的
*/
router.post('/login', async ctx => {
// 查询当前登录邮箱是否在数据库中
const findResult = await User.find({ email: ctx.request.body.email })
if (!findResult.length) {
ctx.state = 404
ctx.body = { email: '用户不存在!' }
} else {
const user = findResult[0]
// 查到后 验证密码
const result = await bcrypt.compareSync(
ctx.request.body.password,
user.password
)
// 密码验证通过
if (result) {
// 返回token
const payload = { id: user.id, name: user.name, avater: user.avatar }
const token = jwt.sign(payload, keys.secretOrKey, { expiresIn: 60 * 60 })

ctx.state = 200
ctx.body = {
success: true,
token: `Bearer ${token}`
}
} else {
ctx.state = 400
ctx.body = {
password: '密码错误!'
}
}
}
})

对登录接口进行测试,邮箱和密码正确的话可以看到,此时,服务器已经返回给了一个 token。

token


前端处理

在前端正确登录,拿到 token 后,就需要将她存储起来,也就是在登录组件中,对token进行存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
methods: {
_login() {
const userInfo = {
email: this.email,
password: this.password
}
login(userInfo).then(res => {
if (res.success) {
// 保持到 localStroage 中
localStorage.setItem('token', res.token)
// 跳转到 / 路由
location.replace('/')
}
}).catch(err => {
throw err
})
}
}

上面代码中的 login 方法,已经封装到 useruser.js 中了,直接调用就行,其主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export function register(userInfo) {
const url = 'http://localhost:5000/api/users/register'

const data = Object.assign({}, userInfo)

return axios
.post(url, data)
.then(response => {
return Promise.resolve(response.data)
})
.catch(err => {
throw err
})
}

在 router 的 index.js 中,就可是使用 loaclStorage.token 来进行前端路由拦截,非登录页面若 localStorage.token 不存在,则跳转到登录页。

1
2
3
4
5
6
7
//路由跳转之前
router.beforeEach((to, from, next) => {
if (to.path !== '/login' && !localStorage.token) {
return next('/login')
}
next()
})

效果如图所示
login

------ 本文结束 ------

版权声明

Tflin's Blog by Tan Feng Lin is licensed under a Creative Commons BY-NC-ND 4.0 International License.
谭丰林创作并维护的Tflin's Blog博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Tflin's Blog 博客( http://tflin.com ),版权所有,侵权必究。