当前位置: 首页 > news >正文

Go Gin 中使用 JWT

一、JWT

JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。

二、为什么要用在你的Gin中使用JWT

传统的Cookie-Sesson模式占用服务器内存, 拓展性不好,遇到集群或者跨服务验证的场景的话, 要支持Sesson复制或者sesson持久化

1.JWT的基本原理

在服务器验证之后, 得到用户信息JSON

1

2

3

4

5

{

     "user_id": "xxxxxx",

    "role": "xxxxxx",

    "refresh_token": "xxxxx"

}

(1)JWT TOKEN怎么组成

JWT是一个很长的字符串

eyJhbGciOiJI123afasrwrqqewrcCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

它由三部分组成, 每部分用点(.)分隔, 三个部分依次如下

 

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

1)Header

Header是一个经过BASE64URL算法加密过的JSON对象, 解密后如下

1

2

3

4

{

  "alg": "HS256",

  "typ": "JWT"

}

其中, alg属性表示签名所用的算法,默认是HS256;

typ则表示当前token的类型, 而JWT的类型则为jwt

Base64URL

与BASE64类似, 由于+、/、=这几个符号在URL中有特殊含义, 因此BASE64RUL算法, 把这几个符号进行了替换

+ -> -

= -> 被忽略

/ -> _

2)Payload

Payload部分也是JSON对象经过BASE64URL算法转成字符串的, Payload部分包含7个基本字段

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

也可以往里面塞入自定义的业务字段, 如下

user_id

role

3)Signature

Signature 部分是对前两部分的签名,防止数据篡改

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

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

(2)解密过程

当系统接收到TOKEN时, 拿出Header和Payload的字符串用.拼接在一起之后, 用Header里面指定的哈希方法通过公式

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

进行加密得出密文

然后用刚刚得出的密文与TOKEN传过来的密文对比, 如果相等则表明密文没有更改.

三、JWT一些特点(优点与缺点)

  • JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  • JWT 不加密的情况下,不能将秘密数据写入 JWT。
  • JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  • JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  • JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  • 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。

1.GIN整合JWT

1

2

go get -u github.com/dgrijalva/jwt-go

go get github.com/gin-gonic/gin

编写jwtutil

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

var Secret = []byte("whatasecret")

// jwt过期时间, 按照实际环境设置

const expiration = 2 * time.Minute

type Claims struct {

    // 自定义字段, 可以存在用户名, 用户ID, 用户角色等等

    Username string

    // jwt.StandardClaims包含了官方定义的字段

    jwt.StandardClaims

}

func GenToken(username string) (string, error) {

    // 创建声明

    a := &Claims{

        Username: username,

        StandardClaims: jwt.StandardClaims{

            ExpiresAt: time.Now().Add(expiration).Unix(), // 过期时间

            IssuedAt:  time.Now().Unix(),                 // 签发时间

            Issuer:    "gin-jwt-demo",                    // 签发者

            Id:        "",                                // 按需求选这个, 有些实现中, 会控制这个ID是不是在黑/白名单来判断是否还有效

            NotBefore: 0,                                 // 生效起始时间

            Subject:   "",                                // 主题

        },

    }

    // 用指定的哈希方法创建签名对象

    tt := jwt.NewWithClaims(jwt.SigningMethodHS256, a)

    // 用上面的声明和签名对象签名字符串token

    // 1. 先对Header和PayLoad进行Base64URL转换

    // 2. Header和PayLoadBase64URL转换后的字符串用.拼接在一起

    // 3. 用secret对拼接在一起之后的字符串进行HASH加密

    // 4. 连在一起返回

    return tt.SignedString(Secret)

}

func ParseToken(tokenStr string) (*Claims, error) {

    // 第三个参数: 提供一个回调函数用于提供要选择的秘钥, 回调函数里面的token参数,是已经解析但未验证的,可以根据token里面的值做一些逻辑, 如`kid`的判断

    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {

        return Secret, nil

    })

    if err != nil {

        return nil, err

    }

    // 校验token

    if claims, ok := token.Claims.(*Claims); ok && token.Valid {

        return claims, nil

    }

    return nil, errors.New("invalid token")

}

  • Secret是秘钥, 用于加密签名
  • expiration是TOKEN过期时间
  • Claims是签名声明对象, 包含自定义的字段和JWT规定的字段

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

