数据交互系列:简述token和如何使用token

文章详细介绍了JWT(JSONWebToken)的工作原理,包括它是如何解决HTTP短连接和无状态管理的问题,以及对比传统的session认证的优势。JWT由头部、载荷和签名三部分组成,用于在网络应用间传递声明。文中还展示了如何在Node.js环境中生成和验证JWT的示例代码,以及客户端如何存储和使用token。同时,讨论了token存储的安全性问题,如CSRF和XSS攻击的风险。

一、什么是token(理论)

  • 解决http短连接,无状态管理的问题。

  • Jeb web token(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开发标准,JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些科瓦ide其他业务逻辑所必须的声明信息。

👍传统的session认证 请添加图片描述

基于session认证所显露的问题

Session:每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言,seesion都是保存在内存中,而随着认证用户的增多,服务器的开销会明显增大。

扩展性:用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求这台服务器上,这个才能拿到授权,这样在分布式的应用上,相应的限制了负载均衡的能力,这也意味着限制了应用的扩展能力。

CSRF:因为基于cookie来进行用户识别的,cookie如果截获,用户就会很容易受到跨站请求伪造的攻击。

👍基于token认证的鉴权机制

基于token的鉴权机制不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

token鉴权流程如下

请添加图片描述

优点:

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
  • JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息
  • 便于传输,jwt的构成非常简单,就是一个字符串,字节占用很小,所以它是。
  • 非常便于传输的,它不需要在服务器保存会话信息,所以易于应用的扩展。

👍JWT的结构

请添加图片描述

JWT 是由三段信息构成的,将三段信息文本用.链接一起就构成了JWT字符串,就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

  • 第一部分我们称它为头部(header)
  • 第二部分我们称其为载荷(payload)
  • 第三部分是签证(signature)

header

jwt的头部承载着两部分的信息:

  • 声明类型
  • 声明加密的算法(通常直接使用HMAC SHA256

完整的头部就像下面这样的JSON


{
   'typ':'JWT',
   'alg':'HS256'
}

将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 base64加密后变成一个完整的字符串。

payload

载荷就是存放有效信息的地方,这个名字是指飞机上承载的货品,有效信息包含三个部分

  • 标准中注册的声明

    • iss:jwt签发者
    • sub:jwt所面向的用户
    • aud:接收jwt的一方
    • exp:jwt的过期时间
    • nbf:定义在什么时间之前,这过期时间必须大于签发时间
    • iat:jwt的签发时间
    • jti:jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
  • 公共的声明

    • 公共的声明可以添加任何信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分客户端可解密。
  • 私有的声明

  • 例如,定义一个payload json:

       {
       	"sub":"1234567890",
       	"name""john Doe",
       	admin:true
       }
    

然后将其进行base64加密,得到jwt的第二部分

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9

signature

jwt 的第三部分是一个签证信息,这个签证信息由三部分组成

  • header(base64后的)
  • payload(base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用,使用.连接组成的字符串,然后通过header声明的加密方式加密secret组合加密,然后就构成了jwt的第三部分。


var encodedString=base64UrlEncode(header)+'.'+base64UrlEncode(payload)
var  signature = HMACHA256(encodedString,'secret')

将这三部分用 . 连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIi wiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

👍客户端获取到token之后

  • Token

    token是服务端生成的一串字符串,以做客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个token前来请求数据即可,无需再次带上用户名和密码。token可以设置在cookie或者headers中,都可以。

  • 公参

    公共参数,一般放在headers中,让所有的请求都带上这个参数,服务器会对他做一些处理,比如常用的比如:会在headers中设置app的版本,用于服务器进行接口的版本兼容。

  • 客户端对于token的存储方式

    • 存储在webStorage中,每次调用接口都当成一个字段传给后台

    • 存储在cookie中,让他自动发送,缺点就是不能跨域

      • 将token存放在cookie中可以指定 httponly,来防止被Javascript读取,也可以指定secure,来保证token只在HTTPS下传输。缺点是不符合Restful最佳实践,容易受到CSRF攻击。
      • CSRF就是恶意攻击者盗用已经认证过的用户信息,以用户信息名义进行一些操作〈如发邮件、转账、购买商品等等)。由于身份已经认证过,所以目标网站会认为操作都是真正的用户操作的。CSRF并不能拿到用户信息,它只是盗用用户凭证去进行操作。
    • 拿到以后存储在webStorage中,每次调用接口的时候放在HTTP请求头的Authorization字段里

      • 将token存放在webStorage中,可以通过js来访问,这样会导致很容易受到xss攻击,如果js脚本被盗用,攻击者就可以轻易访问你的网站, webStroage作为一种储存机制,在传输过程中不会执行任何安全标准
      • xss攻击是一种注入代码攻击,恶意攻击者在目标网站上注入script代码,当访问者浏览王网站的时候通过执行注入的script代码达到窃取用户信息,盗用用户身份等。

二、如何使用token?(案例)

以下使用两个小demo来简述后端生成token到客户端获取token到携带token去发送请求获取数据的整体流程。

后台服务环境基于node,需要安装的第三方npm包如下

{
  "name": "demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5", 
    "express": "^4.18.2", //web服务框架
    "jsonwebtoken": "^9.0.1", //生成和验证token
    "mysql": "^2.18.1", //数据库
    "svg-captcha": "^1.4.0" //生成验证码
  }
}

👍demo1:模拟前后端生成,获取和验证token

成功获取到token,并且检验成功。
请添加图片描述

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
</head>
<body>
    <button id="btn">点我发送请求,认证</button>
    <script>
        btn.addEventListener('click',()=>{
            let config={
                headers:{
                    'Authorizition':localStorage.getItem('token')//从缓存中取
                }
            }
            axios.get('http://localhost:3000/verifytoken',config).then(res=>{
                console.log(res)
            })
        })

        // dom元素加载完就发送请求
        document.addEventListener('DOMContentLoaded',function(res){
            axios.get('http://localhost:3000/gettoken').then((res)=>{
                console.log(res)
                //从res中解析token,并且存在localStorage 供后续请求使用
                let token=res.data.token
                console.log(token)
                localStorage.setItem('token',token) //放到缓存
            })
        })
    </script>
</body>   
</html>

服务端代码(出现的问题和解决的方法)

出现问题
1.跨域问题,
2.客户端请求头设置参数,后端没有处理的问题

请添加图片描述
请添加图片描述
服务端设置响应控制头,cors方法防止跨域 (什么是cors方法?⏩点击此处查看交互专栏里的上一篇文章


// 设置响应头,cors方法,防止跨域
app.use((req,res,next)=>{
    // 允许任何来源跨域
    res.header('Access-Control-Allow-Origin','*')
    // 如果请求中设置了请求头,那么这个首部是必要的
    res.header('Access-Control-Allow-Headers','*')
    next()
})
app.use(express.static('public'))

结果:成功获取到token

请添加图片描述

整体代码展示

// 模拟 加载页面拿到token,再向后台发请求时验证token

// 生成和验证token
const jwt=require('jsonwebtoken')
// 生成验证码
const captcha=require('svg-captcha')
// 启动web服务的框架
const express=require('express')

// 定义密钥
const key='my key'

// 启动服务
const app=express()

// 设置响应头,cors方法,防止跨域
app.use((req,res,next)=>{
    // 允许任何来源跨域
    res.header('Access-Control-Allow-Origin','*')
    // 如果请求中设置了请求头,那么这个首部是必要的
    res.header('Access-Control-Allow-Headers','*')
    next()
})
app.use(express.static('public'))


// 基于jsonwebtoken模块,生成token,将token返回给客户端
app.get('/gettoken',(req,res)=>{
    // 声明携带的载荷
    let payload={
        name:'dema',
        userId:1003,
        exp:Date.now()/1000+3600*24  // 设置过期时间
    }
    
    // sign方法用于办法token
    let token=jwt.sign(payload,key)
    res.send({
        code:0,
        result:'ok',
        token
    })

})    

// 验证token
app.get('/verifytoken',(req,res)=>{
    // 获取Authorization消息头
    // console.log(req)
    let token=req.headers.authorization
    // 验证token,获取token中存储的payload数据
    jwt.verify(token,key,(err,decoded)=>{
        console.log(err) //若验证失败,则err将不是null
        console.log(decoded)
        res.send('ok')
    })
})

app.listen(3000,()=>{
    console.log('server is running....')
})

👍demo2:获取token,携带token去验证输入的验证码是否正确

实现的步骤

    1. 加载页面,客户端获取token,并存入本地缓存
    1. 加载页面,获取验证码图片并渲染到页面
    1. 客户端再次发送请求,验证码+携带token,如果得到的响应是验证码输入成功,即为验证码验证成功

实现的效果

在这里插入图片描述

前端代码完整展示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
</head>  
    <input type="text" id="text">
    <div id="img">
     <!-- svg -->
    </div>
    <button id="btn">点我认证</button>
    <script>
        // 验证验证码
        btn.addEventListener('click',()=>{
            let config={
                headers:{
                    'Authorizition':localStorage.getItem('token')//从缓存中取
                }
            }
            
            axios.get('http://localhost:3000/resgister?ucode='+text.value,config).then(res=>{
                console.log(res)
            })
        })

        

        // dom元素加载完就发送请求
        document.addEventListener('DOMContentLoaded',function(res){
            axios.get('http://localhost:3000/gettoken').then((res)=>{
                let token=res.data.token
                localStorage.setItem('token',token) 
                console.log('token',token) 
            })
        })

        // 页面加载完获取验证码
        window.onload=function(){
            axios.get('http://localhost:3000/getcode').then((res)=>{
                 let svg=res.data.svg 
                 console.log('svg',svg)
                 let img=document.getElementById('img')
                 img.innerHTML=svg
            })
        }
    </script>
</body>   
</html>

服务端代码完整展示

// 生成和验证token
const jwt=require('jsonwebtoken')
// 生成验证码
const captcha=require('svg-captcha')
// 启动web服务的框架
const express=require('express')


// 定义密钥
const key='my key'

// 启动服务
const app=express()

// 设置响应头,cors方法,防止跨域
app.use((req,res,next)=>{
    // 允许任何来源跨域
    res.header('Access-Control-Allow-Origin','*')
    // 如果请求中设置了请求头,那么这个首部是必要的
    res.header('Access-Control-Allow-Headers','*')
    next()
})
app.use(express.static('public'))


//模拟redis数据库
let redis={} //模拟redis数据库


// 基于jsonwebtoken模块,生成token,将token返回给客户端

let token

app.get('/gettoken',(req,res)=>{
    // 声明携带的载荷
    let payload={
        name:'dema',
        userId:1003,
        exp:Date.now()/1000+3600*24  // 设置过期时间
    }
    
    // sign方法用于办法token
    token=jwt.sign(payload,key)
    res.send({
        code:0,
        result:'ok',
        token
    })

})    

// 接收请求,返回验证码图片
app.get('/getcode',(req,res)=>{
    // 生成验证码
    let cap=captcha.create() //生成一个验证码图片
    console.log('生成的token',token)
    // token验证成功
    // 缺一步,把正确答案和token做配对存入redis里
    redis[token]=cap.text
    console.log('当前redis',redis)
    res.send({
        svg:cap.data
    })
})

// 验证token,验证验证码是否一致
app.get('/resgister',(req,res)=>{
    // 获取用户输入的验证码
    let ucode=req.query.ucode
    // 从请求头获取token,验证成功,执行后续业务
    let token=req.headers.authorizition
    // 验证token,获取token中存储的payload数据
    jwt.verify(token,key,(err,decoded)=>{
        console.warn(err)
        if(err!=null){// 验证失败
            res.send('刷新重试')
            return
        }
    })
    //token验证成功
    let answer=redis[token]
    if(ucode.toLocaleLowerCase()==answer.toLocaleLowerCase()){
        res.send('验证码输入正确')
    }else{
        res.send('验证码输入错误')
    }
})
 


app.listen(3001,()=>{
    console.log('server is running....')
})

4.1 业务需求 基于Vue框架的高级语言基础知识线上自评系统面向教育领域,旨在为管理员、教师学生构建一个综合性平台。 管理员需全面管理学校与专业相关事宜,像对院校信息进行增加、删除、修改查询,以及管理专业设置;同时要监管系统内容,还得负责用户账号管理,发布通知公告,并通过系统工具生成相关代码,以保障系统稳定运行功能完善。 教师利用系统进行课程管理,包括上传课程视频与课件、规划章节及作业、查看学生学习情况、审核学生报名、处理课程评论等;开展考试管理,如录入题目、设置考试;管理学生笔记,并在个人中心教师首页进行相关操作。 学生借助系统完成登录注册,在线学习高级语言课程,下载课件,记录笔记,参加在线考试,完成课程作业,整理错题集,进行留言交流,在个人中心管理个人信息,实现自我学习评估与提升。 图 4-1 顶层数据流图 数据字典用于描述高级语言基础知识线上自评系统中的数据流图。根据数据流图对高级语言基础知识线上自评系统的部分数据流详细说明如下。 (1)数据流编号:DF-01 数据流名称:用户在线学习数据 简述:用户在系统中进行在线学习过程中产生的各类行为数据 数据流来源:用户 数据流去向:系统数据库 数据项组成:用户信息+学习时间+学习进度 (2)数据流编号:DF-02 数据流名称:学习资料传输流 简述:用户请求获取学习资料时,系统向用户传输相应资料的过程 数据流来源:系统数据库 数据流去向:用户 数据项组成:资料名称+资料类型+资料内容 (3)数据流编号:DF-03 数据流名称:作业完成提交流 简述:用户完成作业后,将作业内容提交到系统服务器的数据流 数据流来源:用户 数据流去向:系统数据库 数据项组成:用户信息+作业内容+提交时间 (4)数据流编号:DF-04 数据流名称:考试参与数据流 简述:包含用户进入考试、答题等考试过程中的相关数据传输 数据流来源:用户 数据流去向:系统数据库 数据项组成:试卷编号+答题记录 (5)数据流编号:DF-05 数据流名称:考试成绩反馈流 简述:系统根据用户考试结果,将成绩反馈给用户的数据流 数据流来源:系统数据库 数据流去向:用户 数据项组成:考试成绩+试题内容 (6)数据流编号:DF-06 数据流名称:笔记记录传输流 简述:用户记录的笔记内容在本地与系统服务器之间进行传输 数据流来源:用户 数据流去向:系统数据库 数据项组成:用户信息+笔记内容 (7)数据流编号:DF-07 数据流名称:学习情况数据流 简述:系统定期或按需将用户的学习情况汇总后发送给教师 数据流来源:系统数据库 数据流去向:教师 数据项组成:用户编号+课程编号+学习时长汇总+作业完成情况 (8)数据流编号:DF-08 数据流名称:课程评论提交流 简述:用户对课程进行评价后,将评论内容提交到系统的数据流 数据流来源:用户 数据流去向:教师 数据项组成:用户编号+课程编号+评论内容 (9)数据流编号:DF-09 数据流名称:课程管理数据流 简述:教师对课程进行创建、编辑、删除等操作时的数据传输 数据流来源:教师 数据流去向:系统数据库 数据项组成:课程编号+课程名称+课程描述+教师信息 (10)数据流编号:DF-10 数据流名称:考试管理数据流 简述:涵盖教师创建考试、设置考试规则、发布考试等管理操作的数据传递 数据流来源:教师 数据流去向:系统数据库 数据项组成:考试编号+考试名称+考试时间+教师编号 (11)数据流编号:DF-11 数据流名称:笔记管理数据流 简述:教师查看、批注用户笔记等管理操作的数据传输 数据流来源:教师 数据流去向:系统数据库 数据项组成:笔记编号+用户编号+课程编号+教师编号+编辑人 (12)数据流编号:DF-12 数据流名称:系统内容监管流 简述:管理员对系统内课程内容、用户评论等进行监督管理的数据交互 数据流来源:管理员 数据流去向:系统数据库 数据项组成:系统内容+编辑人 (13)数据流编号:DF-13 数据流名称:专业信息管理流 简述:管理员对专业名称管理的数据流 数据流来源:管理员 数据流去向:系统数据库 数据项组成:专业编号+专业名称+编辑人 (14)数据流编号:DF-14 数据流名称:学校信息管理流 简述:管理员对学校名称管理的数据流 数据流来源:管理员 数据流去向:系统数据库 数据项组成:学校信息+信息内容+编辑人 (15)数据流编号:DF-15 数据流名称:用户信息管理流 简述:管理员对用户账号信息、个人资料等进行管理的数据流 数据流来源:管理员 数据流去向:系统数据库 数据项组成:用户编号+用户姓名+账号信息+编辑人 根据这些,详细设计考试这条数据,这个核心功能的一层数据流图
03-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值