golang jwt

目录

一、jwt基础

二、jwt签名与验签的算法

三、定义MyCustomClaims 与jwt各个字段的含义

四、golang的HS256签名与验签

五、golang的RS256签名与验签

六、登录与中间件


一、jwt基础

jwt由三部分构成,header+payload+signature,由两个"."进行分割。

其中header由base64编码,可以直接看。base64加解密

二、jwt签名与验签的算法

1.对称式签名

signedString=SHA256(header+payload+key)

2.非对称式签名

signedString=SHA256(header+payload)

cipherText=RSAencrypt(signedString,privateKey)

签名时,使用cipherText作为signature

验签时,使用公钥先解出signedString,然后在SHA256(header+payload),对照两个signedString。

三、定义MyCustomClaims 与jwt各个字段的含义

type User struct {
	UserID     int
	Username   string
	GrantScope string
}

type MyCustomClaims struct {
	User
	jwt.RegisteredClaims
}
字段 含义
UserId 000001 自定义字段, 用户ID, 表示这个 jwt 作用于特定用户
UserName Tom 自定义字段, 用户名, 表示这个 jwt 作用于特定用户
GrantScope read_user_info 自定义字段, 授权范围, 标识这个 jwt 能够干啥
Issuer
Auth_Server
标准字段, jwt 签名方, 表示是谁签发的这个 jwt
Subject Tom
标准字段, 表示这个 jwt 作用对象, 在这里与 Username 等效, 再写一遍方便理解
Audience jwt.ClaimStrings{"Android_APP", "IOS_APP"} 标准字段, 表示jwt 签发给谁, 比如后端某个服务(Auth_Server)签发给客户端(Android_APP, IOS_APP)使用
ExpiresAt
jwt.NewNumericDate(time.Now().Add(time.Hour))
标准字段, jwt 过期时间点
NotBefore
jwt.NewNumericDate(time.Now().Add(time.Hour))
标准字段, jwt 最早的有效时间点, 早于这个时间点无效
IssuedAt
jwt.NewNumericDate(time.Now().Add(time.Hour))
标准字段, jwt 的签发时间点
ID 随机数 标准字段, jwt的ID, 尽量唯一, 我理解为类似于在Hash之前加盐值, 更加防碰撞

四、golang的HS256签名与验签

package main

import (
	"errors"
	"fmt"
	"math/rand"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

type User struct {
	UserID     int
	Username   string
	GrantScope string
}

type MyCustomClaims struct {
	User
	jwt.RegisteredClaims
}

// 签名密钥
const sign_key = "hello world"

// 随机字符串
var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randStr(str_len int) string {
	rand_bytes := make([]rune, str_len)
	for i := range rand_bytes {
		rand_bytes[i] = letters[rand.Intn(len(letters))]
	}
	return string(rand_bytes)
}

func generateTokenUsingHs256() (string, error) {
	claim := MyCustomClaims{
		User: User{
			UserID:     000001,
			Username:   "Tom",
			GrantScope: "read_user_info",
		},
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "Auth_Server",                                   // 签发者
			Subject:   "Tom",                                           // 签发对象
			Audience:  jwt.ClaimStrings{"Android_APP", "IOS_APP"},      //签发受众
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),   //过期时间
			NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second)), //最早使用时间
			IssuedAt:  jwt.NewNumericDate(time.Now()),                  //签发时间
			ID:        randStr(10),                                     // wt ID, 类似于盐值
		},
	}
	token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claim).SignedString([]byte(sign_key))
	return token, err
}

func parseTokenHs256(token_string string) (*MyCustomClaims, error) {
	token, err := jwt.ParseWithClaims(token_string, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(sign_key), nil //返回签名密钥
	})
	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, errors.New("claim invalid")
	}

	claims, ok := token.Claims.(*MyCustomClaims)
	if !ok {
		return nil, errors.New("invalid claim type")
	}

	return claims, nil
}

func main() {

	token, err := generateTokenUsingHs256()
	if err != nil {
		panic(err)
	}
	fmt.Println("Token = ", token)

	time.Sleep(time.Second * 1)

	my_claim, err := parseTokenHs256(token)
	if err != nil {
		panic(err)
	}
	fmt.Printf("my claim = %+v", my_claim)
}

五、golang的RS256签名与验签

使用生成rsa公私对,选择512bit + PKCS#1

package main

import (
	"crypto/rsa"
	"crypto/x509"
	"encoding/pem"
	"errors"
	"fmt"
	"math/rand"
	"time"

	"github.com/golang-jwt/jwt/v5"
)

type MyCustomClaims struct {
	UserID     int
	Username   string
	GrantScope string
	jwt.RegisteredClaims
}

// 随机字符串
var letters = []rune("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randStr(str_len int) string {
	rand_bytes := make([]rune, str_len)
	for i := range rand_bytes {
		rand_bytes[i] = letters[rand.Intn(len(letters))]
	}
	return string(rand_bytes)
}

// pkcs1
func parsePriKeyBytes(buf []byte) (*rsa.PrivateKey, error) {
	p := &pem.Block{}
	p, buf = pem.Decode(buf)
	if p == nil {
		return nil, errors.New("parse key error")
	}
	return x509.ParsePKCS1PrivateKey(p.Bytes)
}

