Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go语言jwt跨域鉴权

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

作者: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 身份验证中最常见的不记名令牌类型。

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

选择签名方法

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

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

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

签名方法和密钥类型

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

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

安装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跨域鉴权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文