用户认证模式Cookie-Session、JWT-Token(goland实现)

news2024/11/25 0:52:56

用户认证

    • Cookie-Session认证模式
      • 简介
      • 代码示例
      • 优缺点
    • Token认证模式
      • 简介
      • JWT介绍
      • JWT结构
        • 标头(Header)
        • 负载(Payload)
        • 签名(Signature)
      • 代码示例
      • JWT优缺点
      • Access Token和Refresh Token认证模式
      • 代码示例

在计算机网络中,我们知道HTTP是一个无状态的协议,一次请求结束后,下次再发送服务器就不知道这个请求是谁发来的了(同一个IP不代表同一个用户),在Web应用中,用户的认证和鉴权是非常重要的一环,实践中有多种可用模式,并且各有千秋。

Cookie-Session认证模式

简介

在Web应用发展的初期,大部分采用基于Cookie-Session的会话管理方式

  • 客户端使用用户名、密码进行认证
  • 服务端验证用户名、密码正确后生成并存储Session,将SessionID通过Cookie返回给客户端
  • 客户端访问需要认证的接口时在Cookie中携带SessionID
  • 服务端通过SessionID查找Session并进行鉴权,返回给客户端需要的数据
    在这里插入图片描述

代码示例

package main

import (
	"context"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"log"
	"net/http"
	"time"
)

var Rdb *redis.Client //redis全局变量

type UserLogin struct { //登录入参
	UserName string `json:"user_name"`
	Password string `json:"password"`
}

func redisStart() {
	rdb := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:6379",
		Password: "123456",
		DB:       0,
		PoolSize: 100,
	})
	_, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	ctx := context.Background()
	pong, err := rdb.Ping(ctx).Result()
	fmt.Println(pong)
	if err != nil {
		log.Println(err)
	}
	Rdb = rdb
}

func main() {
	redisStart()
	router := gin.Default()
	// 设置登录请求的路由处理函数
	router.POST("/login", loginHandler)

	// 设置受保护页面的路由处理函数
	router.GET("/protected", protectedHandler)

	router.Run(":8080")
}

func loginHandler(c *gin.Context) {

	var user UserLogin
	if err := c.ShouldBindJSON(&user); err != nil {
		log.Println(err)
		return
	}
	// 模拟检查用户名和密码是否匹配
	// 这里应该是与数据库中的用户名和密码进行比对
	if user.UserName == "用户的用户名" && user.Password == "用户的密码" {
		//生成一个Session ID
		sessionID := "你自己设置的sessionID"
		// 将Session ID 作为键 存储到redis中,并设置过期时间(此处为30分钟)
		Rdb.Set(context.Background(), sessionID, "你想存储的用户信息", time.Minute*30)
		//创建Cookie ,将Session ID 设置为Cookie的值
		/*
			name:"session"
			value:sessionID
			失效时间:3600
			path:指定cookie在哪个路径(路由)下生效,默认是`\`
			domain:指定cookie所属域名,默认是当前域名
			secure:该cookie是否被使用安全协议传输。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false
			httpOnly:如果给某个cookie设置了httpOnly属性,则无法通过js脚本读取到该cookie的信息。
		*/
		c.SetCookie("session", sessionID, 3600, "/", "localhost", false, true)
		c.Redirect(http.StatusFound, "/protected")
	} else {
		// 登录失败
		c.String(http.StatusUnauthorized, "Invalid username or password")
	}
}
func protectedHandler(c *gin.Context) {
	//检查是否存在session cookie
	cookie, err := c.Cookie("session")
	if err != nil || cookie == "" {
		c.Redirect(http.StatusOK, "/login")
		return
	}
	//检查session是否存在且过期
	isOk := Rdb.Exists(context.Background(), cookie).Val()
	duration := Rdb.TTL(context.Background(), cookie).Val()
	if isOk == 0 || duration <= 0 {
		c.Redirect(http.StatusFound, "/login")
		return
	}
	// 受保护页面的逻辑
	c.String(http.StatusOK, "Welcome to the protected page!")
}

