文章目录
go-jwt
## jwt格式
一个正确的JWT格式如下所示:
eyJhbGciOiJIUzI1NiIsInR5c.eyJ1c2VybmFtZaYjiJ9._eCVNYFYnMXwpgGX9Iu412EQSOFuEGl2c
我们看到一个JWT字符串由Header,Payload,Signature三个部分组成,中间使用逗号连接。
Header
Header是一个JSON对象,由token类型和加密算法两个部分组成的,如:
json复制代码{
"typ": "JWT",//默认为JWT
"alg": "HS256"//支持多种加密算法
}
将上面的JSON对象使用Base64URL算法转换成字符串,即可得到JWT中的Header部分。
注意:JWT编码并不使用Base64,而Base64Url,这是因为Base64生成字符串里,可能会有+,/和=这三个URL中特殊的符号,而我们又可能将token放在URL上传递到服务器上(如test.com?token=xxx),%EF%BC%8C) 而Base64URL算法,则是在Base64算法生成的字符串基础上,将=省略,将+替换成-,将/替换成_。
Payload
JWT的Payload部分与Header一样,也是一个JSON对象,用来存放我们实际需要的数据,JWT标准提供了七个可选的字段,分别为:
标题 | 描述 |
---|---|
iss(issuer) | 签发者,其值为大小写敏感的字符串或Uri |
sub(subject) | 主题,用于鉴别一个用户 |
exp(expiration time) | 过期时间 |
aud(audience) | 受众 |
iat(issued at) | 签发时间 |
nbf(not before) | 生效时间 |
jti(JWT ID) | 编号 |
除了标准的字段外,我们可以任意定义私有的字段以满足业务需求,如:
json复制代码{
iss:"my",//标准字段
jti:"test",//标准字段
username:"aaa",//自定义字段
"gender":"男",
"avatar":"https://1.jpg"
}
将上面的JSON对象使用Base64URL算法转换成字符串,即可得到JWT中的Payload部分。
。
在go的项目中使用jwt
1、使用"github.com/golang-jwt/jwt"
这个库来生成jwt字符
package main
import (
"fmt"
"github.com/golang-jwt/jwt"
"time"
)
func main() {
//通过其中创建了一个名为 key 的字节切片,并将其初始化为包含字符串 "secret" 的字节数组
key := []byte("111")
/*
jwt.NewWithClaims 函数创建了一个JWT对象。它接受两个参数:签名方法和声明(claims)。
jwt.SigningMethodHS256 表示使用HS256算法进行JWT签名
jwt.MapClaims 表示声明部分,其中包含了您希望在JWT中包含的数据。
"nbf" 字段表示"not before",即JWT的生效时间,这里设置为2015年10月10日12点 00分00秒(UTC时间)的Unix时间戳。
*/
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"fool": "123",
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
})
// 使用 SigendString 通过密钥 来生成jwt字符串
tokenstring, err := token.SignedString(key)
fmt.Println(tokenstring, err)
}
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt"
)
func main() {
hmacSampleSecret := []byte("111")
token := jwt.New(jwt.SigningMethodHS256)
//通过New方法不能在创建的时候携带数据,因此可以通过给token.Claims赋值来定义数据
token.Claims = jwt.MapClaims{
"foo": "bar",
"nbf": time.Date(2015, 10, 10, 12, 0, 0, 0, time.UTC).Unix(),
}
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
}
2、除了用jwt.MapClaims,我们也可以使用自定义的结构
必须要实现这个接口
type Claims interface {
Valid() error
}
整体代码
package main
import (
"fmt"
"github.com/golang-jwt/jwt"
)
type CustomerClaims struct {
Username string `json:"username"`
Gender string `json:"gender"`
Avatar string `json:"avatar"`
Email string `json:"email"`
}
func (c CustomerClaims) Valid() error {
return nil
}
func main() {
//密钥
hmacSampleSecret := []byte("111")
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = CustomerClaims{
Username: "小明",
Gender: "男",
Avatar: "https://1.jpg",
Email: "test@163.com",
}
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
}
如果我们想在自定义结构中使用JWT标准中定义的字段,可以这样子:
go复制代码type CustomerClaims struct {
*jwt.StandardClaims//标准字段
Username string `json:"username"`
Gender string `json:"gender"`
Avatar string `json:"avatar"`
Email string `json:"email"`
}
3、解析jwt
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v4"
)
type CustomerClaims struct {
Username string `json:"username"`
Gender string `json:"gender"`
Avatar string `json:"avatar"`
Email string `json:"email"`
}
func (c CustomerClaims) Valid() error {
return nil
}
var hmacSampleSecret = []byte("111")
func main() {
//密钥
token := jwt.New(jwt.SigningMethodHS256)
token.Claims = CustomerClaims{
Username: "小明",
Gender: "男",
Avatar: "https://1.jpg",
Email: "test@163.com",
}
tokenString, err := token.SignedString(hmacSampleSecret)
fmt.Println(tokenString, err)
username, err, token := ParseToken(tokenString)
fmt.Println(username, token.Method, err)
}
// 解析jwt字符串
func ParseToken(tokenstring string) (string, error, *jwt.Token) {
token, err := jwt.ParseWithClaims(tokenstring, &CustomerClaims{}, func(t *jwt.Token) (interface{}, error) {
return hmacSampleSecret, nil
})
claims := token.Claims.(*CustomerClaims)
return claims.Username, err, token
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
package main
import (
"fmt"
"github.com/golang-jwt/jwt"
)
type CustomerClaims struct {
Username string `json:"username"`
Gender string `json:"gender"`
Avatar string `json:"avatar"`
Email string `json:"email"`
jwt.StandardClaims
}
func main() {
var hmacSampleSecret = []byte("111")
//前面例子生成的token
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IuWwj-aYjiIsImdlbmRlciI6IueUtyIsImF2YXRhciI6Imh0dHBzOi8vMS5qcGciLCJlbWFpbCI6InRlc3RAMTYzLmNvbSJ9.mJlWv5lblREwgnP6wWg-P75VC1FqQTs8iOdOzX6Efqk"
// 第三个参数传入一个keyfunc函数 里面返回一个任意类型
token, err := jwt.ParseWithClaims(tokenString, &CustomerClaims{}, func(t *jwt.Token) (interface{}, error) {
return hmacSampleSecret, nil
})
if err != nil {
fmt.Println(err)
return
}
/*
在这个代码片段中,`claims := token.Claims.(*CustomerClaims)` 是一行代码,它用于从 JWT(JSON Web Token) 中提取声明(claims)信息并将其赋值给变量 `claims`。
让我来解释这行代码的每一部分:
- `token.Claims`:`token` 是通过调用 `jwt.ParseWithClaims` 函数解析 JWT 后得到的 JWT 结构。JWT 包含了头部(header)、载荷(payload)和签名(signature)等部分,其中载荷部分包含声明信息。`token.Claims` 表示从 JWT 中获取声明信息的属性。
- `(*CustomerClaims)`:这部分是类型断言(type assertion)。它将 `token.Claims` 强制转换为类型为 `*CustomerClaims` 的指针。在这里,`CustomerClaims` 是一个自定义的结构体,用于表示 JWT 中的声明。通过这个类型断言,我们告诉编译器将 `token.Claims` 视为 `*CustomerClaims` 类型的数据。
- `claims := token.Claims.(*CustomerClaims)`:这是一个赋值语句,将 `token.Claims` 的内容转换为 `*CustomerClaims` 类型,并将其赋值给名为 `claims` 的变量。现在,`claims` 包含了从 JWT 中提取的声明信息,你可以在后续代码中使用它来访问这些声明的值。
*/
claims := token.Claims.(*CustomerClaims)
fmt.Println(claims)
}
**keyfunc函数示例 **
package main
import (
"errors"
"fmt"
"github.com/golang-jwt/jwt"
)
func main() {
// 示例JWT字符串
tokenString := "..." // 请将您的JWT字符串放在这里
// 定义一个Keyfunc函数,用于提供用于验证JWT签名的密钥
keyFunc := func(token *jwt.Token) (interface{}, error) {
// 如果您的JWT使用相同的密钥进行签名和验证,可以在这里返回密钥
// 在实际应用中,密钥通常需要更复杂的管理逻辑,例如从配置文件或密钥存储中获取
return []byte("your-secret-key"), nil
}
// 调用ParseWithClaims函数来解析JWT
token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, keyFunc)
if err != nil {
fmt.Println("JWT parsing failed:", err)
return
}
// 验证JWT是否有效
if !token.Valid {
fmt.Println("JWT is not valid")
return
}
// JWT验证通过,您可以访问JWT中的声明信息
claims := token.Claims.(*jwt.StandardClaims)
fmt.Println("JWT Valid")
fmt.Println("Subject:", claims.Subject)
fmt.Println("Expiration Time:", claims.ExpiresAt)
// 其他声明字段...
}
上述示例中,我们定义了一个简单的 Keyfunc
函数,它只是返回一个硬编码的密钥 []byte("your-secret-key")
用于验证JWT签名。在实际应用中,您应该根据安全最佳实践从更安全的存储位置获取密钥。
然后,我们调用 jwt.ParseWithClaims
函数来解析JWT,并将密钥提供给它。最后,我们验证JWT是否有效,并访问其中的声明信息。
4、gin-jwt实现
package main
import (
"log"
"net/http"
"time"
"github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
)
// 用于接受登录的用户名与密码
type login struct {
/*
1. `Username` 字段:表示用户名,它具有以下标签:
- `form:"username"`:这个标签表示这个字段可以从表单数据中获取,其中 "username" 是表单字段的名称。
- `json:"username"`:这个标签表示这个字段可以从 JSON 数据中获取,其中 "username" 是 JSON 属性的名称。
- `binding:"required"`:这个标签表示这个字段是必需的,也就是说,在处理登录请求时,用户名必须提供。
2. `Password` 字段:表示密码,它也有类似的标签:
- `form:"password"`:这个标签表示这个字段可以从表单数据中获取,其中 "password" 是表单字段的名称。
- `json:"password"`:这个标签表示这个字段可以从 JSON 数据中获取,其中 "password" 是 JSON 属性的名称。
- `binding:"required"`:同样,这个标签表示这个字段也是必需的,密码必须提供。
*/
Username string `form:"username" json:"username" binding:"required"`
Password string `form:"password" json:"password" binding:"required"`
}
var identityKey = "id"
// jwt中payload的数据
type User struct {
UserName string
FirstName string
LastName string
}
func main() {
// 定义一个Gin的中间件
authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{
Realm: "test zone", //标识
SigningAlgorithm: "HS256", //加密算法
Key: []byte("secret key"), //密钥
Timeout: time.Hour,
MaxRefresh: time.Hour, //刷新最大延长时间
IdentityKey: identityKey, //指定cookie的id
PayloadFunc: func(data interface{}) jwt.MapClaims { //负载,这里可以定义返回jwt中的payload数据
if v, ok := data.(*User); ok {
return jwt.MapClaims{
identityKey: v.UserName,
}
}
return jwt.MapClaims{}
},
IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
return &User{
UserName: claims[identityKey].(string),
}
},
Authenticator: Authenticator, //在这里可以写我们的登录验证逻辑
Authorizator: func(data interface{}, c *gin.Context) bool { //当用户通过token请求受限接口时,会经过这段逻辑
if v, ok := data.(*User); ok && v.UserName == "admin" {
return true
}
return false
},
Unauthorized: func(c *gin.Context, code int, message string) { //错误时响应
c.JSON(code, gin.H{
"code": code,
"message": message,
})
},
// 指定从哪里获取token 其格式为:"<source>:<name>" 如有多个,用逗号隔开
TokenLookup: "header: Authorization, query: token, cookie: jwt",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
})
if err != nil {
log.Fatal("JWT Error:" + err.Error())
}
r := gin.Default()
//登录接口
r.POST("/login", authMiddleware.LoginHandler)
auth := r.Group("/auth")
//退出登录
auth.POST("/logout", authMiddleware.LogoutHandler)
// 刷新token,延长token的有效期
auth.POST("/refresh_token", authMiddleware.RefreshHandler)
auth.Use(authMiddleware.MiddlewareFunc()) //应用中间件
//使用 {}(大括号)来创建一个代码块
//是为了将一组相关的路由规则组织在一起
//形成一个路由组。这是 Gin 框架的常见做法
{
auth.GET("/hello", helloHandler)
}
if err := http.ListenAndServe(":8005", r); err != nil {
log.Fatal(err)
}
}
func Authenticator(c *gin.Context) (interface{}, error) {
var loginVals login
if err := c.ShouldBind(&loginVals); err != nil {
return "", jwt.ErrMissingLoginValues
}
userID := loginVals.Username
password := loginVals.Password
if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") {
return &User{
UserName: userID,
LastName: "Bo-Yi",
FirstName: "Wu",
}, nil
}
return nil, jwt.ErrFailedAuthentication
}
// 处理/hellow路由的控制器
func helloHandler(c *gin.Context) {
claims := jwt.ExtractClaims(c)
user, _ := c.Get(identityKey)
c.JSON(200, gin.H{
"userID": claims[identityKey],
"userName": user.(*User).UserName,
"text": "Hello World.",
})
}