type Claims struct {

    // 自定义字段, 可以存在用户名, 用户ID, 用户角色等等

    Username string

    // jwt.StandardClaims包含了官方定义的字段

    jwt.StandardClaims

}

type StandardClaims struct {

    Audience  string `json:"aud,omitempty"`

    ExpiresAt int64  `json:"exp,omitempty"`

    Id        string `json:"jti,omitempty"`

    IssuedAt  int64  `json:"iat,omitempty"`

    Issuer    string `json:"iss,omitempty"`

    NotBefore int64  `json:"nbf,omitempty"`

    Subject   string `json:"sub,omitempty"`

}

(1)GenToken方法

GenToken方法为某个username生成一个token, 每次生成都不一样

jwt.NewWithClaims(jwt.SigningMethodHS256, a)声明了一个签名对象, 并且指定了HS256的哈希算法

token.SignedString(Secret)表明用刚刚的声明对象和SECRET利用指定的哈希算法去加密,包含下面流程

  • 先对Header和PayLoad进行Base64URL转换
  • Header和PayLoadBase64URL转换后的字符串用.拼接在一起
  • 用secret对拼接在一起之后的字符串进行HASH加密
  • BASE64URL(Header).BASE64URL(Payload).signature连在一起的字符串返回

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

func GenToken(username string) (string, error) {

    // 创建声明

    a := &Claims{

        Username: username,

        StandardClaims: jwt.StandardClaims{

            ExpiresAt: time.Now().Add(expiration).Unix(), // 过期时间

            IssuedAt:  time.Now().Unix(),                 // 签发时间

            Issuer:    "gin-jwt-demo",                    // 签发者

            Id:        "",                                // 按需求选这个, 有些实现中, 会控制这个ID是不是在黑/白名单来判断是否还有效

            NotBefore: 0,                                 // 生效起始时间

            Subject:   "",                                // 主题

        },

    }

    // 用指定的哈希方法创建签名对象

    tt := jwt.NewWithClaims(jwt.SigningMethodHS256, a)

    // 用上面的声明和签名对象签名字符串token

    // 1. 先对Header和PayLoad进行Base64URL转换

    // 2. Header和PayLoadBase64URL转换后的字符串用.拼接在一起

    // 3. 用secret对拼接在一起之后的字符串进行HASH加密

    // 4. 连在一起返回

    return tt.SignedString(Secret)

}

(2)ParseToken方法

ParseToken方法解析一个Token, 并验证Token是否生效

jwt.ParseWithClaims方法, 用于解析Token, 其第三个参数:

提供一个回调函数用于提供要选择的秘钥, 回调函数里面的token参数,是已经解析但未验证的,可以根据token里面的值做一些逻辑, 如判断kid来选用不同的secret

KID(可选): 代表秘钥序号。开发人员可以用它标识认证token的某一秘钥

1

2

3

4

5

6

7

8

9

10

11

12

13

14

func ParseToken(tokenStr string) (*Claims, error) {

    // 第三个参数: 提供一个回调函数用于提供要选择的秘钥, 回调函数里面的token参数,是已经解析但未验证的,可以根据token里面的值做一些逻辑, 如`kid`的判断

    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {

        return Secret, nil

    })

    if err != nil {

        return nil, err

    }

    // 校验token

    if claims, ok := token.Claims.(*Claims); ok && token.Valid {

        return claims, nil

    }

    return nil, errors.New("invalid token")

}

编写中间件

从Header中取出Authorization并拿去解析jwt.ParseToken,

验证token是否被串改, 是否过期

从token取出有效信息并设置到上下文

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

func JWTAuthMiddleware() func(ctx *gin.Context) {

    return func(ctx *gin.Context) {

        // 根据实际情况取TOKEN, 这里从request header取

        tokenStr := ctx.Request.Header.Get("Authorization")

        if tokenStr == "" {

            ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{

                "code": code.ERR_AUTH_NULL,

                "msg":  code.GetMsg(code.ERR_AUTH_NULL),

            })

            return

        }

        claims, err := jwt.ParseToken(tokenStr)

        if err != nil {

            ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{

                "code": code.ERR_AUTH_INVALID,

                "msg":  code.GetMsg(code.ERR_AUTH_INVALID),

            })

            return

        } else if time.Now().Unix() > claims.ExpiresAt {

            ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{

                "code": code.ERR_AUTH_EXPIRED,

                "msg":  code.GetMsg(code.ERR_AUTH_EXPIRED),

            })

            return

        }

        // 此处已经通过了, 可以把Claims中的有效信息拿出来放入上下文使用

        ctx.Set("username", claims.Username)

        ctx.Next()

    }

}

