Cookie、Session、Token、JWT

文章目录

      • 1. Cookie
        • 1.1 为什么需要Cookie
        • 1.2 Cookie是什么
        • 1.3 Cookie机制
      • 2. Session
        • 2.1 为什么需要Session
        • 2.2 Session机制
      • 3. Token
        • 3.1 Token简介
        • 3.2 Token和Session的区别
      • 4. JWT
        • 4.1 JWT简介
        • 4.2 JWT的数据结构与使用
      • 5. Gin框架中JWT的使用
        • 5.1 安装
        • 5.2 使用

1. Cookie

1.1 为什么需要Cookie

因为HTTP协议是无状态的,也就是说每次请求都是独立的,它的执行情况和结果与前面的请求和之后的请求都无直接关系,它不会受前面的请求响应情况直接影响,也不会直接影响后面的请求响应情况。
从应用层面上来说,由于HTTP协议的无状态特点,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 Cookie来实现。

1.2 Cookie是什么

Cookie 是服务器发送到用户浏览器并保存在浏览器本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。一般网络用户习惯用其复数形式 Cookies,指某些网站为了辨别用户身份、进行 Session 跟踪而存储在用户本地终端上的数据,而这些数据通常会经过加密处理。
注意:Cookie保存在客户端,Cookie 是不可跨域的。

1.3 Cookie机制

Cookie是由服务器端生成,发送给客户端(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器。Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等,服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。

2. Session

2.1 为什么需要Session
  1. 由于Cookie本身最大支持4096字节,而Session 可存储数据远高于 Cookie。
  2. Cookie本身保存在客户端,可能被拦截或窃取,而Session 是存储在服务器端的,所以Session 比 Cookie 安全。
2.2 Session机制

Session 是另一种记录服务器和客户端会话状态的机制。Session 是基于Cookie 实现的,Session 存储在服务器端,SessionId 会被存储到客户端的Cookie 中。

image.png
Cookie-Session模式实现用户认证流程:

  • 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session和与之对应的SessionID。
  • 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器。
  • 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名。
  • 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。
举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?
一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。
另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。JWT 就是这种方案的一个代表。

3. Token

3.1 Token简介

token是访问资源接口(API)时所需要的资源凭证。

image.png
token 的身份验证流程:

  • 客户端使用用户名跟密码请求登录
  • 服务端收到请求,去验证用户名与密码
  • 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
  • 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
  • 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
  • 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
3.2 Token和Session的区别
  • Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
  • Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。

4. JWT

4.1 JWT简介

JWT(全称JSON Web Token)是目前最流行的跨域认证解决方案。JWT是一种基于Token的轻量级认证模式,服务端认证通过后,会生成一个JSON对象,经过签名后得到一个Token(令牌)再发回给用户,用户后续请求只需要带上这个Token,服务端解密之后就能获取该用户的相关信息了。
image.png

4.2 JWT的数据结构与使用

JWT是一个很长的字符串,中间用点(.)分隔成三个部分。
三部分分别是:

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

image.png
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面。
另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

5. Gin框架中JWT的使用

5.1 安装
go get github.com/golang-jwt/jwt/v4

5.2 使用
  • 自定义Claims

我们需要定制自己的需求来决定JWT中保存哪些数据,比如我们规定在JWT中要存储username信息,那么我们就定义一个MyClaims结构体如下:

// CustomClaims 自定义声明类型 并内嵌jwt.RegisteredClaims
// jwt包自带的jwt.RegisteredClaims只包含了官方字段
// 假设我们这里需要额外记录一个username字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type CustomClaims struct {// 可根据需要自行添加字段Username             string `json:"username"`jwt.RegisteredClaims        // 内嵌标准的声明
}

然后我们定义JWT的过期时间,这里以24小时为例:

const TokenExpireDuration = time.Hour * 24

接下来还需要定义一个用于签名的字符串:

// CustomSecret 用于加盐的字符串
var CustomSecret = []byte("夏天夏天悄悄过去")
  • 生成JWT