优缺点

优点:

  • session-cookie 认证机制在基本上所有的网页浏览器上都能够支持
  • 实现方式简单

缺点:

  • 服务端需要存储Session,并且由于Session需要经常快速查找,通常存储在内存或内存数据库中 ,如果在线用户的人数较多时,会占用大量的服务器资源。
  • 当需要扩展时,创建Session的服务器可能不是验证Session的服务器,所以还需要将所有Session单独存储并共享。
  • 由于客户端使用Cookie存储SessionID,在跨域场景下需要进行兼容性处理,同时这种方式也难以防范CSRF攻击。

Token认证模式

简介

鉴于基于Session的会话管理方式存在上述的多个缺点,基于Token的无状态(服务端不存储信息)会话方式诞生了。
所谓的Token,其实就是服务端生成的一串加密字符串、以作客户端进行请求的一个“令牌”。当用户第一次使用账号密码成功进行登录后,服务器便生成一个Token及Token失效时间并将此返回给客户端,若成功登陆,以后客户端只需在有效时间内带上这个Token前来请求数据即可,无需再次带上用户名和密码。

逻辑如下:

  • 客户端使用用户名、密码进行认证

  • 服务端验证用户名、密码正确后生成Token返回给客户端

  • 客户端保存Token,访问需要认证的接口是在URL参数或HTTP Header中加入Token

  • 服务端通过解码Token进行鉴权,返回给客户端需要的数据
    在这里插入图片描述
    基于Token的会话管理方式有效解决了基于Session的会话管理方式带来的问题。

  • 服务端不需要存储和用户鉴权有关的信息,鉴权信息会被加密到Token中,服务端只需要读取Token中包含的鉴权信息即可

  • 避免了共享Session导致的不易扩展问题

  • 不需要依赖Cookie,有效避免Cookie带来的CSRF攻击问题

  • 使用CORS可以快速解决跨域问题

JWT介绍

JSON Web Token (JWT) 是一种为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准 ( RFC 7519 ),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

JWT本身没有定于任何技术实现,它只是定义了一种基于Token的会话管理的规则,涵盖Token需要包含的标准内容和Token的生成过程,特别适用于分布式站点的单点登录(SSO)场景。

以下是 JSON Web 令牌使用的一些场景:

  • 授权:这是使用 JWT 最常见的场景。用户登录后,每个后续请求都将包含 JWT,从而允许用户访问该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小并且能够轻松地跨不同域使用。
  • 信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。因为 JWT 可以进行签名(例如,使用公钥/私钥对),所以您可以确定发送者就是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。

JWT结构

JWT令牌是由点.分隔的三个部分组成:

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

JWT通常如下所示:

xxxxx.yyyyy.zzzzz

在这里插入图片描述

头部和负载以JSON的形式存在,这就是JWT中的JSON,三部分的内容都分别单独经过了Base64编码,以.拼接成一个JWT Token

标头(Header)

标头(Header)通常由两部分组成:令牌的类型(JWT)和所使用的签名算法(例如HMAC、SHA256或RSA)。

例如:

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

对该JSON进行Base64Url编码形成JWT的第一部分。

负载(Payload)

令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和附加数据的声明。
声明分为三种类型:注册声明、公开声明和私人声明。

  • 注册声明:这些是一组预定义的声明,不是强制性的,而是推荐的,以提供一组有用的、可互操作的声明。

iss(issuer):签发人/发行者
exp(expiration time):过期时间
sub(subject):主题
aud(audience):受众
nbf(Not Before):生效时间
iat(Issued At):签发时间
jti(JWT ID):编号

声明名称只有三个字符长,因为JWT旨在紧凑。

  • 公共声明:这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在IANA JSON Web 令牌注册表中定义,或者定义为包含防冲突命名空间的 URI。
  • 私人声明:为在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明。

