gin_jwt

本文最后更新于:1 年前

1.jwt

JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案

jwt官网

1.1 原理

JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。这样服务器就不用保存session,实现无状态认证,可扩展性更强

1.2 数据结构

JWT主要分为三个部分

  • Header(头部)
  • Payload(负载)
  • Signature(签名)

image-20230204214410681

可以看出来jwt的组成也十分简单明了

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 协议 ,转载请注明出处!

 目录

Copyright © 2020 my blog
载入天数... 载入时分秒...