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

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

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

相关文章

uniapp实现自定义导航内容高度居中(兼容APP端以及小程序端与胶囊对齐)

①效果图如下 1.小程序端与胶囊对齐 2.APP端内容区域居中 注意:上面使用的是colorui里面的自定义导航样式。 ②思路: 1.APP端和小程序端走不同的方法,因为小程序端要计算不同屏幕下右侧胶囊的高度。 2.其次最重要的要清晰App端和小程序端…

【数学建模】清风数模更新5 灰色关联分析

灰色关联分析综述 诸如经济系统、生态系统、社会系统等抽象系统都包含许多因素,系统整体的发展受各个因素共同影响。 为了更好地推动系统发展,我们需要清楚哪些因素是主要的,哪些是次要的,哪些是积极的,哪些是消极的…

网络基础——网络的由来与发展史

作者:Insist-- 个人主页:insist--个人主页 作者会持续更新网络知识和python基础知识,期待你的关注 目录 一、网络的由来 二、计算机网络的发展史 1、第一阶段 2、第二阶段 3、第三阶段 前言 每天都是使用网络,那么你知道网络…

FPGA----Vivado SDK创建并使用静态链接库(C/C++代码移植)

1、在进行SoC开发时,PS端的C/C代码可能涉及到核心算法需要移植操作,为此,本文讲述了如何将C/C代码打包为.a文件供程序调用 2、文章以我的程序为例,逐步讲述代码生成静态链接库并调用的方法。 下面是我程序的目录结构&#xff0c…

RocketMQ 延迟消息

RocketMQ 延迟消息 RocketMQ 消费者启动流程 什么是延迟消息 RocketMQ 延迟消息是指,生产者发送消息给消费者消息,消费者需要等待一段时间后才能消费到。 使用场景 用户下单之后,15分钟未支付,对支付账单进行提醒或者关单处理…

PostgreSQL查询慢sql原因和优化方案

PostgreSQL sql查询慢优化方案有一下几种解决方案: 1.关闭会话 查询慢sql的执行会话,关闭进程。 查看数据库后台连接进程 SELECT count(*) FROM pg_stat_activity;SELECT * FROM pg_stat_activity; 查看数据库后台连接进程,但是此条SQL不…

用HARU-Net增强核分割:一种基于混合注意的残差u块网络

文章目录 Enhancing Nucleus Segmentation with HARU-Net: A Hybrid Attention Based Residual U-Blocks Network摘要本文方法损失函数后处理消融实验 Enhancing Nucleus Segmentation with HARU-Net: A Hybrid Attention Based Residual U-Blocks Network 摘要 核图像分割是…

W6100-EVB-PICO 做TCP Server进行回环测试(六)

前言 上一章我们用W6100-EVB-PICO开发板做TCP 客户端连接服务器进行数据回环测试,那么本章将用开发板做TCP服务器来进行数据回环测试。 TCP是什么?什么是TCP Server?能干什么? TCP (Transmission Control Protocol) 是一种面向连…

zabbix监控安装部署

目录 一、环境 二、配置 1.配置yum源,这里用的清华的 2.过滤一下安装包,查看依赖包 安装依赖包 3.配置数据库 开机自启 创建数据库 创建用户 授权 导入数据到数据库 查看zabbix数据库有没有表和数据 4.修改zabbix配置文件 1.修改zabbix配置…

去趋势化一个心电图信号、信号功率谱、低通IIR滤波器并平滑信号、对滤波器引起的延迟进行补偿研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

SPM实现framework自动管理和分发

一、前言 Swift Package Manager (SPM) 是苹果官方提供的用于管理 Swift 项目的依赖关系和构建过程的工具。它是一个集成在 Swift 编程语言中的包管理器,用于解决在开发过程中管理和构建包依赖项的需求。 那么如何使用SPM管理和分发Objective C编写的二进制库呢&a…

不同路径 II——力扣63

class Solution {public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int n=

【APITable】教程:创建并运行一个自建小程序

1.进入APITable&#xff0c;在想要创建小程序的看板页面点击右上角的【小程序】&#xff0c;进入小程序编辑页面。 2.创建一个新的小程序区。 点击【 添加小程序】 点击创建小程序&#xff0c;选择模板&#xff0c;输入名字。 3.确定后进入小程序部署引导页面。 4.打开Xshell 7…

初识鸿蒙跨平台开发框架ArkUI-X

HarmonyOS是一款面向万物互联时代的、全新的分布式操作系统。在传统的单设备系统能力基础上&#xff0c;HarmonyOS提出了基于同一套系统能力、适配多种终端形态的分布式理念&#xff0c;能够支持手机、平板、智能穿戴、智慧屏、车机等多种终端设备&#xff0c;提供全场景&#…

99. for循环练习题-3种方式输出0-9

【目录】 文章目录 99. for循环练习题-3种方式输出0-91. for循环和while循环的区别2. 输出 0~(n-1)的数字2.1 基础代码2.2 自定义函数代码2.3 异常处理语句代码 【正文】 99. for循环练习题-3种方式输出0-9 1. for循环和while循环的区别 for循环和while循环都用于重复执行特定…

idea模板的使用(配置xml文件模板)

1. 问题的引出 我们在日常项目中可以发现&#xff0c;sql映射文件和mybatis主配置文件&#xff0c;以及application.yml文件中有很多固定不变的内容&#xff0c;为了方面使用&#xff0c;所以可以把这些xml文件设置为模板 2. 创建模板的步骤 按照图片一步一步进行即可 点击…

(二)结构型模式:1、适配器模式(Adapter Pattern)(C++实现示例)

目录 1、适配器模式&#xff08;Adapter Pattern&#xff09;含义 2、适配器模式应用场景 3、适配器模式的UML图学习 4、C实现适配器模式的示例 1、适配器模式&#xff08;Adapter Pattern&#xff09;含义 将一个接口转换为客户端所期待的接口&#xff0c;从而使两个接口…

Mac安装nvm教程及使用

nvm 是 node 版本管理器&#xff0c;也就是说一个 nvm 可以管理多个 node 版本&#xff08;包含 npm 与 npx&#xff09;&#xff0c;可以方便快捷的安装、切换 不同版本的 node。 1、直接通过brew安装 执行命令&#xff1a;brew install nvm PS&#xff1a; 如果没有安装br…

Oracle database 静默安装 oracle 11g 一键安装

基于oracle安装包中应答文件实现一键安装 支持环境&#xff1a; Linux &#xff1a;centerOS 7 oracle &#xff1a;11.2.0 Oracle应答文件 runInstaller应答文件 /database/response/db_install.rsp netca应答文件 /database/response/netca.rsp dbca应答文件 /database/re…

python中的运算符号含义,python基本运算符的操作

本篇文章给大家谈谈python的运算符号有哪些类型&#xff0c;以及python各运算符号的功能说明&#xff0c;希望对各位有所帮助&#xff0c;不要忘了收藏本站喔。 1.算数运算符&#xff08;最常见的&#xff09; 标准算数运算符&#xff08;加减乘除&#xff09; 取余运算…