负载实例:

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

有效负载进行Base64Url编码以形成 JSON Web 令牌的第二部分。

注意,对于签名令牌,此信息虽然受到防止篡改的保护,但任何人都可以读取。除非加密,否则请勿将秘密信息放入 JWT 的有效负载或标头元素中。

签名(Signature)

要创建签名部分,您必须获取编码的标头、编码的有效负载、密钥、标头中指定的算法,然后对其进行签名。

例如,如果要使用HMAC SHA256算法,则将通过以下方式创建签名:

你需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用Header里面指定的签名算法(默认是HMAC SHA256)按照下面的公式产生签名。

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

代码示例

// JwtPayLoad jwt中payload数据
type JwtPayLoad struct {
	Username string `json:"username"`  //用户名
	NickName string `json:"nick_name"` //昵称
	Role     int    `json:"role"`      // 权限 1 管理员 2 普通用户 3 游客
	UserID   uint   `json:"user_id"`   //用户id
}

type CustomClaims struct {
	JwtPayLoad
	jwt.StandardClaims
}

// GenToken 创建token
func GenToken(user JwtPayLoad) (string, error) {
	var MySecret = []byte(global.Config.Jwy.Secret)
	claim := CustomClaims{
		user,
		jwt.StandardClaims{
			ExpiresAt: jwt.At(time.Now().Add(time.Hour * time.Duration(global.Config.Jwy.Expires))), //默认2小时过期
			Issuer:    global.Config.Jwy.Issuer,                                                     // 签发人
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
	return token.SignedString(MySecret)
}

// ParseToken 解析token
func ParseToken(tokenStr string) (*CustomClaims, error) {
	var MySecret = []byte(global.Config.Jwy.Secret)
	token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return MySecret, nil
	})
	if err != nil {
		global.Logger.Error(fmt.Sprintf("token parse errr: %s", err.Error()))
		return nil, err
	}
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

JWT优缺点

JWT拥有基于Token的会话管理方式所拥有的一切优势,不依赖Cookie,使得其可以防止CSRF攻击,也能在禁用Cookie的浏览器环境中正常运行。

而JWT的最大优势是服务器不再需要存储Session,使得服务端认证鉴权业务可以方便扩展,避免存储在Token中,JWT Token一旦签发,就会在有效期内一直可用,无法在服务端废止,当用户进行登出操作,只能依赖客户端删除掉本地存储的JWT Token,如果需要禁用用户,单独使用JWT就无法做到了。

Access Token和Refresh Token认证模式

前面提到的Token,都是Access Token,也就是访问资源接口时所需要的Token,还有另外一种Token,Refresh Token,通常情况下,Refresh Token的有效期会比较长,而Access Token的有效期比较短,当Access Token由于过期而失效时,使用Refresh Token就可以获取到新的Access Token,如果Refresh Token也失效了,用户就只能重新登录了。

  • 客户端使用用户名密码进行认证
  • 服务端生成有效时间较短的Access Token(例如10分钟),和有效时间较长的Refresh Token(例如7天)
  • 客户端访问需要认证的接口时,携带Access Token
  • 如果Access Token没有过期,服务端鉴权后返回给客户端需要的数据
  • 如果携带Access Token访问需要认证的接口时鉴权失败(例如返回401错误),则客户端使用Refresh Token没有过期,服务端向客户端发新的Access Token
  • 如果Refresh Token没有过期,服务端向客户端发新的Access Token
  • 客户端使用新的Access Token访问需要认证的接口
    在这里插入图片描述

代码示例


//AccessClaims 
func (j *JWT) CreateAccessClaims(baseClaims request.BaseClaims) request.CustomClaims {
	accessExpires, _ := time.ParseDuration(setting.Conf.JWT.AccessExpiresTime)
	claims := request.CustomClaims{
		TypeClaims: "accessClaims",
		BaseClaims: baseClaims,
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(time.Now()),                    //签发时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(accessExpires)), // 过期时间 7天  配置文件
			Issuer:    setting.Conf.JWT.Issuer,                           // 签名的发行者
		},
	}
	return claims
}

