之前用过token,也知道是用来授权的,但是不知道后端如何生成,为什么一定要用token,token又是怎么验证的等等,希望这篇文章帮到和我一样学啥忘啥的笨蛋…
文章目录
1.简单了解JWT
1.JWT 的数据结构
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7fSwidHlwZSI6ImV4cHJlc3Mtand0IiwiY3RpbWUiOjE2MDkwNzk0OTkzMzYsImlhdCI6MTYwOTA3OTQ5OSwiZXhwIjoxNjA5MDgzMDk5fQ.HESHUwi8JduBqXsnmkZMoODZXl2VvW2H7-l-4rYcpYM
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行。
2.JWT 的三个部分依次如下。
Header(头部)
Header 部分是一个 JSON 对象,描述 JWT 的元数据,通常是下面的样子
{
“alg”: “HS256”,
“typ”: “JWT”
}
Payload(负载)
Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用
Signature(签名)
-
iss (issuer):签发人
-
exp (expiration time):过期时间
-
sub (subject):主题
-
aud (audience):受众
-
nbf (Not Before):生效时间
-
iat (Issued At):签发时间
-
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(.
)分隔,就可以返回给用户
来源:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
JSON Web Token 入门教程
作者: 阮一峰 日期: 2018年7月23日
2.两种实现方式:
express-jwt和jsonwebtoken
安装:
npm install express-jwt --save
npm install jsonwebtoken --save
1.jsonwebtoken 介绍
jsonwebtoken 主要用到两个方法,一个是生成token的sign方法,还有一个时验证token的verify方法,参数和上面阮一峰老师介绍的不太一样,但是用法差不多
官网 https://www.npmjs.com/package/jsonwebtoken
使用:
1.sign方法:
// 官网例子,建议使用前面两个,我觉得好用哈哈
jwt.sign({
data: {userId: id, createTime: new Date(),...} // 自定义信息
}, 'secret', { expiresIn: 60 * 60 }); // secret密钥 , expiresIn过期时间,使用的算法参数省略,默认是 SHA256
//or even better:
jwt.sign({
data: 'foobar'
}, 'secret', { expiresIn: '1h' });
// 过期时间写在负载里面,exp字段表示,时间1970年算起
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret');
// 或者这样
var older_token = jwt.sign({ foo: 'bar', iat: Math.floor(Date.now() / 1000) - 30 }, 'shhhhh');
//这样也可以 algorithm: 'RS256' 指定加密算法
jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256' }, function(err, token) {
console.log(token);
});
丢几
我觉得前面两个比较好用,后面几个也可以的
一些字段解释:
data: 是自定义的字段信息,可以根据情况设置信息,比如加上创建token的时间,用户id等等,建议不要加密码,因为token也不是很安全的;
secret: 密钥可以是一个字符串,比如自己直接定义一个’xxxxx’, 或者为了增加安全性,用其他工具算法生成一个密钥,密钥是为了防止token在客户端时被篡改,验证时也要用到密钥,密钥不对肯定通过不了验证啦,,这个是我们服务器设置的,所以不要让其他人知道服务器密钥哦。
expiresIn: 用来设置过期时间的,像上面的两种设置方式,比如 (Data.now() / 1000 ) + (6060) , 或者直接一个对象 { expiresIn: 60 * 60 } 传过去,这两个要区别好:
(Data.now() / 1000 ) + (6060) 默认是从 1970年那个时间开始设置的,单位是毫秒,所以要用当前时间 / 1000 转换成 秒s , 然后加上要设置的过期时间 60 60 ,也就是设置一个小时过期{ expiresIn: 60 * 60 } 这种方式,直接设置过期时间就好,单位为秒s,所以直接6060就好,所以这个好用哈哈…
iat: 回溯时间
{ algorithm: ‘RS256’ } :设置使用的算法类型,默认是 SHA256 ,上面没传这个参数的就是使用了默认的签名加密算法
2.verify方法
// 只列举一个
jwt.verify(token, 'shhhhh', function(err, decoded) {
console.log(decoded.foo) // bar
});
传入要验证的token和密钥就可以了,然后回调函数拿取验证结果, 成功会返回你生成token时data自定义数据,过期时间等等,失败err拿到失败信息,比如token过期信息等等
2.express-jwt 介绍
express-jwt是nodejs的一个中间件,他来验证指定http请求的JsonWebTokens的有效性,如果有效就将JsonWebTokens的值设置到req.user里面,(也可以自定义,后面有说),然后路由到相应的router。 此模块允许您使用Node.js应用程序中的JWT令牌来验证HTTP请求。 JWT通常用于保护API端点。
express-jwt和jsonwebtoken是什么关系
express-jwt内部引用了jsonwebtoken,对其封装使用。 在实际的项目中这两个都需要引用,他们两个的定位不一样。jsonwebtoken是用来生成token给客户端的,express-jwt是用来验证token的。
上面说了express-jwt主要用来验证token,使用起来比使用原生的jsonwebtoken验证方便了…等等,我觉得原生也方便,都可以吧,express-jwt应该还有更强大的功能,我只是会用来验证哈哈
注意点: 生成token都是用jsonwebtoken的sign方法,只是有一点区别要注意,就是如果用express-jwt中间件验证,要在生产的token前加上’ Bearer ’ ,后面有个空格的
也就是后端返回的token格式是 let toeken = ’ Bearer ’ + jwt.sign(data,secret, {expiresIn: 60*60})或者直接放回token, 交给前端处理,前端请求时带上token时前加上 ’
Bearer ’放到 authorization 这个header里, 对应的值以Bearer开头然后空一格
像这样:
authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQmluTWFpbmciLCJkYXRhIjoiPT09PT09PT09PT09PSIsImlhdCI6MTUwMTgxNDE4OCwiZXhwIjoxNTAxODE0MjQ4fQ.GoxGlc6E02W5VvqDNawaOrj3MPO-4UYeFdngKR4bVTE
至于为什么要加,源码是这样判断的啊
来源:
https://blog.csdn.net/qq_27818541/article/details/76656784
//1 从options中获取token 这个忽略 因为 在设置 需要保护的API 时 并没有传递 getToken 这个方法
if (options.getToken && typeof options.getToken === 'function') {
try {
token = options.getToken(req);
} catch (e) {
return next(e);
}
//2 从authorization中获取token
} else if (req.headers && req.headers.authorization) {
// --这是关键代码-----开始切割--------->
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var scheme = parts[0];
var credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials; // <-------最终获取到token---------
} else {
if (credentialsRequired) {
return next(new UnauthorizedError('credentials_bad_scheme', { message: 'Format is Authorization: Bearer [token]' }));
} else {
return next();
}
}
//3 以上两个途径都没有token时 就报错呗
} else {
return next(new UnauthorizedError('credentials_bad_format', { message: 'Format is Authorization: Bearer [token]' }));
}
}
重点:
express-jwt验证使用方式:
// 验证--放到最前面的use
app.use(expressJWT({
secret: secret, // 密钥
algorithms: ['HS256'], // 要加才能对
// requestProperty:'auth',//自定义获取的信息位置,默认验证通过req.user获取token信息
credentialsRequired: false //是否允许无token请求
}).unless({
path: ['/login'] //除了这个地址,其他的URL都需要验证
}))
3.两者使用具体栗子
上面只是介绍,下面就来看看怎么用起来,不难的,看完咯!!
1.用jsonwebtoken实现生成和验证
说明:我用一个token.js文件封装了生成和验证的方法,这样要用到的地方调用就好啦
/*
* @Author: wuwle
* @Date: 2020-12-27 10:24:01
* @LastEditors: wuwle
* @LastEditTime: 2020-12-27 22:30:28
* @FilePath: \demo\vue\毕设\server\utils\jwt\token.js
*/
const jwt = require('jsonwebtoken');
const secret = 'kelexiaoyu'; // 密钥,防止篡改,我就直接一个字符串了,不用密钥生成了,大家也可以放到一个文件中,然后读取fs.readFile()
// 生成方法---data是自定义信息,exp是传的过期时间
let createToken = function (data, exp) {
let oj = {};
obj.data = data ? data : null;
obj.type = 'jsonwebtoken'; // 加个类型哈哈
obj.ctime = new Date().getTime(); //token的创建时间
// obj.exp = Math.floor(Date.now() / 1000) + 60 * 60; // 如果直接放在data这里,要这样设置过期时间,并且这样设置过期时间要加上当前时间从1970开始算,所以要用当前时间 + 过期时间,单位毫秒,除1000换成s
// 用expiresIn就不用,直接设置过期时间
exp = exp ? exp : 60 * 60 * 24; //设定的过期时间,不设置就默认1天
let token = jwt.sign(obj, secret, { expiresIn: exp }); // 调用方法生成
return token;
};
// 验证,传入token
let varifyToken = (token) => {
var info = jwt.verify(token, secret, (error, res) => {
var data = {}; // 通过回调函数自定义返回信息,不然默认是创建token时传进去的obj和时间信息,这里加上状态码这些
if (error) {
data.code = '606'; // 自个定义失败码
data.msg = 'token验证失败';
data.obj = error; // 存失败信息,比如过期等
} else {
data.code = '608';
data.msg = 'token验证成功';
data.obj = res;
}
return data;
});
return info; // 就是我们修改后的data
};
module.exports = { createToken, varifyToken };
这样就封装了token的生成和验证,但是还没有完成,我们只是对token有效性做了验证,还没有对接口做设置,比如哪些接口需要验证,哪些不需要,那么就再封装一个方法咯哈哈
当然你现在就可以去测试一下token生成了,这个可以直接用postman测一下,如果你懒得测试,想把验证过程都写完再测试也可以,那就看下面:
我封装的如下:
/*
* @Author: wuwle
* @Date: 2020-12-27 10:40:11
* @LastEditors: wuwle
* @LastEditTime: 2020-12-27 22:33:25
* @FilePath: \demo\vue\毕设\server\utils\jwt\checkToken.js
*/
// 接受请求并验证
const { varifyToken } = require('./token.js');
// 放最开始的app.use(),接收所有请求
module.exports = (req, res, next) => {
console.log('进来登录了哎呦');
let url = req._parsedUrl.pathname; // 当前访问的url,不包括参数?后面的,不信你自己打印一下看看哈哈
let whiteUrl = ['/login']; // 白名单,不需要验证的,暂时只有/login,后面有再加
if (whiteUrl.indexOf(url) >= 0) {
return next(); // 找到是白名单的,直接放行,不需要验证
}
console.log('进来验证了亚黑');
// 验证
let token = req.headers.authorization; // 拿到前端传过来的token
let result = varifyToken(token); // 调用上一个文件里面的验证方法
if (result.code == 606) {
res.send(result); // 验证失败,返回验证信息
} else {
req.resToken = result; // 存成功的信息,用到时可以用
return next(); // 成功就放行,可以访问接口
}
};
这样就完成了验证,虽然封装的一般般,但是我先这样用…
node后端测试接口:
写个/login测试
//导入创建token和验证文件
const checkToken = require('./utils/jwt/checkToken');
const {createToken} = require('./utils/jwt/token')
const express = require('express');
const app = express();
app.use(checkToken); // 验证,第二个封装的文件
app.post('/login', (req, res, next) => {
// 登录用户名密码验证--还没写...
//验证成功放回信息加上token
res.send({
isLogin: 'true',
token: createToken({}, '1h') // 返回生成的token
});
});
// 登录后访问
app.get('/home', (req, res, next) => {
res.send(req.resToken); // 返回token信息,实际情况自己根据来返回
});
然后就可以愉快的测试啦,我一开始用postman,发现传过来的token自动带上Bearer,导致验证失败,我这个暴脾气,那就自己写接口,本来也要写的哈哈哈,然后就写个登录的接口,这个不用贴代码了吧…因为我前端文件分的有点多,用vue,用axios发个请求,然后生成的token我用localfoage存着,在请求拦截设置头部的authorization,我相信大家都知道这个
// 请求拦截
service.interceptors.request.use(
async config => {
// 读取token
await localForage.getItem('myuniquekey').then(res => {
// 添加token
config.headers.authorization = res
})
return config
},
error => {
console.log('请求拦截报错:', error)
return Promise.reject(error)
}
)
登录返回token信息如下:
那么token可以正确返回了
然后再写个接口在登录后去访问/home,返回token信息如下
可以看到data为空,因为我传的就是空哈哈哈,然后主要看过期时间exp,用1790年到我们设置的过期时间表示的,单位s,你用这个时间减去ctime(token创建时间)/ 1000,创建时间是毫秒要除1000转换s, 相减就得到3600s,就是我们设置过期时间1h啦
还有自动带了iat回溯时间,具体这个干嘛用,不知道,我没去详细了解,懒得去看了…暂时没用到
如果设置过期时间很短,比如我测试10s,过期后返回如下,
那么关于jsonwebtoken使用就先到这了,下面是结合express-jwt的使用
2.jsonwebtoken + express-jwt
同样要封装一下咯,这里我生成token时加上 Bearer
/*
* @Author: wuwle
* @Date: 2020-12-27 22:03:32
* @LastEditors: wuwle
* @LastEditTime: 2020-12-27 23:26:06
* @FilePath: \demo\vue\毕设\server\utils\ejwt\token.js
*/
const jwt = require('jsonwebtoken');
// 生成token
const secret = 'kelexiaoyu'; // 密钥,防止篡改
let createToken = function (data, exp) {
let obj = {};
obj.data = data ? data : null;
obj.type = 'express-jwt';
obj.ctime = new Date().getTime(); //token的创建时间
// 用expiresIn单独传就不用,直接设置过期时间
exp = exp ? exp : 60 * 60 * 24; //设定的过期时间,不设置就默认1天
//区别: 用expressJwt要加上Bearer,后面有个空格的,因为源码是这样写的...咋也不敢问为啥这样写啊..
let token = 'Bearer ' + jwt.sign(obj, secret, { expiresIn: exp });
return token;
};
module.exports = createToken;
验证和失败处理封装如下
/*
* @Author: wuwle
* @Date: 2020-12-27 22:04:17
* @LastEditors: wuwle
* @LastEditTime: 2020-12-28 00:23:14
* @FilePath: \demo\vue\毕设\server\utils\ejwt\checkToken.js
*/
const expressJWT = require('express-jwt');
const secret = 'kelexiaoyu'; // 密钥,防止篡改
// 验证--放到最前面的use
const varifyToken = () => {
return expressJWT({
secret: secret,
algorithms: ['HS256'], // 要加才能对
// requestProperty:'auth',//自定义获取的信息位置,默认验证通过req.user获取token信息
credentialsRequired: false //是否允许无token请求
}).unless({
path: ['/login'] //除了这个地址,其他的URL都需要验证
});
};
// 失败处理--放到最后一个app.use()
const errorToken = (err, req, res, next) => {
if (err.name === 'UnauthorizedError') {
// 这个需要根据自己的业务逻辑来处理( 具体的err值 请看下面)
let obj = {};
obj.msg = 'token验证失败';
obj.code = '606';
obj.error = err;
res.send(obj); //返回失败信息
}
};
module.exports = {
varifyToken,
errorToken
};
// 说明:
// token过期时的err值:
// {
// "name": "UnauthorizedError", 所以我们可以用这个来判断是否验证失败
// "message": "jwt expired",
// "code": "invalid_token",
// "status": 401,
// "inner": {
// "name": "TokenExpiredError",
// "message": "jwt expired",
// "expiredAt": "2017-08-03T10:08:44.000Z"
// }
// }
// token无效时的err值:
// {
// "name": "UnauthorizedError",
// "message": "invalid signature",
// "code": "invalid_token",
// "status": 401,
// "inner": {
// "name": "JsonWebTokenError",
// "message": "invalid signature"
// }
// }
node后端调用:
/*
* @Author: wuwle
* @Date: 2020-12-23 22:54:53
* @LastEditors: wuwle
* @LastEditTime: 2020-12-28 00:24:21
* @FilePath: \demo\vue\毕设\server\app.js
*/
//expressjwt + jsonwebtoken
const { varifyToken, errorToken } = require('./utils/ejwt/checkToken');
const createToken = require('./utils/ejwt/token');
const express = require('express');
const app = express();
// app.use(checkToken);
app.use(varifyToken()); // 接收所有请求验证
app.post('/login', (req, res, next) => {
// 登录用户名密码验证
//验证成功放回信息加上token
res.send({
isLogin: 'true',
token: createToken({}, 10)
});
});
app.get('/home', (req, res, next) => {
// res.send(req.resToken);
res.send(req.user); // 默认这样拿验证通过的token信息
});
// token失败处理
app.use(errorToken);
app.listen(8886, () => {
console.log('服务器启动成功...端口号8886');
});
这个测试就不贴了,和上面一样测。
总结:
可以看到两者使用都不难,相信大家都学会了,,使用哪个大家自行选择,还有用token一般是用来授权,并不是绝对安全的,对于重要接口,可设置过期时间短点,或者结合其他验证使用…
最后写的不好的地方多多交流,我也是今天刚要用到学的,因为我看了好多篇文章,发现没有写的比较详细的,都是直接介绍封装使用较多,,所以写了文章做下笔记,后面发现优化等方法再更新…呼…哈