1、和浏览器本身是没有关系,是凭空生成的(更安全),和session/cookie不一样(浏览器自带的,可以进行管理)。
2、JWT在手机,浏览器、平板、桌面应用都可以使用。
3、jwt只是一个令牌格式而已,你可以把它存储到cookie,也可以存储到localstorage,没有任何限制!
4、session和cookie都保存在cookie。
概念:
jwt全称Json Web Token,强行翻译过来就是json格式的互联网令牌。
它要解决的问题,就是为多种终端设备,提供统一的、安全的令牌格式。
流程:
jwt 认证流程
登录
① 用户登录时,将用户名和密码通过 POST 提交到服务端
② 服务端接收到登录数据之后,与数据库中的用户信息进行校验
③ 校验通过
④ 服务端通过用户 _id,secret 等等相关数据生成 JWT 字符串
⑤ 将 JWT 字符串和其他信息一起返回给浏览器,浏览器拿到 jwt 字符串后,缓存到本地,等待下一次请求
下一次请求
① 用户登录之后的每次请求,都会将 token 携带在请求头的 Authorization 中
② 对 token 验证成功之后,我们可以拿到保存在 token 中的用户数据
③ 进入业务处理
组成:
1、header:令牌头部,记录了整个令牌的类型和签名算法。(本身就已经定死了)
2、payload:令牌负荷,记录了保存的主体信息,比如你要保存的用户信息就可以放到这里。
3、signature:令牌签名,按照头部固定的签名算法对整个令牌进行签名,该签名的作用是:保证令牌不被伪造和篡改。
一个完整的令牌(字符串)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE1ODc1NDgyMTV9.BCwUy3jnUQ_E6TqCayc7rCHkx-vxxdagUwPOWqwYCFc
使用步骤:
nodejs实现的JWT,我们需要借助第三方插件jsonwebtoken
1、在后台安装中间件。
npm i jsonwebtoken
2、在后端路由生成一个JWT文件。
服务器需要生成和验证jwt的工具。
let jwt = require("jsonwebtoken");//引入文件
let secret = "yingside";//设置一个解密的钥匙
/* 加密的一个函数,生成JWT */
/* 第一个参数就是jwt的第二部分,我们需要保存的值,在调用时传入,第二个参数是过期时间(这里写的是秒数) */
function createJWT(payload = {},maxAge = "60 * 60 * 24"){
let token = jwt.sign(payload,secret,{
expiresIn:maxAge
});
return token;
}
/* 解密的函数 */
function verify(token){
let result = jwt.verify(token,secret);//第一个是jwt的内容,第二个是解密钥匙
return result;
}
let token = createJWT({
name:"张三",
age:18,
id:1
},"2 days");
console.log(token);
//eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoi5byg5LiJIiwiYWdlIjoxOCwiaWQiOjEsImlhdCI6MTY0OTcyODQ2MiwiZXhwIjoxNjQ5OTAxMjYyfQ.r_90JA5RlsshKLFFlcya3QrTZpdXvl5DkSZyzXeDdkI
let result = verify(token);
console.log(result)
//{ name: '张三', age: 18, id: 1, iat: 1649728462, exp: 1649901262 }
3、生成jwt并发送给浏览器。
当用户登录成功时需要一个jwt的生成并发送到浏览器,下一次访问时也需要进行验证。
/* 生成jwt并发送给浏览器 */
module.exports.publishToken = (res,payload={},maxAge=60*60*24) => {
let token = createJWT(payload,maxAge);
res.header("authorization",token);//接收登录路由发的登录信息生成,通过响应头发送,响应头有一个属性是authorization,把token值放在这个属性里
return token;
}
4、在登录的路由里引入jwt文件,使用jwt的方法把登录的信息传过去生成jwt并发送给浏览器。
/* 登录 */
router.post('/login', async function (req, res) {
let {name,psw} = req.body; //获取请求数据
let arr= await serviceAdim.find();//得到数据库所有账号密码
let result = arr.filter(item => item.name == name && item.psw == psw)[0]; //得到结果
if(result){
//注意:生成jwt的 负荷payload 只能是简单对象,不能是层级很深复杂对象
let token = jwt.publishToken(res,{_id:result._id,name:result.name,pwd:result.paw});//使用jwt的方法,把登录的信息生成token发给浏览器
res.send({code:0,msg:"登录成功",data:result});
}
else{
res.send({code:1,msg:"登录失败",data:null});
}
});
5、在前端的登录模块中去获取后台jwt文件发送过来的token。
由于是通过响应头发送的,所以获取也要在响应头获取,通过xhr对象获取里面的值。
是在头里面的getResponseHeader的authorization属性,这个属性就是后台传过来的
然后把token存入到本地。
登录模块的登录事件
async loginFn() { //执行异步
let name = this.username.value;
let psw = this.psw.value;
if (name && psw) { //判断是否有值
$.ajax({
url: "/api/adim/login",
type: "post",
data: {
name,
psw
},
success: function (res,txt,xhr) {
// console.log(res);
// console.log(txt);//字符串
// console.log(xhr);//原始的对象,可以在里面得到头信息,得到传过来的token
if (res.code === 0) {
alert("登录成功");
//通过header获取后端传送过来的token,是在头里面的getResponseHeader的authorization属性,这个属性就是后台传过来的
let token = xhr.getResponseHeader("authorization");
//保存token到本地
localStorage.setItem("token",token);
location.hash = "#/";
} else {
alert("登录失败");
}
}
})
} else {
alert('请输入账号和密码')
}
}
6、每一次请求,浏览器带着Jwt一起发送后端进行验证。
由于浏览器都需要带着jwt一起发送,但是有不像cookie会自动带着。
需要自己每次都单独去获取本地的token,再发送过去。
所以我们直接在前端的总文件中每一次都进行这个操作,在mian.js引入jquery发送请求。
/* jwt发送token */
$.ajaxSetup({
beforeSend: function(xhr){
//获取token
let token = localStorage.getItem("token");
//如果jwt存在,那么就把jwt发送到服务器
//发送请求头authorization属性
//值是这个样子的: Bearer token值
//加上Bearer是一种习惯 auth2.0的认证方式
if(token){
xhr.setRequestHeader("authorization",`Bearer ${token}`);
}
}
})
请求时需要注意几点:
1、由于jwt只是在浏览器本地存放了一个字符串。
2、他不像cookie,会随着请求头自动发送到服务器。
3、 jwt 只能我们自己每次获取并发送到服务器。
4、现在jwt保存在localstotorage中,我们可以直接获取,然后通过请求头发送到服务器
5、我们每次请求其实都是一个ajax请求,现在使用的是jquery。
6、那么我们可以把这个jwt放到jquery每次请求中($.ajaxSetup 在jquery3.0以后的版本中是一个方法,方法中的参数是一个对象,之前的版本是一个对象)
7、在得到每次浏览器请求的token后,下一次的请求就需要进行token验证,所以在jwt文件中还需要验证的方法。
/* 验证jwt */
module.exports.verifyToken = (req) => { //所以,每一次请求。都必须附带着jwt token 传送过来,要通过请求获取到jwt
let token = req.headers.authorization;
if(!token){//没有
return null;
}
//一般传送过来的token都是 Bearer token值
token = token.split(" ");
//当然Bearer这个单词也有可能没有
token = token.length === 1 ? token[0] : token[1];
判断是否解密成功
try{
let result = verify(token);//把token解密
return result;
}catch(err){
return null;
}
}
8、在中间件文件中去判断请求的条件以及权限。
需要在中间件文件中引入jwt文件。
var{pathToRegexp} = require("path-to-regexp");
const jwt = require("./jwt.js");
/* 判断每次请求,对应user里的操作 */
let needToToken = [
{method:"GET",path:"/user"},///user是路由里面设置的路径入口
{method:"GET",path:"/user/:id"},
{method:"PUT",path:"/user/:id"},
{method:"DELETE",path:"/user/:id"},
{method:"POST",path:"/user/add"},
];
//注意:路由地址是/user/:id
//但是用户真实传过来的地址是/user/1 /user/2
module.exports = function(req,res,next){
console.log(req.method,req.path);//得到请求的方式和路径
let apis = needToToken.filter(item=>{
let reg = pathToRegexp(item.path);//把路径 /user/2转换为/user/:id
//验证地址是不是需要验证的地址
let flag = reg.test(req.path);
return item.method === req.method && flag;
});
//请求的地址在验证的数据里没有,就直接通过,如果有就需要判断有没有权限
if(apis.length <= 0){
next();
return;
}
/* jwt方式 */
let result = jwt.verifyToken(req);//通过中间件把值传到jwt,本身req就带了请求头
if(result){//认证通过
next();
}else{
res.send({code:1,msg:"验证失败",data:null});
}
}
9、验证之后就可以进行页面的跳转和访问。
由于主页面是导航页面,所以验证需要写在导航页面,只有通过了,才能进首页并执行相关操作。
handle() {
/* jwt方式 */
let token = window.localStorage.getItem("token");//获取本地的token
if (!token) { //如果token都没有,那么肯定没有登录,就跳转到登录
location.hash = "#/login";
return;
} else {
$.ajax({ //如果有token当然还需要到后端进行验证
url: "/api/adim/verify/woami",
type: "GET",
success: function (res) {
if (res.code === 0) {
$("#username").text(res.data.name); //把登录名给添加在页面上
} else {
location.hash = "#/login";
}
}
});
}
}
10、第9步得到了token,同时也需要后端登录的路由里面验证是否正确。
验证登录
router.get("/verify/woami",async function(req,res,next){
/* jwt方式的验证 */
let result = jwt.verifyToken(req);//得到一个解密的jwt
let arr= await serviceAdim.find();//得到数据库所有账号密码
let token = arr.filter(item => item._id == result._id)[0]; //得到结果
//如果再严谨一点,可以通过解密之后的result._id,到数据库再验证一下,重新获取一下用户信息
if(token){
res.send({code:0,msg:"验证成功",data:token});
}
else{
res.send({code:1,msg:"验证失败",data:null});
}
});
11、退出操作。
把本地存储的token清空。
由于是直接点击退出就删除本次储存,需要经过后端,所以直接在前端绑定事件就可以了。
/* 点击退出 */
$("#logout").on("click", function () {
/* jwt的退出 */
localStorage.removeItem('token');
alert("退出成功")
location.hash = "#/login";
});
以上操作就完成了基础的jwt验证。