gin_jwt
本文最后更新于:1 年前
1.jwt
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案
1.1 原理
JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。这样服务器就不用保存session,实现无状态认证,可扩展性更强
1.2 数据结构
JWT主要分为三个部分
- Header(头部)
- Payload(负载)
- Signature(签名)
可以看出来jwt的组成也十分简单明了
Header
header中alg(algorithm)决定了签名使用的加密算法,默认为HS256,也可以根据需要修改加密算法(官网提供了多种加密算法),typ属性表示这个令牌token的类型(type),JWT 令牌统一写为JWT
。
Payload
payload就是要传递的数据,也是一个json对象,JWT 规定了7个官方字段,供选用。
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个 JSON 对象也要使用 Base64URL 算法转成字符串。
Signature
Signature 部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.
)分隔,就可以返回给用户。
1.3 go实现jwt
package jwt
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"strings"
)
type header struct {
Alg string `json:"alg"`
Typ string `json:"typ"`
}
const (
HS256 = "HS256"
)
var alg = HS256
var Secret string
func hs256(secret, data []byte) (ret string, err error) {
hasher := hmac.New(sha256.New, secret)
_, err = hasher.Write(data)
if err != nil {
return "", err
}
r := hasher.Sum(nil)
return base64.RawURLEncoding.EncodeToString(r), nil
}
func Sign(payload interface{}) (ret string, err error) {
h := header{
Alg: alg,
Typ: "JWT",
}
marshal, err := json.Marshal(h)
if err != nil {
return "", err
}
bh := base64.RawURLEncoding.EncodeToString(marshal)
marshal, err = json.Marshal(payload)
if err != nil {
return "", err
}
bp := base64.RawURLEncoding.EncodeToString(marshal)
// 将header和payload 拼接在一起
s := fmt.Sprintf("%s.%s", bh, bp)
ret, err = hs256([]byte(Secret), []byte(s))
if err != nil {
return "", err
}
return fmt.Sprintf("%s.%s.%s", bh, bp, ret), nil
}
func Verify(token string) (err error) {
parts := strings.Split(token, ".")
data := strings.Join(parts[0:2], ".")
hasher := hmac.New(sha256.New, []byte(Secret))
_, err = hasher.Write([]byte(data))
if err != nil {
return err
}
sig, err := base64.RawURLEncoding.DecodeString(parts[2])
if err != nil {
return err
}
if hmac.Equal(sig, hasher.Sum(nil)) {
return nil
}
return errors.New("verify is invalid")
}
2.gin框架实现jwt身份认证
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gotoken/db"
"gotoken/jwt"
"net/http"
)
type AccountToken struct {
Name string `json:"name"`
}
type User struct{
gorm.Model
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
db := db.GetDatabase()
user := User{}
//fmt.Println(user)
jwt.Secret = "123456"
r := gin.Default()
// 中间件 相当于拦截器进行鉴权处理
r.Use(func(c *gin.Context) {
if c.Request.RequestURI == "/login" {
// 如果访问的是login则放行,从而获取token
return
}
token, ok := c.Request.Header["Token"]
fmt.Println(token)
if ok {
err := jwt.Verify(token[0])
if err != nil {
c.AbortWithStatusJSON(403, "Forbidden1") //jwt toke验证不通过 不放行
}
} else {
c.AbortWithStatusJSON(403, "Forbidden2")
}
})
// 第一次访问该路由获取token
r.POST("/login", func(c *gin.Context) {
userNew := User{}
c.ShouldBind(&userNew)
if userNew.Username == "" || userNew.Password == "" {
c.JSON(http.StatusForbidden, gin.H{
"message": "username or password is empty",
})
return
}
db.First(&user, "username = ?", userNew.Username)
if user.Username == userNew.Username && user.Password == userNew.Password {
sign, _ := jwt.Sign(user)
c.JSON(http.StatusOK, gin.H{
"message": "login successfully!",
"data": sign,
})
} else {
c.JSON(http.StatusForbidden, gin.H{
"message": "username or password is invalid",
})
return
}
})
// 访问此路由不需要token
r.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, "index")
})
r.Run(":8081")
}
数据库初始化文件
package db
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var Db *gorm.DB
func init() {
// 初始化数据库
dsn := "root:root@tcp(127.0.0.1:3306)/demo?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
Db = db
}
func GetDatabase() *gorm.DB{
return Db
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!