身份验证是现代 Web 应用程序中至关重要的一部分。它确保只有经过授权的用户才能访问敏感数据和功能。而 JSON Web Token(JWT) 作为一种流行的身份验证机制,成为了目前最炙手可热的跨域认证解决方案之一。它利用令牌在客户端和服务器之间进行交互,从而验证用户的身份。本文将介绍 JWT 认证的相关知识以及利用 Node.js 和 Vue2 实现前后端之间的令牌交互的方法。
1.JWT的组成
JWT是一种用于身份验证和授权的开放标准,它由三部分组成:头信息(header)、消息体(payload)和签名(signature)。它以长字符串的形式存在,中间由点号(.)分隔成三个部分。
- 头信息包含了关于生成 JW T的算法和类型等元数据,通常包括两个字段:alg(算法)和 type(类型),它们都是 Base64Url 编码后的 JSON 对象。
- 消息体包含与 JWT 相关的声明信息,比如用户 ID、角色、过期时间等。这些声明都是 JSON 对象,同样采用 Base64Url 编码。可以在消息体中添加自定义的声明信息,但要注意不要泄露敏感信息。
- 签名则是使用 Secret Key 或 RSA 等公私钥对头信息和消息体进行加密,以确保 JWT 的完整性和真实性。签名使用头信息中指定的算法进行计算,并将结果附加到 JWT 的末尾,同样采用 Base64Url 编码。
当客户端发送请求时,服务器会验证 JWT 的合法性,确认 JWT 中的声明信息是否真实可信。为了验证 JWT 的合法性,服务器需要先解码 JWT,获取其中的头信息和消息体,并重新计算签名,以验证其有效性。如果签名验证通过,服务器便可进一步根据 JWT 中的声明信息完成对用户的认证和授权。
2.JWT特点
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
3.JWT认证流程
- 1.服务端在登录接口中将用户信息等进行加密成 JWT 发送给客户端。
- 2.客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
- 3.此后,客户端每次与服务器通信,都要带上这个 JWT。可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
- 4.服务端使用自己保存的 key 解密 JWT,计算、验证签名以判断该JWT 是否可信。
4.代码实现
(1)后端生成token
jsonwebtoken模块:用于 token 的生成和验证。jsonwebtoken文档
import jwt from 'jsonwebtoken';
let secretkey="填写用于加密的key",
let expiresIn=60 * 60 * 2 //填写token的过期时间 单位秒 这里是2小时
// info 为加密的信息 尽量不要包含敏感信息
export const getToken=async (ctx:Context,info:object,time:number=expiresIn):Promise<string>=>{
try{
const token = jwt.sign({info}, secretkey, {expiresIn:time})
return token;
}catch(e){
throw new ErrorPlus('生成token失败'); // 抛出错误 这里是我自己定义的异常类
}
}
在用户登录成功会后调用函数生成 token 并将 token 返回给前端。
(2)前端处理token
前端在收到 token 后首先将 token 存储在 localstorage 或 cookies 中:
localStorage.setItem("token", token)
配置 axios 请求拦截器和响应拦截器:
import axios from 'axios'
// 创建实例时配置默认值
const instance = axios.create({
baseURL, // 请求的基地址
timeout: 10000 //超时时间
})
// http request 拦截器:是在ajax请求发出之前的操作
// 给每个请求头加上token 用于后端验证
instance.interceptors.request.use(
config => {
let token = localStorage.getItem('mx_token');
// 判断是否存在token 如果存在则在每个 http header都加上token
if (token) {
config.headers.Authorization = `token ${token}`;
}
//必须return回去
return config;
},
err => {
return Promise.reject(err);
}
)
//axios响应拦截器
// 如果我们使用中需要统一处理所有 http 请求和响应, 就需要使用 axios 拦截器。
// 通过配置 http response inteceptor, 如果后端接口返回 401 Unauthorized(说明该用户未授权), 用户需重新登录。
instance.interceptors.response.use(
response => {
return response.data;
},
err => {
if (err.response) {
switch (err.response.status) {
case 401:
//返回401清除token信息并跳转到登录界面
localStorage.removeItem('mx_token');
router.replace({
path: '/login'
})
}
}
return Promise.reject(err.response.data);
}
)
(3)后端编写中间件验证token
解密函数:从请求头中取出token,并验证token的正确性,如果发现没有token,或token过期,token错误等抛出401异常。
// 解密token
export const deToken = async (token: string): Promise<object> => {
try {
if(!token){
throw new ErrorPlus('Token为空',401);
}
let token_arr=token.split(' ');
if(token_arr[0]!=='token'){
throw new ErrorPlus('Token格式错误',401);
}
// 进行解密,解密为uid和scope
const res = jwt.verify(token_arr[1], secretkey) as any;
return res;
} catch (error) {
if ((error as any).name === 'TokenExpiredError') {
throw new ErrorPlus('Token已过期',401);
} else {
throw error;
}
}
};
编写身份认证的 node 中间件,即在每一个请求处理之前,首先验证 token 的正确性。如果验证通过则继续向下执行,否则直接抛出异常。
const checkAuth=async (ctx:Context,next:Next)=>{
try{
let token = ctx.req.headers['authorization'] as string;
const res=await deToken(token) as any;
ctx.userid=res.info;
await next();
}catch(e){
throw e;
}
}
在路由中使用中间件:
import checkAuth from '../middleware/jwt';
// 全局使用
app.use(checkAuth);
// 单个路由使用
router.post('/update_info',checkAuth,update_info);






