乔克视界 乔克视界
首页
  • 运维
  • 开发
  • 监控
  • 安全
  • 随笔
  • Docker
  • Golang
  • Python
  • AIOps
  • DevOps
  • 心情杂货
  • 读书笔记
  • 面试
  • 实用技巧
  • 博客搭建
友链
关于
收藏
  • 分类
  • 标签
  • 归档

乔克

云原生爱好者
首页
  • 运维
  • 开发
  • 监控
  • 安全
  • 随笔
  • Docker
  • Golang
  • Python
  • AIOps
  • DevOps
  • 心情杂货
  • 读书笔记
  • 面试
  • 实用技巧
  • 博客搭建
友链
关于
收藏
  • 分类
  • 标签
  • 归档
  • Docker

  • Golang

    • Golang基础知识

    • Golang进阶知识

    • Golang常用包

    • Gin框架

      • 安装
      • gin路由
      • 请求数据参数绑定
      • gin渲染
      • 使用模板渲染
      • 静态文件的使用
      • 数据渲染
      • gin重定向
      • gin同步和异步
      • go中间件
      • 会话保持
      • 文件上传
      • JWT的简单使用
      • 模板函数
      • Swagger
      • API访问控制
        • 常见的应用中间件
        • 应用配置管理
        • 优雅停止与重启
        • 集成Casbin进行访问权限控制
    • AIOps

    • Python

    • DevOps

    • 专栏
    • Golang
    • Gin框架
    乔克
    2025-07-19
    目录

    API访问控制

    在开发完业务接口后,需要对 API 接口进行访问控制,以免所有接口直接暴露在外,非常不安全。

    目前常见得 API 访问控制有两种方案:

    • OAuth 2.0
    • JWT

    OAuth 2.0 本质上是一个授权的行业标准协议,提供了一整套授权机制的指导标准,常用于使用第三方登录的情况。例如,在登录某些网站时,也可以用第三方站点(例如用微信、QQ、GitHub 账号)关联登录,这些往往是用 OAuth 2.0 的标准实现的。OAuth 2.0 相对会“重”一些,常常还会授予第三方应用获取对应账号的个人基本信息等。

    JWT 与 OAuth 2.0 完全不同,它常用于前后端分离的情况,能够非常便捷地给 API 接口提供安全鉴权。

    # JWT 简介

    JSON Web 令牌(JWT)是一个开放标准(RFC7519),它定义了一种紧凑且自包含的方式,用于在各方之间以 JSON 对象安全地传输信息。由于此信息是经过数字签名的,因此可以被验证和信任。我们可以使用 RSA 或 ECDSA 的公用或专用密钥对 JWT 进行签名。

    JWT 是以紧凑的形式由三部分组成的,这三部分之间以点“.”分隔,组成“xxxxx.yyyyy.zzzzz”的格式,三个部分的含义如下:

    • Header:头部。
    • Payload:有效载荷。
    • Signature:签名。

    # Header

    Header (头部)通常由两部分组成,分别是令牌的类型和所使用的签名算法(HMAC SHA256、RSA 等),它们会组成一个 JSON 对象,用于描述其元数据,例如:

    {
        "alg": "HS256",
        "typ": "JWT"
    }
    
    1
    2
    3
    4

    在上述 JSON 对象中,alg 字段用来表示使用的签名算法,默认是 HMAC SHA256(HS256)。type 字段用来表示使用的令牌类型,这里使用的是 JWT。最后,用 base64UrlEncode 算法对上面的 JSON 对象进行转换,使其成为 JWT 的第一部分。

    # Payload

    Payload(有效负载)是一个 JSON 对象,主要用于存储在 JWT 中实际传输的数据,代码如下(注意,这里只截选了部分代码):

    {
        "sub": "123456",
        "name": "joker",
        "admin": "true"
    }
    
    1
    2
    3
    4
    5

    参数有:

    • aud(Audience):受众,即接受 JWT 的一方。
    • exp(ExpiresAt):所签发的 JWT 过期时间,过期时间必须大于签发时间。
    • jti(JWT Id):JWT 的唯一标识。
    • iat(IssuedAt):签发时间
    • iss(Issuer):JWT 的签发者。
    • nbf(Not Before):JWT 的生效时间,如果未到这个时间,则不可用。
    • sub(Subject):主题。

    同样,使用 base64UrlEncode 算法对该 JSON 对象进行转换,使其成为 JWT Token 的第二部分。需要注意的是,JWT 在转换时用的是 base64UrlEncode 算法,而该算法是可逆的,因此一些敏感信息建议不要放到 JWT 中。如果一定要放,则应进行一定的加密处理。

    # Signature

    Signature(签名)部分是对前面两个部分(Header+Payload)进行约定算法和规则的签名。签名一般用于校验消息在整个过程中有没有被篡改,并且对使用了私钥进行签名的令牌,它还可以验证 JWT 的发送者是否是它的真实身份。

    在生成签名时,首先在应用程序中指定密钥(secret),然后使用传入的指定签名算法(默认是 HMAC SHA256)通过下述签名方式生成 Signature,代码如下:

    HMACSHA256{
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret
    }
    
    1
    2
    3
    4
    5

    由此可以看出,JWT 的第三部分是由 Header、Payload 和 secret 的算法组成的,因而可以用来校验消息是否被篡改。因为一旦消息被篡改,Signature 就无法对上。

    # Base64UrlEncode 算法

    Base64UrlEncode 算法是 Base64 算法的变种。为什么要变呢?原因是 JWT 令牌经常被放在 Header 或 Query Param 中,即 URL 中。

    而在 URL 中,一些个别字符是有特殊意义的,如“+”“/”“=”等。因此在 Base64 UrlEncode 算法中,会对其进行替换。例如,把“+”替换为“-”、把“/”替换为“_”,而"=" 会被忽略处理,以此保证 JWT 令牌在 URL 中的可用性和准确性。

    # JWT 使用场景

    首先,在内部约定好 JWT 令牌的交流方式,比如可以存储在 Header、QueryParam、cookie 或 session 中,最常见的是存储在 Header 中。然后,服务器端提供一个获取 JWT 令牌的接口方法,返回给客户端使用。当客户端请求其余接口时,需要带上所签发的 JWT 令牌,而服务器端接口也会到约定位置获取 JWT 令牌进行鉴权处理。

    # 安装 JWT

    首先拉取 jwt-go 库,该库提供了 JWT 的 Go 实现,能够便捷地提供 JWT 支持,然后执行如下命令:

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

    # 配置 JWT

    # 创建表

    create table `auth` (
        `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
        `app_key` varchar(20) DEFAULT '' COMMENT 'key',
        `app_secret` varchar(50) DEFAULT '' COMMENT 'Secret',
        `created_on` int(10) unsigned default '0' comment '创建时间',
        `created_by` varchar(100) default '' comment '创建人',
        `modified_on` int(10) unsigned default '0' comment '修改时间',
        `modified_by` varchar(100) default '' comment '修改人',
        `deleted_on` int(10) unsigned default '0' comment '删除时间',
        `is_del` tinyint(3) unsigned default '0' comment '是否删除,0表示未删除,1表示删除',
        PRIMARY KEY (`id`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT="认证管理";
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    新增一条测试数据:

    INSERT INTO `blog_auth`(`id`, `app_key`, `app_secret`, `created_on`, `created_by`, `modified_on`, `modified_by`, `deleted_on`, `is_del`) VALUES (1, 'coolops', 'mysecret', 0, 'joker', 0, '', 0, 0);
    
    1

    # 新建 Model 对象

    package model
    
    import "github.com/jinzhu/gorm"
    
    // 公共
    type Model struct {
    	ID         uint32 `gorm:"primary_key" json:"id"`
    	CreatedBy  string `json:"created_by"`
    	ModifiedBy string `json:"modified_by"`
    	CreatedOn  uint32 `json:"created_on"`
    	ModifiedOn uint32 `json:"modified_on"`
    	DeletedOn  uint32 `json:"deleted_on"`
    	IsDel      uint8  `json:"is_del"`
    }
    
    // 认证
    type Auth struct {
    	*Model
    	AppKey    string `json:"app_key"`
    	AppSecret string `json:"app_secret"`
    }
    
    func (a Auth) TableName() string {
    	return "auth"
    }
    
    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

    # 初始化配置

    在配置文件 config.yaml 中新增 JWT 配置,如下:

    # 认证
    JWT:
      Secret: mysecret
      Issuer: blog_service
      Expire: 7200
    
    1
    2
    3
    4
    5

    新增配置文件解析结构体,如下:

    // JWT配置
    type JWTSettingS struct {
    	Secret string
    	Issuer string
    	Expire time.Duration
    }
    
    1
    2
    3
    4
    5
    6

    将 JWTSettingS 加入全局配置中,如下:

    package global
    
    // 全局配置文件
    import "code.coolops.cn/blog_services/pkg/setting"
    
    var (
    	...
    	JWTSetting      *setting.JWTSettingS
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    配置 main.go,读取配置文件

    func setupSetting() error {
    	setting, err := setting2.NewSetting()
    	if err != nil {
    		return err
    	}
    	......
    	err = setting.ReadSection("JWT", &global.JWTSetting)
    	if err != nil {
    		return err
    	}
    	global.JWTSetting.Expire *= time.Second
    	return nil
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    需要注意的是,千万不要把 Secret 暴露给外部,即只能让服务器端知道,否则一旦被解密出来,会非常危险。

    # 处理 JWT 令牌

    虽然 jwt-go 库能够快捷地处理 JWT 令牌相关的行为,但是仍需要根据项目特性对其进行设计。简单来讲,就是组合其提供的 API,设计鉴权场景。

    打开 pkg/app 并创建 jwt.go 文件,写下如下代码:

    package app
    
    import (
    	"code.coolops.cn/blog_services/global"
    	"code.coolops.cn/blog_services/pkg/util"
    	"github.com/dgrijalva/jwt-go"
    	"time"
    )
    
    type Claims struct {
    	AppKey    string `json:"app_key"`
    	AppSecret string `json:"app_secret"`
    	jwt.StandardClaims
    }
    
    func GetJWTSecret() []byte {
    	return []byte(global.JWTSetting.Secret)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    这里主要涉及 JWT 的一些基本属性。第一个是 GetJWTSecret 方法,它用于获取该项目的 JWT Secret,目前我们使用的是默认配置的 Secret。第二个是 Claims 结构体,它分为两大块:第一块是嵌入的 AppKey 和 AppSecret,用于我们自定义的认证信息;第二块是 jwt.StandardClaims 结构体,它是在 jwt-go 库中预定义的,涉及的字段如下:

    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
    2
    3
    4
    5
    6
    7
    8
    9

    这些字段都是非强制性的,但官方建议使用预定义权利要求,能够提供一组有用的、可相互操作的约定。

    然后写下如下代码:

    // 生成Token
    func GenerateToken(appKey, appSecret string) (string, error) {
    	nowTime := time.Now()
    	expireTime := nowTime.Add(global.JWTSetting.Expire)
    	claims := Claims{
    		AppKey:    util.EncodeMD5(appKey),
    		AppSecret: util.EncodeMD5(appSecret),
    		StandardClaims: jwt.StandardClaims{
    			ExpiresAt: expireTime.Unix(),
    			Issuer:    global.JWTSetting.Issuer,
    		},
    	}
    	tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    	token, err := tokenClaims.SignedString(GetJWTSecret())
    	return token, err
    }
    
    // 校验Token
    func ParseToken(token string) (*Claims, error) {
    	tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    		return GetJWTSecret(), nil
    	})
    	if tokenClaims != nil {
    		claims, ok := tokenClaims.Claims.(*Claims)
    		if ok && tokenClaims.Valid {
    			return claims, nil
    		}
    	}
    	return nil, err
    }
    
    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

    GenerateToken 方法的主要功能是生成 JWT Token,其流程是根据客户端传入的 AppKey 和 AppSecret,以及在项目配置中设置的签发者(Issuer)和过期时间(ExpiresAt),根据指定的算法生成签名后的 Token。这其中涉及两个内部方法,具体如下。

    • jwt.NewWithClaims:根据 Claims 结构体创建 Token 实例。它一共包含两个形参,第一个形参是 SigningMethod,其包含 SigningMethodHS256、SigningMethodHS384 和 SigningMethodHS512 三种 crypto.Hash 加密算法的方案。第二个形参是 Claims,主要用于传递用户预定义的一些权利要求,以便后续的加密、校验等行为。
    • tokenClaims.SignedString:生成签名字符串,根据传入的 Secret,进行签名并返回标准的 Token。

    ParseToken 方法的主要的功能是解析和校验 Token,其流程是解析传入的 Token,然后根据 Claims 的相关属性要求进行校验。这其中涉及两个内部方法,具体如下。

    • ParseWithClaims:用于解析鉴权的声明,方法内部是具体的解码和校验的过程,最终返回*Token。
    • Valid:验证基于时间的声明,如过期时间(ExpiresAt)、签发者(Issuer)、生效时间(Not Before)。需要注意的是,即便在令牌中没有任何声明,也仍然被认为是有效的。

    至此我们介绍了 JWT 令牌的生成、解析和校验的方法,在后续应用中间件时,会对其进行调用,使其能够在应用程序中将一整套的动作串联起来。

    # 获取 JWT 令牌

    # 新增 Model 方法

    为了获取令牌信息,需要新增 Model 方法,如下:

    func (a Auth) Get(db *gorm.DB) (Auth, error) {
    	var auth Auth
    	db = db.Where(
    		"app_key = ? AND app_secret = ? AND is_del = ?",
    		a.AppKey, a.AppSecret, 0,
    	)
    	err := db.First(&auth).Error
    	if err != nil {
    		return auth, err
    	}
    	return auth, nil
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    上述方法主要用于服务器端在获取客户端传入的 app_key 和 app_secret 后,根据传入的认证信息进行验证,以此判别是否真的存在这样一条数据。

    # 新增 dao 方法

    package dao
    
    import "code.coolops.cn/blog_services/internal/model"
    
    func (d *Dao) GetAuth(appKey, appSecret string) (model.Auth, error) {
    	auth := model.Auth{
    		AppKey:    appKey,
    		AppSecret: appSecret,
    	}
    	return auth.Get(d.engine)
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    # 新增 service 方法

    新增 service 方法,对基本逻辑进行处理。

    package service
    
    import "errors"
    
    type AuthRequest struct {
    	AppKey    string `form:"app_key" binding:"required"`
    	AppSecret string `form:"app_secret" binding:"required"`
    }
    
    func (s *Service) CheckAuth(param *AuthRequest) error {
    	auth, err := s.dao.GetAuth(param.AppKey, param.AppSecret)
    	if err != nil {
    		return err
    	}
    	if auth.ID > 0 {
    		return nil
    	}
    	return errors.New("auth info does not exist")
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    在上述代码中,我们声明了 AuthRequest 结构体,用于接口入参的校验。AppKey 和 AppSecret 都设置为必填项。在 CheckAuth 方法中,我们使用客户端传入的认证信息作为筛选条件获取数据行,根据是否取到认证信息 ID 判定认证信息 ID 是否存在。

    # 新增路由方法

    package api
    
    import (
    	"code.coolops.cn/blog_services/global"
    	"code.coolops.cn/blog_services/internal/service"
    	"code.coolops.cn/blog_services/pkg/app"
    	"code.coolops.cn/blog_services/pkg/errcode"
    	"github.com/gin-gonic/gin"
    )
    
    func GetAuth(ctx *gin.Context) {
    	param := service.AuthRequest{
    		AppKey: ctx.GetHeader("app_key"),
    		AppSecret: ctx.GetHeader("app_secret"),
    	}
    	response := app.Response{Ctx: ctx}
    	valid, errs := app.BindAndValid(ctx, &param)
    	if !valid {
    		global.Logger.ErrorF("app.BindAndValid err: %v", errs)
    		response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
    		return
    	}
    
    	svc := service.NewService(ctx)
    	err := svc.CheckAuth(&param)
    	if err != nil {
    		global.Logger.ErrorF("svc.CheckAuth err: %v", err)
    		response.ToErrorResponse(errcode.UnauthorizedAuthNotExist)
    		return
    	}
    
    	token, err := app.GenerateToken(param.AppKey, param.AppSecret)
    	if err != nil {
    		global.Logger.ErrorF("app.GenerateToken err: %v", err)
    		response.ToErrorResponse(errcode.UnauthorizedTokenGenerate)
    		return
    	}
    	response.ToResponse(gin.H{
    		"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
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

    这部分的主要逻辑是在校验及获取入参后,通过 Query 获取的 app_key 和 app_secrect 进行数据库查询,检查认证信息是否存在,若存在则进行 Token 的生成并返回。

    然后新增路由:

    r.POST("/auth", api.GetAuth)
    
    1

    # 接口验证

    启动服务,使用 postman 进行验证,如下:

    19d599f4726da18d130fdf792f11de3e MD5

    # 处理应用中间件

    # 编写 JWT 中间件

    虽然能获取 Token 了,但是对于其他的业务接口,它还没产生任何作用,应如何将整个应用流程串联起来呢?此时就涉及特定类别的接口统一处理了,即选择应用中间件的方式。打开 internal/middleware,新建 jwt.go 文件,写入如下代码:

    package middleware
    
    import (
    	"code.coolops.cn/blog_services/pkg/app"
    	"code.coolops.cn/blog_services/pkg/errcode"
    	"github.com/dgrijalva/jwt-go"
    	"github.com/gin-gonic/gin"
    )
    
    func JWT() gin.HandlerFunc  {
    	return func(ctx *gin.Context) {
    		var (
    			token string
    			ecode = errcode.Success
    		)
    		if s,exist:=ctx.GetQuery("token");exist{
    			token = s
    		}else {
    			token = ctx.GetHeader("token")
    		}
    		if token == ""{
    			ecode = errcode.InvalidParams
    		}else {
    			_, err := app.ParseToken(token)
    			if err != nil {
    				switch err.(*jwt.ValidationError).Errors {
    				case jwt.ValidationErrorExpired:
    					ecode = errcode.UnauthorizedTokenTimeout
    				default:
    					ecode = errcode.UnauthorizedTokenError
    				}
    			}
    		}
    		if ecode != errcode.Success{
    			response := app.NewResponse(ctx)
    			response.ToErrorResponse(ecode)
    			ctx.Abort()
    			return
    		}
    		ctx.Next()
    	}
    }
    
    
    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

    在上述代码中,我们通过 GetHeader 方法从 Header 中获取 token 参数,并调用 ParseToken 对其进行解析,再根据返回的错误类型进行断言,其返回的错误类型所包含的场景如下:

    const (
    	ValidationErrorMalformed        uint32 = 1 << iota // Token is malformed
    	ValidationErrorUnverifiable                        // Token could not be verified because of signing problems
    	ValidationErrorSignatureInvalid                    // Signature validation failed
    
    	// Standard Claim validation errors
    	ValidationErrorAudience      // AUD validation failed
    	ValidationErrorExpired       // EXP validation failed
    	ValidationErrorIssuedAt      // IAT validation failed
    	ValidationErrorIssuer        // ISS validation failed
    	ValidationErrorNotValidYet   // NBF validation failed
    	ValidationErrorId            // JTI validation failed
    	ValidationErrorClaimsInvalid // Generic claims validation error
    )
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 接入 JWT 中间件

    在编写完 JWT 的中间件后,我们需要将其接入应用流程中。需要注意的是,并非所有的接口都需要用到 JWT 中间件,因此我们需要利用 gin 中的分组路由的概念,只对 apiv1 的路由分组进行 JWT 中间件的引用。也就是说,只有 apiv1 路由分组里的路由方法会受此中间件的约束,代码如下:

    apiv1 := r.Group("/api/v1")
    	apiv1.Use(middleware.JWT())
    		{
    		apiv1.POST("/tags", tag.Create)
    		apiv1.DELETE("/tags/:id", tag.Delete)
    		apiv1.PUT("/tags/:id", tag.Update)
    		apiv1.PATCH("/tags/:id/state", tag.Update)
    		apiv1.GET("/tags", tag.List)
    	}
    	return r
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 验证

    (1)没加 Token

    3cac9bc35b163d51ccf3bb1a54fc69cb MD5

    (2)加入错误 Token

    af1245217738a75fc72dfea54647af94 MD5

    (3)加入正确 Token

    e6d99ff36aa25cdd7cf9a53c1dade3b5 MD5

    # JWT 反向解密

    JWT 令牌的内容是非严格加密的。也就是说,对 JWT 令牌机制有一定了解的人可以进行反向解密.

    调用接口/auth 获取到 Token,如下:

    {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBfa2V5IjoiYjdkZjVhOTMyYzk4OTBlMzE2OTZhNzFkMmNiNTJhMTIiLCJhcHBfc2VjcmV0IjoiMDZjMjE5ZTViYzgzNzhmM2E4YTNmODNiNGI3ZTQ2NDkiLCJleHAiOjE2MTI0Mjk2OTMsImlzcyI6ImJsb2dfc2VydmljZSJ9.Q6OH9wPSkO-V7smeo6FEaHRPSMyvhYjQVL4oE3rqW5Q"
    }
    
    1
    2
    3

    针对新获取的 Token 值,手动复制中间一段(即 Payload),编写一个测试 Demo 进行 base64 的解码。Demo 代码如下:

    package main
    
    import (
    	"encoding/base64"
    	"fmt"
    )
    
    func main() {
    	paylod, _ := base64.StdEncoding.DecodeString("eyJhcHBfa2V5IjoiYjdkZjVhOTMyYzk4OTBlMzE2OTZhNzFkMmNiNTJhMTIiLCJhcHBfc2VjcmV0IjoiMDZjMjE5ZTViYzgzNzhmM2E4YTNmODNiNGI3ZTQ2NDkiLCJleHAiOjE2MTI0MzA4ODYsImlzcyI6ImJsb2dfc2VydmljZSJ9")
    	fmt.Println(string(paylod))
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    运行后得到如下代码:

    {"app_key":"b7df5a932c9890e31696a71d2cb52a12","app_secret":"06c219e5bc8378f3a8a3f83b4b7e4649","exp":1612430886,"iss":"blog_service"}
    
    1

    可以看到,假设有人拦截到 Token 后,是可以通过解密该 Token 来获取 Payload 信息的。也就是说,在 Payload 中不应该明文存储重要的信息,若一定要存,则必须进行不可逆加密,以确保信息的安全性。

    过期时间是存储在 Payload 中的,也就是说,JWT 令牌一旦签发,在没有做特殊逻辑的情况下,过期时间是不可以再度变更的,因此请务必根据实际项目情况进行设计。

    文章来自《Go 语言编程之旅》

    作者:乔克

    本文链接:https://jokerbai.com

    版权声明:本博客所有文章除特别声明外,均采用 署名-非商业性-相同方式共享 4.0 国际 (CC-BY-NC-SA-4.0) 许可协议。转载请注明出处!

    上次更新: 2025/07/19, 09:17:41
    Swagger
    常见的应用中间件

    ← Swagger 常见的应用中间件→

    最近更新
    01
    使用 Generic Webhook Trigger 触发 Jenkins 多分支流水线自动化构建
    07-19
    02
    使用Zadig从0到1实现持续交付平台
    07-19
    03
    基于Jira的运维发布平台
    07-19
    更多文章>
    Theme by Vdoing | Copyright © 2019-2025 乔克 | MIT License | 渝ICP备20002153号 |
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式