密码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没有加密功能,所以不要往载荷里面放敏感数据
- 访问登录接口,后端生成jwt返回,在载荷里面存一些必要信息,比如用户id,用户的权限id
- 前端需要把这个token持久化存起来,然后后续的请求,就把token带到请求头里面
- 用户访问需要认证的接口,后端通过算法,算出这个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不足:
- 没有主动过期策略
jwt过期如何实现
- token黑名单策略
在用户注销之后,将token放入redis
在认证的时候,先判断token有没有被篡改,有没有过期,如何再判断有没有再redis的黑名单中
如果在黑名单中,就说明之前这个token是注销了的,认证就不通过
Jwt 双令牌
json web token 一般用于前后端分离模式下的身份认证
JWT有两种机制,单令牌机制和双令牌机制。
单令牌机制服务端只生成一个 token,一般过期时间比较长,因此安全性稍差。
双令牌机制服务端生成两个token,一个access_token用来鉴权,过期时间一般较短(5分钟,15分钟等),另一个 refresh_token,只用来获取新的 access_token,过期时间较长(可设置为24小时或者更长)
单令牌机制一般步骤:
- 前端发起登录请求
- 服务端验证用户名密码,验证通过则下发token返回给前端。
- 前端收到返回的token进行保存。
- 前端后续请求头将携带 token 供服务端验证。
- 服务端收到请求首先验证 token,验证通过则正常提供服务,不通过则返回相应提示信息
双令牌机制一般步骤:
- 前端发起登录请求
- 服务端验证用户名密码,验证通过则下发access_token和refresh_token 返回给前端。
- 前端收到返回的两个 token进行保存。
- 前端后续请求头将携带 access_token供服务端验证。
- 服务端收到请求首先验证 token,验证通过则正常提供服务,不通过则返回相应提示信息。
- 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)
}