jwt-go
什么是jwt
全称为json web token,通过数字签名的方式,以json对象为载体,在不同的服务终端之间安全的传输信息,最常见的场景就是授权认证,一旦用户登录,后续的每个请求都会包含jwt,系统在每次处理用户请求之前,都要先经行jwt安全校验,通过后再经行处理。jwt由三个部分组成—header,payload,signature。
创建一个jwt
官方代码
mySigningKey := []byte("AllYourBase")
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// Create the Claims
claims := MyCustomClaims{
"bar",
jwt.StandardClaims{
ExpiresAt: 15000,
Issuer: "test",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(mySigningKey)
fmt.Printf("%v %v", ss, err)
逐步分析
创建一个带要求的jwt官方给的方法为NewWithClaims。
NewWithClaims方法为
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
方法参数中,第一个参数为加密方法,第二个参数Claims是一个接口
其接口构造为
type Claims interface {
Valid() error
}
其中实现了本接口的结构体有
type MapClaims map[string]interface{}
type StandardClaims struct {
Audience string `json:"aud,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Id string `json:"jti,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
NotBefore int64 `json:"nbf,omitempty"`
Subject string `json:"sub,omitempty"`
}
其中NotBefore 表示生效时间,ExpiresAt 失效时间,Issuer签发者
先用StandardClaims 来实现一个Claims接口
type MyClaims struct {
username string `json:"username"`
jwt.StandardClaims
}
实例化
claims := MyClaims{
Username: "logiee",
StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 60,
ExpiresAt: time.Now().Unix() + 60*60,
Issuer: "logiee",
},
}
加密方式暂时选用对称加密HS256
则NewWithClaims将生成一个token,先直接输出token
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"time"
)
type MyClaims struct {
Username string `json:"username"`
jwt.StandardClaims
}
func main() {
claims := MyClaims{
Username: "logiee",
StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 60,
ExpiresAt: time.Now().Unix() + 60*60,
Issuer: "logiee",
},
}
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
fmt.Println(t)
}
这就是jwt中的header,payload,signature。
但现在前端无法接收这个格式,则需要改造成签名字符串。
官方提供方法SignedString
func (t *Token) SignedString(key interface{}) (string, error)
其中的参数key,官方要我们放在byte切片中
先设置一个自己的签名。
mySignKey := []byte("Allow")
执行签发
func main() {
mySignKey := []byte("Allow")
claims := MyClaims{
Username: "logiee",
StandardClaims: jwt.StandardClaims{
NotBefore: time.Now().Unix() - 60,
ExpiresAt: time.Now().Unix() + 60*60,
Issuer: "logiee",
},
}
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
s, _ := t.SignedString(mySignKey)
fmt.Println(s)
}
则可以获得一个完整的token
解密
官方提供方法ParseWithClaims (CustomClaimsType)
官方代码
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJleHAiOjE1MDAwLCJpc3MiOiJ0ZXN0In0.HE7fK0xOQwFEr4WDgRWj4teRPZ6i3GLwD5YCm6Pwu_c"
type MyCustomClaims struct {
Foo string `json:"foo"`
jwt.StandardClaims
}
// sample token is expired. override time so it parses as valid
at(time.Unix(0, 0), func() {
token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("AllYourBase"), nil
})
if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
fmt.Printf("%v %v", claims.Foo, claims.StandardClaims.ExpiresAt)
} else {
fmt.Println(err)
}
})
ParseWithClaims
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error)
第一个参数为接收的token字符串,第二个参数claims与上文一样,第三个参数为函数参数,最为重要。
参数函数为
type Keyfunc func(*Token) (interface{}, error)
解析方法使用这个回调函数来提供验证的密钥。该函数接收已解析但未验证的Token。这允许你使用令牌头部的属性(如`kid’)来确定使用哪个密钥。
则解密代码
withClaims, _ := jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return mySignKey, nil
})
fmt.Println(withClaims)
我们通过断言来获得myClaims中的username
withClaims, _ := jwt.ParseWithClaims(s, &MyClaims{}, func(token *jwt.Token) (interface{}, error) {
return mySignKey, nil
})
fmt.Println(withClaims.Claims.(*MyClaims).Username)