本节内容为实现登录与注册前端页面,并将 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) => { |