使用中间件

/login不用中间件

中间件指定在authorizedrouter, 因此authorized下的所有路由都会使用此中间件

1

2

3

4

5

6

7

8

9

10

func main() {

    r := gin.Default()

    r.POST("/login", router.Login)

    authorized := r.Group("/auth")

    authorized.Use(jwt.JWTAuthMiddleware())

    {

        authorized.GET("/getUserInfo", router.GetUserInfo)

    }

    r.Run(":8082")

}

测试

login请求获取token

POST http://localhost:8082/login

 

把token放入getUserInfo请求

GET  http://localhost:8082/auth/getUserInfo

 

其他

完整的JWT登录还应该包括

  • 使TOKEN失效(过期或者黑名单等功能)
  • refresh token

相关文章:

Go Gin 中使用 JWT

一、JWT JWT全称JSON Web Token是一种跨域认证解决方案,属于一个开放的标准,它规定了一种Token实现方式,目前多用于前后端分离项目和OAuth2.0业务场景下。 二、为什么要用在你的Gin中使用JWT 传统的Cookie-Sesson模式占用服务器内存, 拓展性…...

AWS中Lambda集成SNS

1.创建Lambda 在Lambda中,创建名为AWSSNSDemo的函数 use strict console.log(loading function); var aws require(aws-sdk); var docClient new aws.DynamoDB.DocumentClient(); aws.config.regionap-southeast-1;exports.handler function(event,context,cal…...

Mac下⬇️Git如何下载/上传远程仓库

使用终端检查电脑是否安装Git git --version 通过此文章安装Git ➡️ ​​​​​​​传送门🌐 方式1⃣️使用终端操作 1.下载——克隆远程仓库到本地 git clone [远程地址] 例:git clone https://gitee.com/lcannal/movie.git​ 2.编…...

linux 命令--常用关机命令

1.使用shutdown命令 shutdown命令是Linux系统下最常用的关机命令之一。它可以让系统在指定时间内进行关机或者重启操作。例如,下面的命令可以让系统在5分钟后进行关机操作: sudo shutdown -h5其中,“-h”表示关机,“5”表示5分钟…...

ttf-dejavu fontconfig字体

ttf-dejavu fontconfig是验证码,pdf,excel时需要用到的字体 编辑dockerfile,先切换国内镜像源,默认alpinelinux是国外源,下载包会很慢 vim Dockerfile FROM alpine:latest RUN sed -i s/dl-cdn.alpinelinux.org/mirr…...

Open3D点云数据处理(十九):最小二乘直线拟合(矩阵方程法)

文章目录 1 最小二乘直线拟合原理(矩阵方程角度)2 相关知识2.1 超定线性方程组2.2 正规方程2.3 奇异值分解3 最小二乘直线拟合代码实现4 点云最小二乘直线拟合5 相关链接专栏目录:Open3D点云数据处理(Python) 1 最小二乘直线拟合原理(矩阵方程角度) 最小二乘直线拟合是…...

数据库事务ACID介绍

一、ACID简介 ACID,是指数据库管理系统(DBMS)在增删改数据的的过程中,为保证事务(transaction)的准确性,可靠性等,所必须具备的四个特性:原子性(atomicity&a…...

SM8650 qcxserver.c STRM_Initialize

STRM_Initialize streammanager 初始化流程 目录 STRM_Initialize Gptp::Init Config::Init SensorManager::Init SensorPlatform::SensorPlatformInit SensorManager::LoadSensorLib SensorManager::OpenSensorLib SensorManager::DetectAll SensorManager::DetectHandlerT…...

适配器模式-java实现

意图 复用已经存在的接口,与所需接口不一致的类。即将一个类(通常是旧系统中的功能类),通过适配器转化成另一个接口的实现。(简单来说,就是复用旧系统的功能,去实现新的接口) 我们举…...

【elasticSearch系】3.完整搭建详尽版elk

话不多说,我们先看下经典的elk 是由哪些组件搭建组合起来的 elasticsearch和kibana搭建 可以查看之前我们搭建elasticsearch和kibana 的这篇文章 logstash搭建 为了和之前我们搭建elasticsearch和kibana版本保持一致,这里我们还是选择7.17.3 下载地址 点击下载,这里为了…...

代码随想录day04

24. 两两交换链表中的节点 ● 力扣题目链接 ● 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 ● 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 思路 ● 使用迭代的方法,分析交换逻辑即可 ○ …...

[Realtek] WPA_SUPPLICANT + WPA_CLI使用指南

开启wpa_supplicant wpa_supplicant –Dnl80211 -iwlan0 -c ./wpa.conf –B 或者 wpa_supplicant -Dwext -iwlan0 -c ./wpa.conf -B 扫描AP wpa_cli -p/var/run/wpa_supplicant scan 查看AP扫描结果 wpa_cli -p/var/run/wpa_supplicant scan_results 连接到热点 OPEN…...

# ⛳ Docker 安装、配置和详细使用教程-Win10专业版

目录 ⛳ Docker 安装、配置和详细使用教程-Win10专业版🚜 一、win10 系统配置🎨 二、Docker下载和安装🏭 三、Docker配置🎉 四、Docker入门使用 ⛳ Docker 安装、配置和详细使用教程-Win10专业版 🚜 一、win10 系统配…...

Linux 教程

目录 Linux 教程 内核引导 运行init 运行级别 系统初始化 Linux 系统目录结构 Linux 教程 Lin...

图论——最短路算法

引入&#xff1a; 如上图&#xff0c;已知图G。 问节点1到节点3的最短距离。 可心算而出为d[1,2]d[2,3]112,比d[1,3]要小。 求最短路径算法&#xff1a; 1.Floyd(弗洛伊德) 是一种基于三角形不等式的多源最短路径算法。边权可以为负数 表现为a[i,j]a[j,k]<a[i,k]。 …...

在项目中增加网络加载需要考虑什么?

1、下载器 网络加载的第一步肯定是下载&#xff0c;那么选择一个合适的下载器是十分重要的&#xff0c;这个下载器最好支持什么功能&#xff1f; 多线程下载&#xff08;同时需要服务端支持&#xff0c;下载时可指定range&#xff09; 断点续传 通用性&#xff08;其他位置也…...

阿里云服务器部署RabbitMQ流程

阿里云百科分享使用阿里云服务器部署RabbitMQ流程&#xff0c;RabbitMQ是实现了高级消息队列协议&#xff08;AMQP&#xff09;的开源消息代理软件&#xff0c;用于在分布式系统中存储转发消息&#xff0c;有良好的易用性、扩展性和高可用性。本文介绍如何通过ECS实例部署Rabbi…...

青大数据结构【2014】

一、单选 二、简答 为了解决顺序队列的假溢出问题&#xff0c;提出了循环队列&#xff0c;即把存储队列的表从逻辑上看成一个环 判别队列空和满有三种方法&#xff1a; 1&#xff09;采用计数器判别&#xff0c;空时&#xff0c;计数器为0&#xff1b;满时&#xff0c;计数器…...

Ansible Playbook快速部署一主多从MySQL集群

部署目标&#xff1a; 1、快速部署一套一主两从的mysql集群 2、部署过程中支持交互式定义安装目录及监听端口号 部署清单目录结构&#xff1a; rootmaster:/opt/mysql# tree . . ├── group_vars │ └── all.yml ├── hosts ├── mysql.yml └── roles└── mys…...

27.Netty源码之FastThreadLocal

highlight: arduino-light FastThreadLocal FastThreadLocal 的实现与 ThreadLocal 非常类似&#xff0c;Netty 为 FastThreadLocal 量身打造了 FastThreadLocalThread 和 InternalThreadLocalMap 两个重要的类。下面我们看下这两个类是如何实现的。 FastThreadLocalThread 是对…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

初探Service服务发现机制

1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能&#xff1a;服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源&#xf…...

20个超级好用的 CSS 动画库

分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码&#xff0c;而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库&#xff0c;可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画&#xff0c;可以包含在你的网页或应用项目中。 3.An…...

(一)单例模式

一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...