前后端鉴权是保障系统安全性和数据隐私的重要环节。
前端鉴权主要侧重于在用户界面上进行初步的验证和授权操作。后端鉴权主要是对前端传来的身份验证信息进行严格的验证和授权。本文分别讲解一下前后端的鉴权。
完成效果Token不存在Token不正确前端鉴权(路由守卫)前端鉴权主要是防君子不防小人。
当用户访问需要登录权限的页面地址时,前端判断本地是否存在用户信息,如果存在,则直接跳转页面,否则跳转登录页面。
这里我们通过为vue项目添加路由守卫去实现这个效果。
const whiteList = ["/login", "/register"];router.beforeEach((to, from, next) => { // 如果是白名单的路由就不需要检查本地存储是否有用户数据,其他路由需要有用户数据,否则跳转登录 if (whiteList.includes(to.path)) { next(); } else { if (localStorage.getItem("userInfo")) { next(); } else { next("/login"); } }});以下是对这段代码的分析:
定义了一个白名单数组 whiteList,其中包含了不需要检查用户登录状态的路由路径。使用 router.beforeEach() 方法注册了一个全局前置守卫,它会在每次路由跳转前被调用。在守卫函数中,首先判断当前要跳转的路由是否在白名单中。如果在白名单中,则直接允许路由跳转,不需要检查用户登录状态。如果不在白名单中,则检查本地存储 localStorage 是否存在名为 "userInfo" 的数据。如果存在 "userInfo" 数据,则允许路由跳转。如果不存在 "userInfo" 数据,则强制跳转到 /login 路由,要求用户登录。效果展示:
可以看到效果为我们在没有登陆的状态下,url地址输入其他的url地址,我们就会自动跳到登录页面。
但是这里有一个缺陷,我们只是通过去判断本地存储是否有userInfo数据,有就放行,这是不严谨的,因为正常用户也许确实可以防止,但是如果是程序员自己手动在本地存储放入了userInfo数据,这就会导致这个拦截失效。
可见这种方式只能防止普通用户,如果要加强防护接下来就要使用到后端鉴权了。
后端鉴权(Token)后端鉴权则更为关键和复杂,本文的后端采用的是Node.js。
后端需要对前端传来的身份验证信息进行严格的验证和授权。这包括验证令牌的有效性、检查用户的权限级别,以确定用户是否有权访问特定的资源或执行特定的操作。
这里我们做一个简单的小demo。
根据前端传过来的数据,生成一个jwt,然后要求前端在访问其他需要鉴权的请求时需要带上这个token。
首先我们需要安装生成jwt的依赖。
npm i jsonwebtoken接下来封装一个方法用于专门生成jwt。
jwt.jsconst jwt = require('jsonwebtoken');function sign(option){ return jwt.sign(option, 'secret', { expiresIn: '24' });}module.exports = { sign}然后在登录接口中成功验证完账户和密码之后添加这个jwt的生成,并将jwt返回给前端。
const { sign } = require("../utils/jwt.js");let data = { id: res[0].id, nickname: res[0].nickname, username: res[0].username, }; let token = sign(data); ctx.body = { code: 8000, msg: "登录成功", data, token: token, };从 ../utils/jwt.js 文件中导入 sign 函数,该函数用于生成 JWT 令牌。从数据库查询到的用户信息中,提取出用户的 id、nickname 和 username 等关键信息,存储在 data 对象中。调用 sign 函数,使用 data 对象作为载荷,生成一个 JWT 令牌,并存储在 token 变量中。在响应数据中,返回以下信息:code:返回状态码,这里是 8000。msg:返回信息,这里是 "登录成功"。data:用户信息对象。token:生成的 JWT 令牌。现在我们打印查看我们生成到的token。
可以看到前端也同样拿到了token。
在之后的请求中我们就需要在后端添加一个拦截器,对于需要鉴权的请求都进行拦截,同时拿到token去进行权限的鉴定,只有token是合法的且有效的,才能够放行请求。
继续我们代码的书写。
现在我们就需要一个拦截器去在每次发送请求时带上Token。
@api/index.js// 添加请求拦截器axios.interceptors.request.use(function (req) { // 在发送请求之前添加token const userInfo = localStorage.getItem("userInfo"); if (userInfo) { const UserInfo = JSON.parse(userInfo); req.headers.Authorization = UserInfo.token; } return req;});既然有生成Token的方法,我们就需要解开Token的方法。
jwt.js// 验证tokenfunction verify() { return (ctx, next) => { const token = ctx.header.authorization; if (token) { console.log(token); // 验证token合法性 try { const dexoded = jwt.verify(token, "secret"); console.log(dexoded, "////"); if (dexoded.id) { next(); } } catch (err) { ctx.body = { code: 401, msg: "token失效", data: "error", }; } } else { ctx.body = { code: 401, msg: "token未提供", data: "error", }; } };}定义了一个 verify 函数,它返回一个中间件函数。在中间件函数中,首先从请求头中获取 authorization 字段,该字段通常包含了 JWT 令牌。如果 authorization 字段存在,则将其打印到控制台进行验证。接下来,使用 jwt.verify() 函数验证 JWT 令牌的合法性。该函数需要传入两个参数:token: 要验证的 JWT 令牌。"secret": 用于签名 JWT 令牌的密钥。如果 JWT 令牌验证成功,则说明用户已经登录,可以允许访问后续的资源。此时,中间件会调用 next() 函数,让请求继续向下执行。如果 JWT 令牌验证失败,则说明令牌已经失效或不合法。此时,中间件会返回一个 HTTP 状态码为 401 的响应,表示未授权访问。响应内容包括错误码 401、错误信息 "token失效" 和错误数据 "error"。如果 authorization 字段不存在,则说明用户没有提供 JWT 令牌。此时,中间件会返回一个 HTTP 状态码为 401 的响应,表示未授权访问。响应内容包括错误码 401、错误信息 "token未提供" 和错误数据 "error"。将方法添加到需要使用Token的接口中。
// 测试tokenrouter.post("/home", verify(), async (ctx) => { ctx.body = { code: 8000, msg: "访问成功", data: "succeed", };});定义了一个 POST 请求路由 /home。在路由处理程序中,首先调用了 verify() 中间件函数。这个中间件函数会验证请求头中是否包含有效的 JWT 令牌。如果 JWT 令牌验证成功,则允许访问该路由,并返回一个包含以下内容的响应:code:返回状态码,这里是 8000。msg:返回信息,这里是 "访问成功"。data:返回数据,这里是 "succeed"。如果 JWT 令牌验证失败,则中间件函数会返回一个包含错误信息的响应,阻止访问该路由。只有经过身份验证的用户,才能访问这个 /home 路由并获取相应的数据。
这样后端就成功的通过验证Token来判断用户的状态。
由于当Token失效或者没有的时候会返回401的状态码。
这个时候前端收到了401的状态码就说明Token异常了,就需要跳转到登录页面去重新登陆。
继续修改前端代码,为其添加一个响应拦截器。
// 添加响应拦截器axios.interceptors.response.use(function (res) { if (res.status !== 200) { // 程序错误 console.log(res.status); showToast("服务器异常"); return Promise.reject(res); } else { console.log(res.status); if (res.data.code === 401) { showToast("登录过期,正在跳转至登录页···"); setTimeout(() => { router.push("/login"); }, 1500); return Promise.reject(res); } if (res.data.code !== 8000) { // 业务逻辑错误 showToast(res.data.msg); return Promise.reject(res); } return res.data; }});使用 axios.interceptors.response.use() 方法注册一个响应拦截器。在响应拦截器中,首先检查 HTTP 状态码是否为 200。如果不是 200,则说明程序出现了错误,会在控制台输出状态码,并弹出一个吐司提示 "服务器异常"。之后会直接 reject 这个响应,不再执行后续的业务逻辑。如果 HTTP 状态码为 200,则进一步检查响应数据中的 code 字段。如果 code 值为 401,则说明用户的登录凭证已经过期。此时会弹出一个吐司提示 "登录过期,正在跳转至登录页···",并在 1.5 秒后跳转到登录页面。之后会直接 reject 这个响应,不再执行后续的业务逻辑。如果 code 值不为 8000,则说明存在业务逻辑错误。此时会弹出一个吐司提示,显示响应数据中的 msg 字段。之后会直接 reject 这个响应,不再执行后续的业务逻辑。如果 code 值为 8000,则说明请求成功,可以直接返回响应数据中的 data 字段。最终我们就成功的实现了效果
作者:爱吃土豆丝呦链接:https://juejin.cn/post/7391699424844070963