Go语言jwt跨域鉴权的实现实例

 更新时间:2025年11月21日 10:09:57   作者:PPPsych  
本文详细的介绍了JWT的基本概念,包括JWT的组成部分和签名方法,及生成和解析JWT和设置认证中间件,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

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

jwt介绍

官网:https://jwt.io/

JWT 到底是什么?

简而言之,jwt是一个签名的 JSON 对象,可以做一些有用的事情(例如,身份验证)。它通常用于BearerOauth 2 中的令牌。令牌由三部分组成,由.'s 分隔。前两部分是 JSON 对象,已经过base64url编码。最后一部分是签名,以同样的方式编码。

第一部分称为标题。它包含验证最后一部分签名的必要信息。例如,使用哪种加密方法进行签名以及使用了什么密钥。

中间的部分是有趣的部分。它称为声明,包含您关心的实际内容。有关保留密钥和添加自己的正确方法的信息。

JWT 和 OAuth

值得一提的是,OAuth 和 JWT 不是一回事。JWT 令牌只是一个签名的 JSON 对象。它可以在任何有用的地方使用。但是,存在一些混淆,因为 JWT 是 OAuth2 身份验证中最常见的不记名令牌类型。

不用太深入,这里是对这些技术交互的描述:

  • OAuth 是一种允许身份提供者与用户登录的服务分开的协议。例如,每当您使用 Facebook 登录不同的服务(Yelp、Spotify 等)时,您都在使用 OAuth。
  • OAuth 定义了几个用于传递身份验证数据的选项。一种流行的方法称为“不记名令牌”。不记名令牌只是一个字符串,只能由经过身份验证的用户持有。因此,只需出示此令牌即可证明您的身份。您可能可以从这里得出为什么 JWT 可能会成为一个好的不记名令牌。
  • 因为不记名令牌用于身份验证,所以对它们保密很重要。这就是使用不记名令牌的交易通常通过 SSL 发生的原因。

选择签名方法

有几种可用的签名方法,您可能应该花时间了解各种选项,然后再选择一种。主要的设计决策很可能是对称的还是非对称的。

对称签名方法(例如 HSA)仅使用一个密钥。这可能是最简单的签名方法,因为任何[]byte都可以用作有效的秘密。它们在计算上的使用速度也略快一些,尽管这很少有关系。当令牌的生产者和消费者都受信任,甚至是同一个系统时,对称签名方法效果最好。由于相同的密钥用于签名和验证令牌,因此您无法轻松分发密钥以进行验证。

非对称签名方法(例如 RSA)使用不同的密钥来签名和验证令牌。这使得使用私钥生成令牌成为可能,并允许任何消费者访问公钥进行验证。

签名方法和密钥类型

jwt-go库支持 JWT 的解析和验证以及生成和签名。当前支持的签名算法是 HMAC SHA、RSA、RSA-PSS 和 ECDSA

每个签名方法都需要不同的对象类型作为其签名密钥。有关详细信息,请参阅软件包文档。以下是最常见的:

  • HMAC 签名方法( HS256, HS384, HS512)[]byte需要用于签名和验证的值
  • RSA 签名方法( , RS256, RS384)RS512期望*rsa.PrivateKey用于签名和*rsa.PublicKey验证
  • ECDSA 签名方法( , ES256, ES384)ES512期望*ecdsa.PrivateKey用于签名和*ecdsa.PublicKey验证

安装jwt

go get github.com/dgrijalva/jwt-go

简单使用

生成JWT

type MyClaims struct {
	//除了满足下面的Claims,还需要以下用户信息
	Username string `json:"username"`
	Password string `json:"password"`
	//jwt中标准的Claims
	jwt.StandardClaims
}

// 使用指定的 secret 签名声明一个 key ,便于后续获得完整的编码后的字符串token
var key = []byte("secret")

//GenToken 生成token的方法
func GenToken(username string, password string) (string, error) {
	//创建一个我们自己的声明
	c := MyClaims{
		username, //自定义字段
		password,
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(time.Hour * 2).Unix(), //过期时间
			Issuer:    "Psych",                              //签发人
		},
	}

	//使用指定的签名方法创建签名对象
	//这里使用HS256加密算法
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)

	//注意这个地方的 key 一定要是字节切片不能是字符串
	return token.SignedString(key)
}

解析jwt

