深入浅出JWT

news2024/9/28 9:33:54

什么是JWT?

JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

  • JSON Web Token 是一个开放标准(RFC 7519)
  • 定义了一种紧凑且独立的方式,可以将各方之间的信息作为JSON对象进行安全传输
  • 该信息可以验证信任,因为是经过数字签名

JWT的构成

  • 头部(Header)
  • 有效载荷(Payload)
  • 签名(Signature)

JWT分成了三个部分,每个部分都有黑点隔开

更多精彩内容,请微信搜索“前端爱好者戳我 查看

Header本质是个JSON,这个JSON里面有2个字段

  • typ:token的类型,这里固定有JWT
  • alg:使用的hash算法,例如:HMAC SHA256或者RSA

Header编码前后

  • {“alg”:“HS256”,“typ”:“JWT”}
  • 编码后就是一段Base64字符串

Payload

  • 存储需要传递的信息,如用户ID、用户名等
  • 还包含元数据,如过期时间、发布人等
  • 与Header不同,Playload可以加密

Playload编码前后

  • {“user_id”:“xiaofengche”}
  • 编码后就是一段Base64字符串

Signature

  • 对Header和Payload部分进行签名
  • 保证Token在传输的过程中没有被篡改或者损坏

Signature算法

Signature = HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

生成完签名之后依然需要进行Base64编码

其优点如下:

  • 支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
  • 无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
  • 更适用CDN:可以通过内容分发网络请求服务端的所有资料
  • 更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
  • 无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御

JWT工作原理

客户端(浏览器)通过POST请求将用户名和密码传给服务器,服务端对用户名和密码进行核对,核对成功后将用户ID等其他信息作为JWT的有效载荷,将其与头部进行base64编码后形成一个JWT,然后后端将那一段字符串作为登录成功这个请求的返回结果返回给前端,然后前端将其保存在localStorage或者sessionStorage中。

之后前端每次请求都会把JWT字符串作为Http头里面的Authorization(鉴权),然后发送给后端,后端检查其是否存在,如果存在则验证JWT字符串的有效性(例如签名是否正确,令牌是否过期等)。

验证通过后,后端则使用JWT中包含的用户信息进行其他业务逻辑并返回相应的结果。

Session简介

Session是一种非常重要非常流行的用户认证授权的方式。

认证:让服务器知道你是是谁
授权:让服务器知道你什么能干什么不能干

Session的优势

  • 相比JWT,最大的优势就在于可以主动清除session了(因为session是保存在服务端的,服务端可以主动清除;JWT是以Token形式保存在客户端,只要没过期,客户端就可以一直拿着Token来进行用户认证与授权)
  • session保存在服务器端,相对较为安全
  • 结合cookie使用,较为灵活,兼容性较好