// GenToken 生成JWT
func GenToken(username string) (string, error) {// 创建一个我们自己的声明claims := CustomClaims{username, // 自定义字段jwt.RegisteredClaims{ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireDuration)),Issuer:    "my-project", // 签发人},}// 使用指定的签名方法创建签名对象token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)// 使用指定的secret签名并获得完整的编码后的字符串tokenreturn token.SignedString(CustomSecret)
}
  • 解析JWT
// ParseToken 解析JWT
func ParseToken(tokenString string) (*CustomClaims, error) {// 解析token// 如果是自定义Claim结构体则需要使用 ParseWithClaims 方法token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, err error) {// 直接使用标准的Claim则可以直接使用Parse方法//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {return CustomSecret, nil})if err != nil {return nil, err}// 对token对象中的Claim进行类型断言if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { // 校验tokenreturn claims, nil}return nil, errors.New("invalid token")
}

在Gin框架中使用JWT,首先我们注册一条路由/auth,对外提供获取Token的渠道:

r.POST("/auth", authHandler)

我们的authHandler定义如下:

func authHandler(c *gin.Context) {// 用户发送用户名和密码过来var user UserInfoerr := c.ShouldBind(&user)if err != nil {c.JSON(http.StatusOK, gin.H{"code": 2001,"msg":  "无效的参数",})return}// 校验用户名和密码是否正确if user.Username == "q1mi" && user.Password == "q1mi123" {// 生成TokentokenString, _ := GenToken(user.Username)c.JSON(http.StatusOK, gin.H{"code": 2000,"msg":  "success","data": gin.H{"token": tokenString},})return}c.JSON(http.StatusOK, gin.H{"code": 2002,"msg":  "鉴权失败",})return
}

用户通过上面的接口获取Token之后,后续就会携带着Token再来请求我们的其他接口,这个时候就需要对这些请求的Token进行校验操作了,很显然我们应该实现一个检验Token的中间件,具体实现如下:

