本节内容为实现登录与注册前端页面,并将 JWT 令牌存储在浏览器的 LocalStorage 中以实现登录状态的持久化。
1. 实现登录页面
打开我们的前端项目代码,在 src/views/user 目录下创建 account 目录,然后创建 UserAccountLoginView 和 UserAccountRegisterView 组件。
我们需要在全局存一些信息,例如每个页面都需要知道当前登录用户的信息,这就需要用到 Vue 的一个特性叫做 vuex。在 src/store 目录下创建 user.js:
1 | import $ from "jquery"; |
如果使用 Axios 发送 POST 请求可以将参数放在请求体中,也就是第二个参数:
1 | axios.post("http://localhost:3000/user/account/login/", |
注意如果使用这种方式发送请求需要在后端 Controller 中修改接收参数的方式,从 @RequestParam 修改为 @RequestBody:
1 |
|
@RequestParam 和 @RequestBody 都是 Spring MVC 中常用的参数绑定注解,它们在处理 HTTP 请求时有以下区别:
@RequestParam用于将 HTTP 请求中的参数绑定到方法的参数上,主要用于处理 GET 请求的参数或 POST 请求中的表单参数。@RequestBody用于接收整个请求体,并将其转换为方法参数所需的对象或数据类型。它主要用于处理 POST 请求,并且请求体通常是 JSON 格式。
如果后端继续使用 @RequestParam 来接收参数,那么你可以在 Axios 的 POST 请求中使用 params 对象来传递参数(注意将第二个参数设置为 null):
1 | axios.post("http://localhost:3000/user/account/login/", null, |
使用 Axios 发送带请求头的 GET 请求方式如下:
1 | axios.get("http://localhost:3000/user/account/info/", |
然后需要将其引入到 store 目录下的 index.js 中:
1 | import { createStore } from "vuex"; |
现在就可以实现我们的登录前端页面 UserAccountLoginView:
1 | <template> |
我们的导航栏也要根据登录状态显示不同的内容,可以用 v-if 和 v-else 来根据条件决定是否显示内容:
1 | <template> |
还有别忘了更新路由,即 src/router 目录下的 index.js:
1 | import { createRouter, createWebHistory } from "vue-router"; |
2. 实现退出登录功能
在上一节中我们没有实现退出登录的后端 API,我们的 jwt token 完全是存在用户本地的,令牌中会存有过期时间,服务器端能够判断令牌是否过期,因此不用管后端的退出登录。那么如果用户想自己退出登录也很简单,直接将 jwt token 删除即可,无需向后端发送请求,没有令牌后就无法访问后端服务器了。
现在我们是将令牌存在浏览器的内存中,一刷新自动就会重置,之后我们会将其存到 LocalStorage 中,这样即使用户刷新或者关闭浏览器都不会自动退出登录状态。
我们先来实现主动退出登录功能,在 store 目录的 user.js 中添加清空 state 的操作:
1 | import $ from "jquery"; |
然后在 NavBar 中调用函数:
1 | <template> |
3. 设置前端页面授权机制
现在我们的前端页面还没有任何的访问限制,例如在未登录状态下也可以访问任意的页面。当未登录时访问任何页面都应该重定向到登录页面。
页面的授权控制可以在 router 中通过 beforeEach 函数实现,当我们每次在通过 router 进入某个页面之前都会先调用该函数,函数有三个参数:to 表示要跳转到哪个页面,from 表示从哪个页面跳转过去,next 表示页面执行的下一步跳转操作。
我们每次在跳转到某个页面之前需要先判断一下该页面是否需要登录,如果需要登录且当前处于未登录状态则跳转至登录页面。因此我们就需要在每个页面中存储是否需要授权的信息,可以定义在任意名字的变量中,一般可以把额外信息放在 meta 域中。
修改后的 router/index.js 如下:
1 | import { createRouter, createWebHistory } from "vue-router"; |
4. 实现注册页面
注册页面 UserAccountRegisterView 的实现其实就和登录页面基本一致,多加一个确认密码输入框即可。注册时不会修改前端的 state 值,因此也无需将 register 函数实现在 store/user.js 中:
1 | <template> |
5. 登陆状态的持久化
我们可以将登录后获得的 jwt token 存放在浏览器的一小块硬盘空间 LocalStorage 中,首先在 store/user.js 中修改:
1 | import $ from "jquery"; |
我们在 login 函数中将登录成功后收到的 jwt token 存在 LocalStorage 中,在 logout 函数中清除 LocalStorage 中的 jwt token。需要特别注意的是 getInfo 函数中添加了 async: false,这是表示将该 Ajax 请求变为同步的,具体作用在之后讲解。
现在当我们要跳转到某个链接前可以先取出 LocalStorage 中的 jwt token,判断是否存在并且未过期,如果有效则在跳转之前直接调用 store/user.js 中的 updateJwtToken 更新浏览器内存中的 jwt token,并通过 getInfo 函数更新用户信息。还是在 router/index.js 中的 router.beforeEach 函数中实现:
1 | import { createRouter, createWebHistory } from "vue-router"; |
注意,在第一个 if 语句中调用了 store 的 getInfo 函数,由于 Ajax 的回调函数默认是异步的(Axios 也是异步),因此第二个 if 语句会在 success 回调函数执行前就被执行了,这会导致 jwt_token_valid 还没被更新,从而被判断成未登录状态,直接跳转至登录页面,所以我们在前面将 getInfo 函数中的 Ajax 设置为同步,保证了以上代码的正确执行逻辑。
如果使用 Axios,需要结合 async 与 await 关键字实现同步,getInfo 函数如下:
1 | async getInfo(context, data) { |
router.beforeEach 函数如下:
1 | router.beforeEach(async (to, from, next) => { |