session的劣势

  • cookie+session在跨域场景表现并不好(cookie具有不可跨域性
  • 如果是分布式部署,需要做多机共享session机制
  • 基于cookie的机制很容易被CSRF(CSRF是跨站请求伪造,一种攻击,它可以用你的cookie进行攻击)
  • 查询session信息可能会有数据库查询操作(想要拿到完整的session信息还需要拿session_id去查询数据库,查询就需要时间和计算能力,这就会带来一定的性能问题。)

Session相关的概念介绍

  • session:主要存放在服务器端,相对安全
  • cookie:主要存放在客户端,并且不是很安全
  • sessionStorage:仅在当前会话下有效,关闭页面或浏览器后被清除
  • localstorage:除非被清除,否则永久保存

JWT vs. Session

可扩展性

JWT可以无缝接入水平拓展,因为基于Token(令牌)的身份验证是无状态的,所以不需要在session中存储用户信息,应用程序可以轻松拓展,可以使用Token从不同的服务器中访问资源,而不用担心用户是否真的登录在某台服务器上。

安全性

这两种都是会受到攻击的。

RESTful API

RESTful要求程序是无状态的,像session这种是有状态的认证方式,显然是不能做RESTful API的。

性能

客户端向服务端发出请求的时候,可能会有大量的用户信息在JWT中,每个Http请求都会产生大量的开销;如果用session的话只要少量的开销就可以了, 因为session_id非常小,JWT的大小可能是它的好几倍。

但是session_id也有缺点,查询完整信息需要session_id,这也是要消耗性能的;JWT字符串包含了完整信息,JWT就不需要数据库查询,性能消耗就少一点,JWT相当于用空间交换时间

时效性

JWT的时效性要比session差一点。因为JWT只有等到过期时间才可以销毁,无法实时更新,session可以在服务端主动手动销毁。

在Node.js中使用JWT

安装jsonwebtoken

npm i jsonwebtoken

签名

在终端进入 node 命令行,引入jwt

使用sign方法进行签名,它的第一个参数是JSON对象,第二个参数可以写密钥

> token = jwt.sign({name:"xiaofengche"},'secret');

'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb2ZlbmdjaGUiLCJpYXQiOjE2Mjg0MDYzMzF9.zOCf0dzBRvuBjOCcZZ5nuLbUGd4q05SQuFod48ScML4'

拿到token之后就可以传给客户端,客户端每次请求都可以拿着这个token放在头部传回给服务端,服务端拿到token之后就可以判断当前用户是谁了,有什么权限。

验证

使用解码 decode就可以判断用户是谁

> jwt.decode(token);
{ name: 'xiaofengche', iat: 1628406331 }
这里的iat是指签名时的时间,单位是秒

需要验证用户信息是否被篡改,verify第二个参数是要加密钥的

> jwt.verify(token,'secret');
{ name: 'xiaofengche', iat: 1628406331 }

证明token是合法的,签名也是合法的

应用场景实现用户注册

// /routes/users.js

// 用户登录 
router.post('/login', UsersControllers.loginUsers)  
// controllers/users.js
 
//用户登录
async loginUsers(ctx) {
    let user = {
        username: 'admin', 
        pwd: 'admin'
    }

    let token = jwt.sign({
        username: user.username
    },'jianshu-server-jwt',{
        expiresIn: 3600 * 24 * 7 
    }) 

    // sign 第一个参数是JSON对象,第二个参数可以写密钥,第三个参数是token保存时长
    ctx.body = {
        token: token
    } 
}

设计用户Schema

需要重新设计Schema,添加密码这个字段。

// //models/users.js

const mongoose = require('mongoose');
//mongoose提供的Schema类生成文档Schema
const { Schema,model } = mongoose

const userSchema = new Schema({
  //将没用的信息隐藏起来
  __v:{type:Number,select:false},
  
  //required表示这个属性是必选的
  //default可以设置默认值
  name:{type:String,required:true},
  
  //像密码这种敏感信息不应该随便暴露,需要将其隐藏起来——select:false
  password:{type:String,required:true,select:false},

});

//建立模型
//User:为文档集合名称
module.exports = model('User',userSchema);

在相关操作添加新字段的定义,更新models

//创建用户

  async create(ctx){
    //校验请求体的name位字符串类型并且是必选的
    ctx.verifyParams({
      //必选:required 删掉也是默认为true
      name:{ type:'string',required:true },
      password:{type:'string',required:true},
    });

    const user = await new User(ctx.request.body).save();
    ctx.body = user;
  }

  //更新用户
  async update(ctx){
  
    // 校验参数是否为空
    ctx.verifyParams({
      //必选:required 删掉也是默认为true
      name:{ type:'string',required:false },
      password:{type:'string',required:false},

    });

    const user = await User.findByIdAndUpdate(ctx.params.id,ctx.request.body);

    if(!user){ctx.throw(404,'用户不存在');}
    ctx.body = user;
  }

由于修改用户属性可以部分修改,所以需要修改更改路由的请求方法

//put是整体替换,现在的用户可以更新一部分属性
router.patch('/:id',update);

编写保证唯一性的逻辑(用户的唯一性)

在创建用户编写保证唯一性的逻辑,保证创建时用户名不重复

//更新用户
  async update(ctx){
    ctx.verifyParams({
      //必选:required 删掉也是默认为true
      name:{ type:'string',required:false },
      password:{type:'string',required:false},

    });

    //获取请求体中的用户名
    const {name} = ctx.request.body

    // findOne返回符合条件的第一个用户
    const repreatedUser = await User.findOne({name});

    //如果有重复用户返回409状态码代表冲突
    if(repreatedUser){
      ctx.throw(409,"用户名已占用");
    }

    const user = await User.findByIdAndUpdate(ctx.params.id,ctx.request.body);
    if(!user){ctx.throw(404,'用户不存在');}
    ctx.body = user;
  }

效果如下:

实现登录并获取token

登录接口设计

登录这个动作不属于用户增删改查的任何一种,可以模仿github采用POST+动词形式

jsonwebtoken生成token

首先在config.js配置密钥

secret:'jwt-secret',

在users.js引入jsonwebtoken和密钥,接着实现登录接口

const jsonwebtoken = require('jsonwebtoken');
const {secret} = require('../config');

//登录
  async login(ctx){
    ctx.verifyParams({
      name:{type:'string',required:true},
      password:{type:'string',required:true},
    });
    
    //登录有两种情况:
    // 1. 用户名不存在或密码错误,登录失败;
    // 2. 登录成功
    
    //查找符合条件的第一个用户
    const user = await User.findOne(ctx.request.body);
    
    if(!user){ctx.throw(401,'用户名或密码不正确');}
    
    //获取id和name
    const {_id,name} = user;
    //登录成功生成token,参数分别为用户不敏感的信息,签名密钥,过期时间
    //1d:一天
    
    const token = jsonwebtoken.sign({_id,name},secret,{expiresIn:'1d'});  // 重要
    ctx.body = {token};
  }

最后别忘了在routes->users.js注册接口

//delete是关键字,取别名
const {find,findById,create,update,delete:del,login} = require('../controllers/users');
router.post('/login',login)

效果演示:

Koa中间件实现用户认证与授权

总体思路

  • 登录签发token:在前端登录时先验证传递来的账户信息,如比对成功,就生成 token 令牌,返回给前端。(也可以像 session 那样直接ctx.cookies.set(key, value, [options])写入 cookie,比如 koa-session``)

  • 前端拿到 token 并进行保存(通常使用 localStorage, 也可以是 cookie),在之后每次请求时由请求头携带(一般是Authorization字段) 发送给服务端。

  • 访问验证token:对于需要登录权限才能访问的接口,先进行token认证(可以单独写一个验证中间件做一层拦截),确认token正确并还在有效期内,才能进行后续处理。客户端拿到错误知道需要(重新)登录

  • 用户退出登录时,清理存在客户端的token

自己编写Koa中间件实现用户认证与授权

  • 认证:验证token,并获取用户信息

routes->users.js编写认证中间件。

假设客户端是通过Authorization字段 加上Bearer 空格+token这种形式把token传进来的,我们就知道怎么获取token了

// `routes->users.js` 

const jsonwebtoken = require('jsonwebtoken');

const {secret} = require('../config');

const auth = async(ctx,next) => {
  //当不设置authorization的时候把它设置为空字符串
  const {authorization = ''} = ctx.request.header;
  
  //去掉'Bearer '才是我们真正想要的token
  const token = authorization.replace('Bearer ','');
  
  //验证用户信息
  try{
    const user = jsonwebtoken.verify(token,secret); // 验证token
    ctx.state.user = user;
    
  } catch(err) {
    //所有的验证失败手动抛成401错误,也就是未认证
    ctx.throw(401,err.message);
  }
  await next();
}

最后把中间件放在需要认证的接口上

router.patch('/:id', auth,update);

router.delete('/:id',auth,del);
  • 授权:使用中间件保护接口
    users.js控制器中编写鉴权中间件(也可以像上面一样在routes->users.js里面)
  async checkOwner(ctx,next){
    //判断当前修改或删除的用户id是不是当前登录用户的id
    if(ctx.params.id !== ctx.state._id){
      //操作的对象不是自己就抛出错误
      ctx.throw(403,'没有权限')
    }
    await next();
  }

最后把中间件添加到需要鉴权的接口上

const {find,findById,create,update,delete:del,login,checkOwner} = require('../controllers/users');

router.patch('/:id', auth,checkOwner,update);

router.delete('/:id',auth,checkOwner,del);

用koa-jwt中间件实现用户认证与授权

  • 安装koa-jwt:npm i koa-jwt --save

这是一个第三方中间件,功能强大。有了这个中间件,我们就不需要自己编写中间件了。

  • 使用中间件保护接口
    引入中间件,只需一行代码就可以替换掉自己编写的认证中间件。
// app.js 

// 引入jwt
const koajwt = require('koa-jwt');
const { secret } = require('./config/secret')

// 使用jwt 
app.use(koajwt({
  secret: secret
}).unless({ // 配置哪些接口不需要jwt认证
 path: [/^\/users\/login/,/^\/users\/register/]
}))

– 使用中间件获取用户信息

koa-jwt同样将用户信息存放在ctx.state.user上,自定义授权中间件依然能正常使用。

const jwt = require('jsonwebtoken');
const { secret } = require('../config/secret') /引入加密字符串

 // 验证用户登录 
async verify(ctx) {
    let token = ctx.header.authorization.replace('Bearer ', '') 
    try {
        // 校验 token ,为密钥信息
        let result = jwt.verify(token, secret)  
        await User.findOne({ _id: result._id }).then(res => {
            if (res) {
                // 给客户端返回token
                ctx.body = {
                    code: 200,
                    msg: '认证成功',
                    user: res
                }

            } else {
                ctx.body = {
                    code: 500,
                    msg: '认证失败'
                }
            }
        }).catch(err => {
            ctx.body = {
                code: 500,
                msg: err
            }
        })
    } catch (err) {
        ctx.body = {
            code: 500,
            msg: error
        }
    } 
}

显示结果

前端爱好者

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546551.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能Python-python3__2

Python3 中的位运算符 在 Python3 中&#xff0c;位运算符是用来执行二进制位操作的操作符。这些操作符允许我们对二进制位进行一些有用的操作&#xff0c;如位移、按位与、按位或、按位取反等。 在本文中&#xff0c;我们将介绍 Python3 中的位运算符 <<&#xff0c;也…

查看电脑的BIOS版本的五种方法

查看主板BIOS版本的五种方法 概述1. 在 BIOS 中查看2. 使用 DirectX 诊断工具3. 使用 CPU-Z 中查看4. 在 CMD 中查看&#xff08;一&#xff09;5. 在 CMD 中查看 &#xff08;二&#xff09;结束语 概述 BIOS是 Basic Input Output System 的缩略词&#xff0c;直译就是 **基…

Packet Tracer – VLAN 实施故障排除场景 2

Packet Tracer – VLAN 实施故障排除场景 2 拓扑图 地址分配表 设备 接口 IPv4 地址 子网掩码 默认网关 S1 VLAN 56 192.168.56.11 255.255.255.0 不适用 S2 VLAN 56 192.168.56.12 255.255.255.0 不适用 S3 VLAN 56 192.168.56.13 255.255.255.0 不适用 P…

chatgpt赋能Python-python3调用摄像头

Python3调用摄像头&#xff1a;介绍和实践 Python3 是一种高级编程语言&#xff0c;具有易学易用、支持多种编程方式和跨平台的优点&#xff0c;成为了科学计算、数据分析和人工智能等领域的常用语言。本篇文章将介绍如何使用Python3调用摄像头&#xff0c;并提供实践代码供读…

计算机网络 - 应用层

Application Layer Network Application 的架构 client-server 最常见的架构, 比如上淘宝, google drive都是客户端-服务器的架构 P2P 很多下载软件用的是这个架构, 比如BitTorrent Web and HTTP Overview 是一个超文本传输的协议, 客户端请求, 服务器响应, 发送对象的We…

【野火启明_瑞萨RA6M5】按键输入检测

文章目录 一、GPIO输入——按键输入检测二、硬件设计三、软件设计下载验证 一、GPIO输入——按键输入检测 按键检测原理 按键机械触点断开、闭合时&#xff0c;由于触点的弹性作用&#xff0c;按键开关不会马上稳定接通或一下子断开&#xff0c;使用按键时会产生 下图中的带波…

城市内涝的原因是什么?城市内涝监测预警系统有什么作用?

城市内涝是指在城市地区发生的、由于降雨或其他水源无法迅速排出而引起的洪水现象&#xff0c;城市内涝是城市发展过程中面临的重要挑战之一。本文着重为大家介绍城市内涝的原因&#xff0c;以及城市内涝监测预警系统的作用。 一、城市内涝的原因是什么? 1、排水系统不完善&am…

【基础6】存储过程的 创建与调用

目录 什么是存储过程 用户自定义存储过程 练习 什么是存储过程 什么是存储过程 类似于C语言中的函数。用来执行管理任务或应用复杂的业务规则存储过程可以带参数&#xff0c;也可以返回结果存储过程可以包含数据操纵等语句、变量、逻辑控制语句等。&#xff08;单个select语…

QT窗体绘图QPainter

QPainter INSCODE AI 创作助手&#xff1a; QPainter是Qt中的一个类&#xff0c;用于在窗口、图像或其他用户界面上绘制图形和文本。它提供了一些方便的方法来画线、矩形、圆、多边形和文本 QPainter绘图函数 INSCODE AI 创作助手&#xff1a; QPainter是Qt中一个用于绘图的类&…

MATLAB开发中的常见问题和解决方法:如何解决常见的性能和bug问题

章节一&#xff1a;引言 在MATLAB开发中&#xff0c;经常会遇到一些常见的性能和bug问题。这些问题可能导致程序运行缓慢、结果不准确或者甚至崩溃。本文将介绍一些常见问题&#xff0c;并提供解决方法和案例&#xff0c;帮助开发者更好地应对这些挑战。 章节二&#xff1a;性…

chatgpt赋能Python-python3多行输入

Python3多行输入教程&#xff1a;如何让你的输入更加高效 Python3是一种功能强大、易于学习的编程语言&#xff0c;被广泛应用于科学计算和数据分析领域。但是&#xff0c;对于初学者来说&#xff0c;输入数据可能是一个有些棘手的问题。在本文中&#xff0c;我们将介绍如何使…

chatgpt赋能Python-python3拷贝文件

Python3 拷贝文件方法及注意事项 拷贝文件在日常开发中是一项基本的操作。Python3作为一门既简洁又强大的编程语言&#xff0c;也提供了丰富的文件操作函数。在本篇文章中&#xff0c;我们将介绍如何使用Python3拷贝文件以及需要注意的细节。 为什么要拷贝文件&#xff1f; …

RHCE--openlab搭建网站

--作业要求-- 综合练习&#xff1a;请给openlab搭建web网站 ​ 网站需求&#xff1a; ​ 1.基于域名[www.openlab.com](http://www.openlab.com)可以访问网站内容为 welcome to openlab!!! ​ 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#x…

积水监测用什么传感器?道路积水监测设备

近年来&#xff0c;由于全球气候变化导致极端天气频发&#xff0c;在暴雨的侵袭下&#xff0c;许多城市都面临路面积水严重的问题&#xff0c;道路积水是道路交通安全和行车舒适度的一大威胁&#xff0c;给司机和行人带来了巨大的安全隐患。 如何解决道路积水问题? 1、强城市排…

ChatGPT又一重磅更新 - iOS官方App发布

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

余压传感器(探测器)最重要的两个功能是什么?

余压传感器&#xff08;探测器&#xff09;是什么&#xff1f; 余压传感器&#xff08;探测器&#xff09;主要用于测量两个位置之间的压差。 余压传感器&#xff08;探测器&#xff09;的作用&#xff1f; 流量测量&#xff1a;在管道中&#xff0c;通过测量两点之间的压差&…

头歌计算机组成原理实验—运算器设计(11)第11关:MIPS运算器设计

第11关&#xff1a;MIPS运算器设计 实验目的 学生理解算术逻辑运算单元&#xff08;ALU&#xff09;的基本构成&#xff0c;掌握 Logisim 中各种运算组件的使用方法&#xff0c;熟悉多路选择器的使用&#xff0c;能利用前述实验完成的32位加法器、 Logisim 中的运算组件构造指…

基于Freertos的ESP-IDF开发——5.使用按键[不带消抖、带消抖、长按短按识别]

基于Freertos的ESP-IDF开发——5.使用按键[不带消抖、带消抖、长按短按识别] 0. 前言1. 确定GPIO引脚2. 触发函数(不带消抖)3. 触发函数(带消抖)4. 长按和短按识别5.其他FreeRtos文章 0. 前言 这一节我们来学习使用按键操作。包括带消抖和不带消抖。 由于之前已经学习过FreeR…

chatgpt赋能Python-python3_run

Python3 Run&#xff1a;学习Python必须掌握的运行方式 如果你在学习Python编程&#xff0c;那么Python3 Run是你必须掌握的运行方式之一。Python3 Run可以让你在本地环境中运行Python代码并查看运行结果。在本文中&#xff0c;我们将介绍Python3 Run的基本概念、使用方法和注…

概论_第4章__协方差Cov(X)的定义和性质___相关系数的定义和性质

前面讨论的方差是 一维随机变量X, 对于二维随机变量&#xff0c; 怎样计算方差呢&#xff1f; 这就引出了 协方差&#xff1a; 讨论X与Y之间相互关系的数字特征。 一 协方差的定义 协方差通俗的理解&#xff1a; 两个随机变量X, Y协作产生的方差。 计算协方差的公式有…