//ParseToken 解析token的方法
func ParseToken(tokenString string) (*MyClaims, error) {
	//解析token
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{},
		func(token *jwt.Token) (i interface{}, err error) {
			return key, nil
		})
	if err != nil {
		return nil, err
	}

	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { //校验token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

测试:生成token并解析token

func main() {
	//生成token
	token, err := GenToken("Psych", "123456")
	if err != nil {
		panic(err)
	}
	fmt.Printf("token: %v\n", token)

	fmt.Println("----------------------")

	//解析token
	parseToken, err := ParseToken(token)
	if err != nil {
		panic(err)
	}
	fmt.Printf("parseToken.UserName: %v\n", parseToken.Username)
	fmt.Printf("parseToken.Password: %v\n", parseToken.Password)
}

运行结果:

[Running] go run "e:\golang开发学习\go_pro\main.go"
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IlBzeWNoIiwicGFzc3dvcmQiOiIxMjM0NTYiLCJleHAiOjE2NjI1MzQ5MTEsImlzcyI6IlBzeWNoIn0.hsqMq08NB2uz5OSMXB5KT-77WlirvcSX8kgtyWIVEP0
----------------------
parseToken.UserName: Psych
parseToken.Password: 123456

[Done] exited with code=0 in 1.572 seconds

jwt在项目中的使用

第一步:在一个go文件中,写生成jwt和解析jwt的方法,方便调用

package jwt

import (
	"errors"
	"time"

	"github.com/dgrijalva/jwt-go"
)

//载荷
type Customclaims struct {
	Empname string `json:"empname"`
	Phone   string `json:"phone"`
	Role    string `json:"role"`
	jwt.StandardClaims
}

// MyClaims 自定义声明结构体并内嵌jwt.StandardClaims
// jwt包自带的jwt.StandardClaims只包含了官方字段
// 我们这里需要额外记录一个UserID字段,所以要自定义结构体
// 如果想要保存更多信息,都可以添加到这个结构体中
type MyClaims struct {
	UserName string `json:"user_name"`
	jwt.StandardClaims
}

var mySecret = []byte("呼哧呼哧")

func keyFunc(_ *jwt.Token) (i interface{}, err error) {
	return mySecret, nil
}

const TokenExpireDuration = time.Hour * 24 * 365

// GenToken 生成access token 和 refresh token
func GenToken(userName string) (aToken, rToken string, err error) {
	// 创建一个我们自己的声明
	c := MyClaims{
		userName, // 自定义字段
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 过期时间
			Issuer:    "personal-blog",                            // 签发人
		},
	}
	// 加密并获得完整的编码后的字符串token
	aToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, c).SignedString(mySecret)

	// refresh token 不需要存任何自定义数据
	rToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{
		ExpiresAt: time.Now().Add(time.Second * 30).Unix(), // 过期时间
		Issuer:    "personal-blog",                         // 签发人
	}).SignedString(mySecret)
	// 使用指定的secret签名并获得完整的编码后的字符串token
	return
}

// ParseToken 解析JWT
func ParseToken(tokenString string) (claims *MyClaims, err error) {
	// 解析token
	var token *jwt.Token
	claims = new(MyClaims)
	token, err = jwt.ParseWithClaims(tokenString, claims, keyFunc)
	if err != nil {
		return
	}
	if !token.Valid { // 校验token
		err = errors.New("invalid token")
	}
	return
}

// RefreshToken 刷新AccessToken
func RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {

	// 从旧access token中解析出claims数据	解析出payload负载信息
	var claims MyClaims
	_, err = jwt.ParseWithClaims(aToken, &claims, keyFunc)

	// 当access token是过期错误 并且 refresh token没有过期时就创建一个新的access token

	return GenToken(claims.UserName)

}

第二步:登陆的时候生成token

//登录
func LoginHandler(c *gin.Context) {
	// 1.获取请求参数 2.校验数据有效性
	var L models.Users
	if err := c.ShouldBindJSON(&L); err != nil {
		zap.L().Error("invalid params", zap.Error(err))
		ResponseErrorWithMsg(c, CodeInvalidParams, err.Error())
		return
	}
	var my models.Update_my
	//用户登录
	if err, a := mysql.Login(&L); err != nil {
		zap.L().Error("mysql.Login(&u) failed", zap.Error(err))
		ResponseError(c, CodeInvalidPassword)
		return
	} else {
		my = a
	}

	// 生成Token
	aToken, rToken, _ := jwt.GenToken(L.UserName)
	ResponseSuccess(c, gin.H{
		"accessToken":  aToken,
		"refreshToken": rToken,
		"username":     L.UserName,
		"role":         L.Role,
		"realname":     my.Realname,
		"phone_number": my.PhoneNumber,
		"id_number":    my.IDNumber,
	})
}

