学习 Go 中 Jwt 的基本使用
Jwt 是什么
Jwt 是一种用于身份验证和授权的开放标准(RFC 7519)。它通常用于在不同系统之间传递声明(claims),这些声明可以是用户信息、权限等。
推荐阅读:
安装 Jwt
go-jwt 官方仓库- 安装 Jwt 包
bash
go get -u github.com/golang-jwt/jwt/v5
- 导入 Jwt 包
go
import "github.com/golang-jwt/jwt/v5"
默认 claims
默认 claims 包含了一些标准的声明,如 aud
、exp
、iss
等。
go
package main
import (
"errors"
"fmt"
"time"
)
import "github.com/golang-jwt/jwt/v5"
// 用于签名的字符串
var mySigningKey = []byte("sanshi")
var (
TokenValid = errors.New("未知错误")
TokenExpired = errors.New("token已过期")
TokenNotValidYet = errors.New("token尚未激活")
TokenMalformed = errors.New("这不是一个token")
TokenSignatureInvalid = errors.New("无效签名")
TokenInvalid = errors.New("无法处理此token")
)
// getToken 使用默认声明创建jwt
func getToken() (string, error) {
// 创建 Claims
claims := &jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{"sanshi-blog"}, // 受众
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 过期时间
Issuer: "sanshi", // 签发人
}
// 生成token对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 生成签名字符串
return token.SignedString(mySigningKey)
}
// parseToken 解析 token
func parseToken(tokenString string) (jwt.Claims, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
return nil, TokenExpired
case errors.Is(err, jwt.ErrTokenMalformed):
return nil, TokenMalformed
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
return nil, TokenSignatureInvalid
case errors.Is(err, jwt.ErrTokenNotValidYet):
return nil, TokenNotValidYet
default:
return nil, TokenInvalid
}
}
if claims, ok := token.Claims.(jwt.Claims); ok && token.Valid {
return claims, nil
}
return nil, TokenValid
}
func main() {
token, _ := getToken()
fmt.Println(token)
claims, err := parseToken(token)
if err != nil {
panic(err)
}
fmt.Printf("%+v", claims)
}
自定义 claims
当你需要在 token 中存储自定义的信息时,你可以自定义 claims。
go
package main
import (
"errors"
"fmt"
"time"
)
import "github.com/golang-jwt/jwt/v5"
var (
TokenValid = errors.New("未知错误")
TokenExpired = errors.New("token已过期")
TokenNotValidYet = errors.New("token尚未激活")
TokenMalformed = errors.New("这不是一个token")
TokenSignatureInvalid = errors.New("无效签名")
TokenInvalid = errors.New("无法处理此token")
)
type CustomClaims struct {
BaseClaims
BufferTime int64 // 缓存时间可以用于刷新 token 判断使用
jwt.RegisteredClaims
}
type BaseClaims struct {
ID uint
Username string
RoleId uint
}
type JWT struct {
SigningKey []byte
}
func NewJWT() *JWT {
return &JWT{
[]byte("sanshi"),
}
}
// GetToken 使用默认声明创建jwt
func (j *JWT) GetToken() (string, error) {
// 创建 Claims
claims := CustomClaims{
BufferTime: int64(3600 * time.Second),
BaseClaims: BaseClaims{
ID: 1,
Username: "wangLei",
RoleId: 1,
},
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{"sanshi-blog"}, // 受众
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 过期时间
Issuer: "sanshi", // 签发人
},
}
// 生成 token
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(j.SigningKey)
}
func (j *JWT) keyFunc(token *jwt.Token) (any, error) {
return j.SigningKey, nil
}
// ParseToken 解析 token
func (j *JWT) ParseToken(tokenString string) (jwt.Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, j.keyFunc)
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
return nil, TokenExpired
case errors.Is(err, jwt.ErrTokenMalformed):
return nil, TokenMalformed
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
return nil, TokenSignatureInvalid
case errors.Is(err, jwt.ErrTokenNotValidYet):
return nil, TokenNotValidYet
default:
return nil, TokenInvalid
}
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, TokenValid
}
func main() {
j := NewJWT()
token, _ := j.GetToken()
fmt.Println(token)
claims, err := j.ParseToken(token)
if err != nil {
panic(err)
}
fmt.Printf("%+v", claims)
}
无感刷新 token
refresh_token
通过提供一个 refresh_token 来刷新 token。
go
package main
import (
"errors"
"fmt"
"time"
)
import "github.com/golang-jwt/jwt/v5"
var (
TokenValid = errors.New("未知错误")
TokenExpired = errors.New("token已过期")
TokenNotValidYet = errors.New("token尚未激活")
TokenMalformed = errors.New("这不是一个token")
TokenSignatureInvalid = errors.New("无效签名")
TokenInvalid = errors.New("无法处理此token")
tokenExpired = 30 * 60 * time.Second
refreshExpired = 40 * 60 * time.Second
)
type CustomClaims struct {
BaseClaims
jwt.RegisteredClaims
}
type BaseClaims struct {
ID uint
Username string
RoleId uint
}
type JWT struct {
SigningKey []byte
}
func NewJWT() *JWT {
return &JWT{
[]byte("sanshi"),
}
}
// GetToken 使用默认声明创建jwt
func (j *JWT) GetToken() (string, string, error) {
// 创建 Claims
claims := CustomClaims{
BaseClaims: BaseClaims{
ID: 1,
Username: "wangLei",
RoleId: 1,
},
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{"sanshi-blog"}, // 受众
ExpiresAt: jwt.NewNumericDate(time.Now().Add(tokenExpired)), // 过期时间
Issuer: "sanshi", // 签发人
},
}
// 生成 token
aToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(j.SigningKey)
if err != nil {
return "", "", err
}
// 生成 refresh_token
rToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{"wangLei"},
ExpiresAt: jwt.NewNumericDate(time.Now().Add(refreshExpired)),
Issuer: "wangLei",
}).SignedString(j.SigningKey)
return aToken, rToken, err
}
// ParseToken 解析 token
func (j *JWT) ParseToken(tokenString string) (jwt.Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, j.keyFunc)
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
return nil, TokenExpired
case errors.Is(err, jwt.ErrTokenMalformed):
return nil, TokenMalformed
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
return nil, TokenSignatureInvalid
case errors.Is(err, jwt.ErrTokenNotValidYet):
return nil, TokenNotValidYet
default:
return nil, TokenInvalid
}
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, TokenValid
}
func (j *JWT) keyFunc(token *jwt.Token) (any, error) {
return j.SigningKey, nil
}
func (j *JWT) RefreshToken(aToken, rToken string) (newAToken, newRToken string, err error) {
// refresh 无效直接返回
if _, err = jwt.Parse(rToken, j.keyFunc); err != nil {
return
}
// 从旧的 access_token 中解析出 claims 数据
var claims CustomClaims
_, err = jwt.ParseWithClaims(aToken, &claims, j.keyFunc)
// 当 access_token 是过期错误,并且 refresh_token 没有过期 则创建一个新的 access_token
if errors.Is(err, jwt.ErrTokenExpired) {
// 同步再次刷新 refresh_token 可以用来实现永久登录
// 当 refresh_token 过期要求用户重新登录
newAToken, _, err = j.GetToken()
if err != nil {
return
}
return newAToken, rToken, nil
}
return
}
func main() {
j := NewJWT()
aToken, rToken, _ := j.GetToken()
fmt.Println(aToken, rToken)
claims, err := j.ParseToken(aToken)
if err != nil {
panic(err)
}
fmt.Printf("%+v \n", claims)
claims, err = j.ParseToken(rToken)
if err != nil {
panic(err)
}
fmt.Printf("%+v \n", claims)
}
缓冲时间
当 token 快要过期并且在即将过期的一段时间内请求了接口,自动将 token 续期。
go
package main
import (
"errors"
"fmt"
"golang.org/x/sync/singleflight"
"time"
)
import "github.com/golang-jwt/jwt/v5"
var (
TokenValid = errors.New("未知错误")
TokenExpired = errors.New("token已过期")
TokenNotValidYet = errors.New("token尚未激活")
TokenMalformed = errors.New("这不是一个token")
TokenSignatureInvalid = errors.New("无效签名")
TokenInvalid = errors.New("无法处理此token")
ConcurrencyControl = &singleflight.Group{}
tokenExpired = 30 * 60 * time.Second
bufferTime = 40 * 60 * time.Second
testTokenExpired = 60 * 60 * time.Second
)
type CustomClaims struct {
BaseClaims
BufferTime int64 // 缓存时间可以用于刷新 token 判断使用
jwt.RegisteredClaims
}
type BaseClaims struct {
ID uint
Username string
RoleId uint
}
type JWT struct {
SigningKey []byte
}
func NewJWT() *JWT {
return &JWT{
[]byte("sanshi"),
}
}
// GetToken 使用默认声明创建jwt
func (j *JWT) GetToken(claims *CustomClaims) (string, error) {
// 生成 token
return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(j.SigningKey)
}
func (j *JWT) keyFunc(token *jwt.Token) (any, error) {
return j.SigningKey, nil
}
// ParseToken 解析 token
func (j *JWT) ParseToken(tokenString string) (*CustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, j.keyFunc)
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
return nil, TokenExpired
case errors.Is(err, jwt.ErrTokenMalformed):
return nil, TokenMalformed
case errors.Is(err, jwt.ErrTokenSignatureInvalid):
return nil, TokenSignatureInvalid
case errors.Is(err, jwt.ErrTokenNotValidYet):
return nil, TokenNotValidYet
default:
return nil, TokenInvalid
}
}
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, TokenValid
}
// RefreshToken 刷新 token
func (j *JWT) RefreshToken(token string) (string, error) {
customClaims, err := j.ParseToken(token)
if err != nil {
return "", TokenInvalid
}
if customClaims.ExpiresAt.Unix()-time.Now().Unix() < customClaims.BufferTime {
customClaims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(testTokenExpired))
newToken, _ := j.CreateTokenByOldToken(token, customClaims)
return newToken, nil
}
return token, nil
}
// CreateTokenByOldToken 重新生成 token
func (j *JWT) CreateTokenByOldToken(token string, c *CustomClaims) (string, error) {
v, err, _ := ConcurrencyControl.Do("JWT:"+token, func() (interface{}, error) {
return j.GetToken(c)
})
return v.(string), err
}
func main() {
j := NewJWT()
c := CustomClaims{
BufferTime: int64(bufferTime),
BaseClaims: BaseClaims{
ID: 1,
Username: "wangLei",
RoleId: 1,
},
RegisteredClaims: jwt.RegisteredClaims{
Audience: jwt.ClaimStrings{"sanshi-blog"}, // 受众
ExpiresAt: jwt.NewNumericDate(time.Now().Add(tokenExpired)), // 过期时间
Issuer: "sanshi", // 签发人
},
}
token, _ := j.GetToken(&c)
fmt.Println(token)
claims, err := j.ParseToken(token)
if err != nil {
panic(err)
}
fmt.Printf("%+v \n", claims)
token, _ = j.RefreshToken(token)
fmt.Println(token)
claims, err = j.ParseToken(token)
if err != nil {
panic(err)
}
fmt.Printf("%+v \n", claims)
}