//refreshClaims
func (j *JWT) CreateRefreshClaims(baseClaims request.BaseClaims) request.CustomClaims {
	RefreshExpires, _ := time.ParseDuration(setting.Conf.JWT.RefreshExpiresTime)
	claims := request.CustomClaims{
		TypeClaims: "refreshClaims",
		RegisteredClaims: jwt.RegisteredClaims{
			IssuedAt:  jwt.NewNumericDate(time.Now()),                     //签发时间
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(RefreshExpires)), // 过期时间   配置文件
			Issuer:    setting.Conf.JWT.Issuer,                            // 签名的发行者
		},
	}
	return claims
}

// CreateToken 创建一个token
func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.SigningKey) //SigningKey 秘钥 自己定义
}


// ParseToken 解析 token
func ParseToken(tokenStr string) (*CustomClaims, error) {
	var MySecret = []byte(global.Config.Jwy.Secret)
	token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return MySecret, nil
	})
	if err != nil {
		global.Logger.Error(fmt.Sprintf("token parse errr: %s", err.Error()))
		return nil, err
	}
	if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

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

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

相关文章

解决QT 编译qmake 无法找到问题

问题&#xff1a; Command qmake not found, but can be installed with: sudo apt install qtchooser 原因&#xff1a; 这个错误提示指出在当前环境中找不到 qmake 命令 解决方法&#xff1a; 其实ubuntu已经给提示了就是要安装qtchooser 安装命令为&#xff1a; sudo…

Springboot中 AOP实现日志信息的记录到数据库

1、导入相关的依赖 <!--spring切面aop依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency> 注意&#xff1a;在application.properties文件里加这样一…

串2:云计算架构思考

开始之前&#xff0c;先给出串1&#xff1a;一文将大数据、云计算、物联网、5G&#xff08;移动网&#xff09;、人工智能等最新技术串起来_龙赤子的博客-CSDN博客 承上 事物的复杂性一般有两个方面&#xff0c;一个是本身结构的复杂&#xff0c;一个是运行机制的复杂。因为这…

如何判断某个视频是深度伪造的?

目录 一、前言 二、仔细检查面部动作 三、声音可以提供线索 四、观察视频中人物的身体姿势 五、小心无意义的词语 深造伪造危险吗&#xff1f; 一、前言 制作深度伪造视频就像在Word文档中编辑文本一样简单。换句话说&#xff0c;您可以拍下任何人的视频&#xff0c;让他…

Java 使用 Google Guava 实现接口限流

一、引入依赖 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version> </dependency>二、自定义注解及限流拦截器 自定义注解&#xff1a;Limiter package com.haita…

更省更快更安全的云服务器,一站式集中管理,随时随地远程——站斧云桌面

随着全球化和数字化经济的发展&#xff0c;越来越多的企业开始海外扩张和拓展国际市场。而云服务器作为一种高效、灵活且可靠的IT基础设施方案&#xff0c;已成为出海企业不可或缺的重要工具。这里就为大家介绍云服务器在出海企业中的几个使用场景。 1.全球范围内协同办公 对…

Unity 资源与源码

Unity场景资源 Unity场景资源

USB TO TTL

什么是USB TO TTL&#xff1f; 常用的单片机&#xff0c;引出来的串口&#xff0c;如果不加其他的接口电路&#xff0c;出来的信号就是TTL电平。 如果需要看串口的打印信息&#xff0c;一般是需要接一个上位机的&#xff0c;常规的就是电脑&#xff0c;而现在的电脑一般的通信…

transformer理解

transformer的理解 Q、K、V的理解 核心是自注意力机制。即每个位置的结果为所有位置的加权平均和。为了得到每个位置的权重,需要Q*K得到。 整个多头的self-attention过程 单个encoder encoder-decoder encoder中的K和V会传到decoder中的encoder-decoder attention中。 …

