fastGin脚手架——工具部分
密码hash 这个是go内置的密码加密库 相对于传统的md5,bcrypt要更加的安全 import "golang.org/x/crypto/bcrypt" // GenerateFromP

fastGin脚手架——工具部分

发布时间:2025-02-27 (2025-02-27)
AI 文章总结
用 AI 快速提炼本文核心内容和阅读重点。

密码hash

这个是go内置的密码加密库

相对于传统的md5,bcrypt要更加的安全

import "golang.org/x/crypto/bcrypt"

// GenerateFromPassword 加密密码
func GenerateFromPassword(password string) (string, error) {
  hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
  if err != nil {
    return "", err
  }
  return string(hashedPassword), nil
}

// CompareHashAndPassword 校验密码
func CompareHashAndPassword(hashedPassword string, password string) bool {
  err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
  return err == nil
}

jwt颁发与验证

由三段组成

头部.载荷.签名

jwt没有加密功能,所以不要往载荷里面放敏感数据

  1. 访问登录接口,后端生成jwt返回,在载荷里面存一些必要信息,比如用户id,用户的权限id
  2. 前端需要把这个token持久化存起来,然后后续的请求,就把token带到请求头里面
  3. 用户访问需要认证的接口,后端通过算法,算出这个token是否有效,是否过期,只要它有效并且没有过期,那么就认证通过

如何判断这个token是有效的?(怎么保证这个token是不被篡改的)

重点是签名,根据头部里面定义的加密算法,对头部和载荷进行对应方式的加密字符串,在其中会涌入一个私钥,根据这个私钥进行非对称加密

加密过程

{sha256}.{userID:1,role:2}.xxxx
cecret: 1234

xxxx: sha256(base46Encode({sha256}.{userID:1,role:2}))

验证过程:

sdgk.djerjretj.xxxx
可以拿到头部的加密方式 是 sha256
我们再加密一次
yyyy = sha256(sdgk.djerjretj)
只需要判断 yyyy是不是等于xxxx 如果相等,就说明jwt没有被篡改
如果不等就说明被篡改了

jwt不足:

  1. 没有主动过期策略

jwt过期如何实现

  1. token黑名单策略

在用户注销之后,将token放入redis

在认证的时候,先判断token有没有被篡改,有没有过期,如何再判断有没有再redis的黑名单中

如果在黑名单中,就说明之前这个token是注销了的,认证就不通过

Jwt 双令牌

json web token 一般用于前后端分离模式下的身份认证

JWT有两种机制,单令牌机制和双令牌机制。

单令牌机制服务端只生成一个 token,一般过期时间比较长,因此安全性稍差。

双令牌机制服务端生成两个token,一个access_token用来鉴权,过期时间一般较短(5分钟,15分钟等),另一个 refresh_token,只用来获取新的 access_token,过期时间较长(可设置为24小时或者更长)

单令牌机制一般步骤:

  1. 前端发起登录请求
  2. 服务端验证用户名密码,验证通过则下发token返回给前端。
  3. 前端收到返回的token进行保存。
  4. 前端后续请求头将携带 token 供服务端验证。
  5. 服务端收到请求首先验证 token,验证通过则正常提供服务,不通过则返回相应提示信息

双令牌机制一般步骤:

  1. 前端发起登录请求
  2. 服务端验证用户名密码,验证通过则下发access_token和refresh_token 返回给前端。
  3. 前端收到返回的两个 token进行保存。
  4. 前端后续请求头将携带 access_token供服务端验证。
  5. 服务端收到请求首先验证 token,验证通过则正常提供服务,不通过则返回相应提示信息。
  6. access_token过期后,前端请求头 将 access_token替换为 refresh_token,调用刷新 access_token的接口。获取到新的 access_token并保存,重新发起请求, 这个refresh_token只能用于获取access_token,其他接口是过不了鉴权的

go生成jwt

// utils/jwts/enter.go
package jwts

import (
  "errors"
  "fai_server/global"
  "github.com/dgrijalva/jwt-go"
  "time"
)

type ClaimsUserInfo struct {
  UserID uint `json:"userID"`
  Role   int8 `json:"role"`
}

type Claims struct {
  ClaimsUserInfo
  jwt.StandardClaims
}

// GetToken 生成token
func GetToken(info ClaimsUserInfo) (string, error) {
  j := global.Config.Jwt
  cla := Claims{
    ClaimsUserInfo: info,
    StandardClaims: jwt.StandardClaims{
      ExpiresAt: time.Now().Add(time.Duration(j.Expires) * time.Hour).Unix(), // 过期时间
      Issuer:    j.Issuer,                                                    // 签发人
    },
  }
  token := jwt.NewWithClaims(jwt.SigningMethodHS256, cla)
  return token.SignedString([]byte(j.Secret)) // 进行签名生成对应的token
}

// ParseToken 解析token
func ParseToken(tokenString string) (*Claims, error) {
  j := global.Config.Jwt
  token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    return []byte(j.Secret), nil
  })
  if err != nil {
    return nil, err
  }
  claims, ok := token.Claims.(*Claims)
  if ok && token.Valid {
    if claims.Issuer != j.Issuer {
      return nil, errors.New("invalid issuer")
    }
    return claims, nil
  }
  return nil, errors.New("invalid token")
}

响应封装

package res

import "github.com/gin-gonic/gin"

type Response struct {
  Code int    `json:"code"`
  Data any    `json:"data"`
  Msg  string `json:"msg"`
}

func Ok(data any, msg string, c *gin.Context) {
  c.JSON(200, Response{
    Code: 0,
    Data: data,
    Msg:  msg,
  })
}

func OkWithMsg(msg string, c *gin.Context) {
  Ok(gin.H{}, msg, c)
}
func OkWithData(data any, c *gin.Context) {
  Ok(data, "成功", c)
}

func Fail(code int, msg string, c *gin.Context) {
  c.JSON(200, Response{
    Code: code,
    Data: gin.H{},
    Msg:  msg,
  })
}

func FailWithMsg(msg string, c *gin.Context) {
  Fail(7, msg, c)
}

func FailWithError(err error, c *gin.Context) {
  Fail(7, err.Error(), c)
}

计算md5

虽然md5在密码方面不安全,但是可以用于其他校验的地方

比如文件一致性校验

package md5

import (
  "crypto/md5"
  "encoding/hex"
  "io"
  "mime/multipart"
)

func MD5WithFile(file multipart.File) string {
  m := md5.New()
  io.Copy(m, file)
  hashBytes := m.Sum(nil)
  return hex.EncodeToString(hashBytes)
}