// JWTAuthMiddleware 基于JWT的认证中间件
func JWTAuthMiddleware() func(c *gin.Context) {return func(c *gin.Context) {// 客户端携带Token有三种方式 1.放在请求头 2.放在请求体 3.放在URI// 这里假设Token放在Header的Authorization中,并使用Bearer开头// 这里的具体实现方式要依据你的实际业务情况决定authHeader := c.Request.Header.Get("Authorization")if authHeader == "" {c.JSON(http.StatusOK, gin.H{"code": 2003,"msg":  "请求头中auth为空",})c.Abort()return}// 按空格分割parts := strings.SplitN(authHeader, " ", 2)if !(len(parts) == 2 && parts[0] == "Bearer") {c.JSON(http.StatusOK, gin.H{"code": 2004,"msg":  "请求头中auth格式有误",})c.Abort()return}// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它mc, err := ParseToken(parts[1])if err != nil {c.JSON(http.StatusOK, gin.H{"code": 2005,"msg":  "无效的Token",})c.Abort()return}// 将当前请求的username信息保存到请求的上下文c上c.Set("username", mc.Username)c.Next() // 后续的处理函数可以用过c.Get("username")来获取当前请求的用户信息}
}
r.GET("/home", JWTAuthMiddleware(), homeHandler)func homeHandler(c *gin.Context) {username := c.MustGet("username").(string)c.JSON(http.StatusOK, gin.H{"code": 2000,"msg":  "success","data": gin.H{"username": username},})
}

首先发送post请求获取到token
image.png
然后携带token访问服务端
image.png
上述jwt demo的使用的整体代码放在了这个仓库:https://github.com/acezsq/jwt_demo

参考资料:
【秋天不落叶】傻傻分不清之 Cookie、Session、Token、JWT
【李文周】Cookie和Session
【李文周】在gin框架中使用JWT
【阮一峰】JSON Web Token 入门教程

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【滑动窗口】力扣239.滑动窗口最大值

前面的文章我们练习数十道 动态规划 的题目。相信小伙伴们对于动态规划的题目已经写的 得心应手 了。 还没看过的小伙伴赶快关注一下,学习如何 秒杀动态规划 吧! 接下来我们开启一个新的篇章 —— 「滑动窗口」。 滑动窗口 滑动窗口 是一种基于 双指…

Matlab|配电网智能软开关(sop)规划模型

目录 1 主要内容 目标函数 2 部分程序 3 程序结果 3.1 sop选址定容优化模型 3.2 对比算例(不含sop) 4 下载链接 1 主要内容 该程序参考文献《基于改进灵敏度分析的有源配电网智能软开关优化配置》,采用二阶锥算法,以改进的…

动态调整html表格每列宽度

为什么想自动计算列宽呢?因为我有一次拿到一个项目,它里面的列宽都是写死的。后来需要改,一个个的改太麻烦了。 诸如这样的表格在网站上非常常见。我们不对列做设置的话,列宽就会取每列文本内容的最大长度。在只有一条文本非常长…

ubuntu安装开源汇编调试器NASM

安装 安装很简单,直接在终端输入以下命令即可 sudo apt-get install nasm 安装完成后,如果可以查看到nasm的版本号即可视为安装成功 nasm -version 测试 创建汇编文件 创建一个asm文件 vim hello.asm 文件内容如下 section .datahello: db …

Linux随记(八)

一、crontab运行shell脚本,py脚本 (注意事项) 情景描述: 目前有个sh脚本他最初大致内容是。 cat t11.sh#!/bin/bash source /etc/profile /bin/python3 /tmp/1.py sh /tmp/1.sh echo -e "$(date %F)" >…

LeetCode148题:排序链表(python3)

在数组排序中,常见的排序算法有:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序、桶排序、基数排序等。 而对于链表排序而言,因为链表不支持随机访问,访问链表后面的节点只能依靠 next 指针从头…

2024-03-05 linux 分区老显示满,Use 100%,原因是SquashFS 是一种只读文件系统,它在创建时就已经被填满,所有空间都被使用。

一、这两天一直纠结一个问题,无论怎么修改,linux 分区老显示满,Use 100%,全部沾满。如下图的oem分区。 二、导致出现上面的原因是:SquashFS文件系统里的空间利用率总是显示为100%。 三、SDK里面也说明SquashFS文件系统…

昏暗场景增强-低照度增强-弱光增强(附代码)

引言 随着现代科技的发展,图像采集设备已经渗透到生活的方方面面,然而在昏暗场景、低照度或弱光条件下,图像的质量往往受到严重影响,表现为亮度不足、对比度低下、色彩失真以及细节丢失等问题。这类图像对于人眼识别和计算机视觉…

基于机器学习的网络入侵检测与特征选择及随机森林分类器性能评估(NSL-KDD数据集)

简介 本文将详细介绍如何利用Python和相关机器学习库对NSL-KDD数据集进行预处理,特征选择,并通过随机森林算法构建网络入侵检测模型。同时,还将展示如何计算并可视化模型的ROC曲线以评估其性能。 首先,我们导入了必要的库&#…

Java服务器-Disruptor使用注意

最近看了一下部署后台的服务器状况,发现我的一个Java程序其占用的CPU时长超过100%,排查后发现竟是Disruptor引起的,让我们来看看究竟为什么Disruptor会有这样的表现。 发现占用CPU时间超过100%的进程 首先是在服务器上用top命令查看服务器状…

300分钟吃透分布式缓存-23讲:Redis是如何淘汰key的?

淘汰原理 首先我们来学习 Redis 的淘汰原理。 系统线上运行中,内存总是昂贵且有限的,在数据总量远大于 Redis 可用的内存总量时,为了最大限度的提升访问性能,Redis 中只能存放最新最热的有效数据。 当 key 过期后,或…

专题一 - 双指针 - leetcode 202. 快乐数 | 简单难度

leetcode 202. 快乐数 leetcode 202. 快乐数 | 简单难度1. 题目详情1. 原题链接2. 基础框架 2. 解题思路1. 题目分析2. 算法原理3. 时间复杂度 3. 代码实现4. 知识与收获 leetcode 202. 快乐数 | 简单难度 1. 题目详情 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」…