const pri_key = `-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBAJS/Q6xhgqzycc5KEPtQrI8Bf8WakGmpum3zutno28qASP1Y0BAs
WTEVNtrxuVXb9R3ebcrI5lYnkxR5DobffWkCAwEAAQJAdGptMJDwkSL+5xEY0ViG
dTYbJjCeLdRk0IEdEEcrHgSvn6YRvlQ5lsSMSFxRscCO40XMq9OofPzV6RD1kWgQ
AQIhAMXwMHC3qXmCcsxcyJ3yjCjRzSmA+PQLkijX2Z4nFQ/pAiEAwGEohnQXgdDc
vGauBJQc3clb33s4y+Woi86WEAkUEYECIHxCWsaIJfZH9DVjEfZF68M8YiVp99+M
3AaT6uOj+U7xAiEAk6n/8TQq1vn6dKJb8CfAAH0Oh/uNHPSq6qUniidtwAECIQCW
cPfmrnitlRFkmV9/c5Waln9VXpj0e0sU7Zj4GqOOSg==
-----END RSA PRIVATE KEY-----
`

const pub_key = `-----BEGIN RSA PUBLIC KEY-----
MEgCQQCUv0OsYYKs8nHOShD7UKyPAX/FmpBpqbpt87rZ6NvKgEj9WNAQLFkxFTba
8blV2/Ud3m3KyOZWJ5MUeQ6G331pAgMBAAE=
-----END RSA PUBLIC KEY-----
`

func generateTokenUsingRS256() (string, error) {
	claim := MyCustomClaims{
		UserID:     000001,
		Username:   "Tom",
		GrantScope: "read_user_info",
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "Auth_Server",                                   // 签发者
			Subject:   "Tom",                                           // 签发对象
			Audience:  jwt.ClaimStrings{"Android_APP", "IOS_APP"},      //签发受众
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),   //过期时间
			NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second)), //最早使用时间
			IssuedAt:  jwt.NewNumericDate(time.Now()),                  //签发时间
			ID:        randStr(10),                                     // jwt ID, 类似于盐值
		},
	}
	rsa_pri_key, err := parsePriKeyBytes([]byte(pri_key))
	token, err := jwt.NewWithClaims(jwt.SigningMethodRS256, claim).SignedString(rsa_pri_key)
	return token, err
}

func parsePubKeyBytes(pub_key []byte) (*rsa.PublicKey, error) {
	block, _ := pem.Decode(pub_key)
	if block == nil {
		return nil, errors.New("block nil")
	}
	pub_ret, err := x509.ParsePKCS1PublicKey(block.Bytes)
	if err != nil {
		return nil, errors.New("x509.ParsePKCS1PublicKey error")
	}

	return pub_ret, nil
}

func parseTokenRs256(token_string string) (*MyCustomClaims, error) {
	token, err := jwt.ParseWithClaims(token_string, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		pub, err := parsePubKeyBytes([]byte(pub_key))
		if err != nil {
			fmt.Println("err = ", err)
			return nil, err
		}
		return pub, nil
	})
	if err != nil {
		return nil, err
	}

	if !token.Valid {
		return nil, errors.New("claim invalid")
	}

	claims, ok := token.Claims.(*MyCustomClaims)
	if !ok {
		return nil, errors.New("invalid claim type")
	}

	return claims, nil
}

func main() {

	token, err := generateTokenUsingRS256()
	if err != nil {
		panic(err)
	}
	fmt.Println("Token = ", token)

	time.Sleep(time.Second * 2)

	my_claim, err := parseTokenRs256(token)
	if err != nil {
		panic(err)
	}
	fmt.Println("my claim = ", my_claim)

}

六、登录与中间件

package main

import (
	"strconv"
	"test/jwt/hs256"

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

type LoginReq struct {
	Username string `json:"username"`
	Password string `json:"password"`
}

func Login(ctx *gin.Context) {
	var req LoginReq
	err := ctx.ShouldBindJSON(&req)
	if err != nil {
		ctx.String(200, "param error")
		return
	}
	if req.Username != "zhangsan" || req.Password != "123" {
		ctx.String(200, "auth error")
		return
	}
	user := hs256.User{UserID: 1, Username: req.Username, GrantScope: "xx"}
	token, err := hs256.GenerateTokenUsingHs256(user)
	if err != nil {
		ctx.String(200, "unknown error")
		return
	}
	ctx.JSON(200, gin.H{
		"id":       1,
		"username": req.Username,
		"token":    token,
	})
}

func UserInfo(ctx *gin.Context) {
	userid := ctx.Param("userid")
	username := ctx.Param("username")
	ctx.JSON(200, gin.H{
		"userid":   userid,
		"username": username,
	})
}

func Auth(ctx *gin.Context) {
	token := ctx.Request.Header.Get("Authorization")
	if len(token) == 0 {
		ctx.String(401, "No Authorization")
		ctx.Abort()
	}
	claim, err := hs256.ParseTokenHs256(token)
	if err != nil {
		ctx.String(401, "No Authorization")
		ctx.Abort()
	}
	ctx.Params = append(ctx.Params, gin.Param{
		Key:   "userid",
		Value: strconv.Itoa(claim.User.UserID),
	})
	ctx.Params = append(ctx.Params, gin.Param{
		Key:   "username",
		Value: claim.User.Username,
	})
	ctx.Next()
}

func main() {
	en := gin.Default()
	en.POST("/login", Login)
	g := en.Group("/user", Auth)
	{
		g.GET("/userinfo", UserInfo)
	}
	en.Run(":8081")
}

你可能感兴趣的:(golang)