Linux实训笔记~操作系统概述

1、操作系统 操作系统作为接口的示意图: 没有安装操作系统的计算机, 通常被称为裸机。 2、不同应用利于的主流操作系统 桌面操作系统 服务器操作系统 嵌入式操作系统 移动设备操作系统

postman接口测试实战讲解

目录 背景描述 创建一个GET请求 在pre-request scripts构建签名 脚本写在环境变量中 postman console的用法 Collection Runner 自动化API测试 创建接口的测试用例 选择并运行自动化接口测试 测试结果 有还不懂的同学可以找我拿演示视频喔 背景描述 有一个项目要使…

C语言实现基于Linux,epoll和多线程的WebServer服务器

代码结构&#xff1a; Server.h 头文件&#xff0c;对函数进行了声明 #pragma once #include<stdio.h> // 新建一个用于TCP监听的socket文件描述符&#xff0c;并返回 int initListenFd(unsigned short port);// 启动epoll int epollRun(int lfd);// accept建立连接 vo…

SpringCloudAlibaba微服务实战系列(五)Sentinel1.8.5+Nacos持久化

Sentinel数据持久化 前面介绍Sentinel的流控、熔断降级等功能&#xff0c;同时Sentinel应用也在面临着一个问题&#xff1a;我们在Sentinel后台管理界面中配置了一堆流控、降级规则&#xff0c;但是Sentinel一重启&#xff0c;这些规则全部消失了。那么我们就要考虑Sentinel的持…

【代码随想录day19】从中序与后序遍历序列构造二叉树

题目 思路 思路同 从前序与中序遍历序列构造二叉树&#xff0c;区别是root需要从postorder列表的尾部取。 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left l…

CSRF跨站请求伪造总结

CSRF 什么是CSRF&#xff1f; CSRF被称为跨站请求伪造&#xff0c;它利用用户已登录的身份&#xff0c;在用户毫不知情的情况下&#xff0c;以用户的名义完成非法操作。 跟跨站脚本攻击&#xff08;XSS&#xff09;相比&#xff0c;XSS利用的是用户对指定网站的信任&#xf…

JasperReport与SpringBoot整合及模板制作

0. 示例代码 示例代码地址 1. 报表介绍 1.1 为什么要使用报表? 在企业级应用开发中&#xff0c;报表生成、报表打印下载是其重要的一个环节。除了Excel报表之外&#xff0c;PDF报表也有广泛的应用场景&#xff0c;必须用户详细资料&#xff0c;用户简历等 目前世面上比较流…

数据结构(二)

目录 Trie树 并查集 堆 Trie树 作用:用来高效地存储和查找字符串集合的数据结构 基本形式: 模板代码如下: #include<iostream> using namespace std;const int N 100010;//idx代表当前用到哪个下标 //既是根节点&#xff0c;又是空节点 //cnt存储的是以当前点结尾的…

在Springboot集成Activiti工作流引擎-引入、调用,测试【基础讲解】

工作流 通过计算机对业务流程自动化执行管理 他主要解决的是使在多个参与者之间按照某种“预定义规则”自动进行传递稳定 信息或任务的过程 通俗来讲 业务上一个玩着的审批流程 比如请假&#xff0c;出差 外出采购等 工作流引擎就是来解决流程问题的 提高我们的工作效率 如果…

day43-Spring_IOC

0目录 1.2.3 1. Spring_IOC 1.1 定义&#xff1a;轻量级框架&#xff0c;java EE的春天&#xff0c;主流框架 1.2 Spring特性&#xff1a;IOC控制反转&#xff1b;AOP面相切面 1.3 组成部分&#xff1a;Spring在SSM中所起到的作用&#xff08;SpringMVC和Mybatis框架的黏…

Mybatis-Plus插入数据返回主键两种方式(注解或XML)

废话不多说&#xff0c;直接撸代码: <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace&qu…