目录
一、jwt基础
二、jwt签名与验签的算法
三、定义MyCustomClaims 与jwt各个字段的含义
四、golang的HS256签名与验签
五、golang的RS256签名与验签
六、登录与中间件
jwt由三部分构成,header+payload+signature,由两个"."进行分割。
其中header由base64编码,可以直接看。base64加解密
1.对称式签名
signedString=SHA256(header+payload+key)
2.非对称式签名
signedString=SHA256(header+payload)
cipherText=RSAencrypt(signedString,privateKey)
签名时,使用cipherText作为signature
验签时,使用公钥先解出signedString,然后在SHA256(header+payload),对照两个signedString。
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 |
|
标准字段, jwt 签名方, 表示是谁签发的这个 jwt | |
Subject | Tom |
|
|
Audience | jwt.ClaimStrings{"Android_APP", "IOS_APP"} | 标准字段, 表示jwt 签发给谁, 比如后端某个服务(Auth_Server)签发给客户端(Android_APP, IOS_APP)使用 | |
ExpiresAt |
|
标准字段, jwt 过期时间点 | |
NotBefore |
|
标准字段, jwt 最早的有效时间点, 早于这个时间点无效 | |
IssuedAt |
|
标准字段, jwt 的签发时间点 | |
ID | 随机数 | 标准字段, jwt的ID, 尽量唯一, 我理解为类似于在Hash之前加盐值, 更加防碰撞 |
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)
}
使用生成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")
}