第三步:在控制器中写一个go文件,JWTAuthMiddleware基于JWT的认证中间件

package controller

import (
	"Fever_backend/dao/mysql"
	"Fever_backend/pkg/jwt"
	"errors"
	"fmt"
	"go.uber.org/zap"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
)

const (
	ContextUserNameKey = "userName"
)

var (
	ErrorUserNotLogin = errors.New("当前用户未登录")
)

// 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 == "" {
			ResponseErrorWithMsg(c, CodeInvalidToken, "请求头缺少Auth Token")
			c.Abort()
			return
		}
		// 按空格分割
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			ResponseErrorWithMsg(c, CodeInvalidToken, "Token格式不对")
			c.Abort()
			return
		}
		// parts[1]是获取到的tokenString,我们使用之前定义好的解析JWT的函数来解析它
		mc, err := jwt.ParseToken(parts[1])
		if err != nil {
			fmt.Println(err)
			ResponseError(c, CodeInvalidToken)
			c.Abort()
			return
		}
		// 将当前请求的username信息保存到请求的上下文c上
		c.Set(ContextUserNameKey, mc.UserName)
		c.Next() // 后续的处理函数可以用过c.Get("userID")来获取当前请求的用户信息
	}
}

第四部:登陆验证token

//这里只是举了个例,具体业务需要具体分析
//登录验证token
	v1.Use(controller.JWTAuthMiddleware())
	{
		//修改密码
		v1.POST("/change_password", controller.ChangePasswordHandler)
		//加权限
		v1.POST("/add_casbin", controller.AddCasbin)
	}

到此这篇关于Go语言jwt跨域鉴权的实现实例的文章就介绍到这了,更多相关Go语言jwt跨域鉴权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang获取prometheus数据(prometheus/client_golang包)

    golang获取prometheus数据(prometheus/client_golang包)

    本文主要介绍了使用Go语言的prometheus/client_golang包来获取Prometheus监控数据,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03
  • GoLand编译带有构建标签的程序思路详解

    GoLand编译带有构建标签的程序思路详解

    这篇文章主要介绍了GoLand编译带有构建标签的程序,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • 解决Golang在Web开发时前端莫名出现的空白换行

    解决Golang在Web开发时前端莫名出现的空白换行

    最近在使用Go语言开发Web时,在前端莫名出现了空白换行,找了网上的一些资料终于找到了解决方法,现在分享给大家,有需要的可以参考。
    2016-08-08
  • golang基于websocket通信tcp keepalive研究记录

    golang基于websocket通信tcp keepalive研究记录

    这篇文章主要为大家介绍了golang基于websocket通信tcp keepalive研究记录,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • GoLang中panic与recover函数以及defer语句超详细讲解

    GoLang中panic与recover函数以及defer语句超详细讲解

    这篇文章主要介绍了GoLang的panic、recover函数,以及defer语句,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-01-01
  • 让Go反射变快的方法实例探究

    让Go反射变快的方法实例探究

    反射允许你在运行时获得有关 Go 类型的信息,如果你曾经愚蠢地尝试编写 json.Unmarshal 之类的新版本,本文将探讨的就是如何使用反射来填充结构体值
    2024-01-01
  • go如何删除字符串中的部分字符

    go如何删除字符串中的部分字符

    这篇文章主要介绍了go删除字符串中的部分字符操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • Go语言针对Map的11问你知道几个?

    Go语言针对Map的11问你知道几个?

    Go Map 的 11 连问,你顶得了嘛?这篇文章小编为大家准备了 Go 语言 Map 的 11 连问,相信大家看完肯定会有帮助的,感兴趣的小伙伴可以收藏一波
    2023-05-05
  • 详解Go语言中for range的

    详解Go语言中for range的"坑"

    这篇文章主要介绍了详解Go语言中for range的"坑",文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Golang嵌入资源文件实现步骤详解

    Golang嵌入资源文件实现步骤详解

    在应用程序中附带代码以外的其他资源可能会很有用,常用的实现方法是嵌入对象或数据。在数据库中存储数据应用中,需要定义schema,在应用启动时创建表,但如果找不到schema文件呢?Go1.16提供embed包让实现变得简单,之前很多第三方包实现类似功能
    2023-01-01

最新评论