345 lines
9.3 KiB
Go
345 lines
9.3 KiB
Go
package am_jwt
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"github.com/axiomatrix-org/optimized-toolchain/am_redis"
|
||
"github.com/gin-gonic/gin"
|
||
"gopkg.in/dgrijalva/jwt-go.v3"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// 工具返回代码
|
||
const (
|
||
OKCode = 2000
|
||
InternalServerErrorCode = 5000
|
||
ErrCodeMalformed = 4001 // token格式错误
|
||
ErrCodeExpired = 4002 // token过期
|
||
ErrCodeNotValidYet = 4003 // token尚未生效
|
||
ErrCodeSignatureInvalid = 4004 // token签名无效
|
||
ErrCodeInvalidRole = 4005 // token权限不符
|
||
ErrCodeNoAuthHeader = 4006 // 没有对应的请求头
|
||
ErrCodeMalformedReq = 4007 // 请求头格式不正确
|
||
)
|
||
|
||
// 工具错误类型
|
||
var (
|
||
MalformedError = errors.New("malformed jwt")
|
||
ExpiredAndDiedError = errors.New("expired jwt")
|
||
ExpiredButCanSaveError = errors.New("expired jwt, but can save it")
|
||
NotValidError = errors.New("invalid jwt")
|
||
SignatureError = errors.New("expired jwt")
|
||
InvalidRoleError = errors.New("invalid role")
|
||
UnknownError = errors.New("unknown jwt error")
|
||
)
|
||
|
||
// 不同身份权限,数字越大,权限越大。高权限用户可以执行低权限用户的所有操作
|
||
const (
|
||
ROOTROLE = 4 // root权限,最大的权限,整个系统的超级管理权限
|
||
ADMINROLE = 3 // admin权限,只能由root授予,拥有管理用户的权限,但不具备修改root的权限
|
||
USERROLE = 2 // user权限,普通用户权限
|
||
TEMPROLE = 1 // temp权限,临时权限,该权限仅用于注册和修改密码的临时使用
|
||
)
|
||
|
||
// 身份字段转换权限等级数字
|
||
func ClaimToRole(claim string) int {
|
||
switch claim {
|
||
case "root":
|
||
return ROOTROLE
|
||
case "admin":
|
||
return ADMINROLE
|
||
case "user":
|
||
return USERROLE
|
||
case "temp":
|
||
return TEMPROLE
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// token claims
|
||
type TokenClaims struct {
|
||
Email string // 用户的email
|
||
Role string // 用户的身份登记
|
||
Exp int // token过期时间,以秒计数
|
||
Issuer string // 签发人
|
||
SECRET string // token secret
|
||
PUBLIC string // token public
|
||
jwt.StandardClaims // standard claims,无需用户设定
|
||
}
|
||
|
||
// token config
|
||
type TokenConfig struct {
|
||
Issuer string `json:"issuer"`
|
||
}
|
||
|
||
/*
|
||
* PRIVATE
|
||
* 查验redis储存情况
|
||
* 参数:
|
||
* 1. token string:要查验的token
|
||
*/
|
||
|
||
func checkRedis(token string) (*TokenClaims, error) {
|
||
// 从redis中查验
|
||
claimsJson, err := am_redis.GetValue(token)
|
||
claims := &TokenClaims{}
|
||
if err != nil {
|
||
if errors.Is(err, am_redis.RedisGetNilError) { // 如果不存在于库中,则证明已经提前过期
|
||
return nil, ExpiredAndDiedError
|
||
} else {
|
||
return nil, err
|
||
}
|
||
}
|
||
err = json.Unmarshal([]byte(claimsJson), claims)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return claims, nil
|
||
}
|
||
|
||
/*
|
||
* 生成token字串
|
||
* 参数:
|
||
* 1. claims *TokenClaims token参数
|
||
*/
|
||
func GenToken(claims *TokenClaims) (string, error) {
|
||
tokenClaims := claims
|
||
|
||
// 配置standard claims
|
||
tokenClaims.StandardClaims = jwt.StandardClaims{
|
||
ExpiresAt: time.Now().Add(time.Second * time.Duration(claims.Exp)).Unix(), // 过期时间
|
||
IssuedAt: time.Now().Unix(), // 签名日期
|
||
Issuer: claims.Issuer, // 签发人
|
||
}
|
||
|
||
// 解析並刪除私鑰
|
||
privKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(claims.SECRET))
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
tokenClaims.SECRET = ""
|
||
|
||
// 生成token字串
|
||
tokenGenerator := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims)
|
||
token, err := tokenGenerator.SignedString(privKey) // 生成token
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 储存redis
|
||
value, err := json.Marshal(claims)
|
||
if err != nil {
|
||
fmt.Println(err)
|
||
return "", err
|
||
}
|
||
|
||
err = am_redis.SetValue(token, string(value), claims.Exp) // 键为token字串,值为原始过期时间
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return token, nil
|
||
}
|
||
|
||
/*
|
||
* 解析token
|
||
* 参数:
|
||
* 1. token string:要解析的token字串
|
||
* 2. roleRequired int:验证通过需要的权限等级
|
||
* 3. secret string:解码密钥
|
||
*/
|
||
func ParseToken(token string, roleRequired int, secret string) (*TokenClaims, error) {
|
||
// 解析公鑰
|
||
pem, err := jwt.ParseRSAPublicKeyFromPEM([]byte(secret))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 解析token
|
||
result, err := jwt.ParseWithClaims(token, &TokenClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||
return pem, nil
|
||
})
|
||
|
||
// 解析出现问题
|
||
if err != nil {
|
||
if ve, ok := err.(*jwt.ValidationError); ok {
|
||
if ve.Errors&jwt.ValidationErrorMalformed != 0 { // token格式不正确
|
||
return nil, MalformedError
|
||
} else if ve.Errors&jwt.ValidationErrorExpired != 0 { // token过期
|
||
claims, err := checkRedis(token) // 查验redis
|
||
if err != nil {
|
||
if errors.Is(err, ExpiredAndDiedError) { // 如果redis也过期了
|
||
return nil, ExpiredAndDiedError // 无可救药
|
||
} else {
|
||
return nil, err
|
||
}
|
||
}
|
||
return claims, ExpiredButCanSaveError // 否则,还能救一救(重新生成token)
|
||
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { // token尚未生效
|
||
return nil, NotValidError
|
||
} else if ve.Errors&jwt.ValidationErrorSignatureInvalid != 0 { // token签名错误
|
||
return nil, SignatureError
|
||
} else { // 其他错误
|
||
return nil, err
|
||
}
|
||
}
|
||
}
|
||
|
||
// 解析未出现问题,即此token形式合法
|
||
if claims, ok := result.Claims.(*TokenClaims); ok && result.Valid {
|
||
// 从redis中查验
|
||
_, err := checkRedis(token)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 刷新token在redis中的过期时间
|
||
err = am_redis.SetValue(token, strconv.Itoa(claims.Exp), claims.Exp)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 验证权限合法性
|
||
if ClaimToRole(claims.Role) >= roleRequired { // 如果提供的token权限验证大于所需权限,初步判断通过
|
||
if roleRequired == 1 && ClaimToRole(claims.Role) == 2 { // user权限无权新增用户
|
||
return nil, InvalidRoleError
|
||
} // user不允許操作temp
|
||
|
||
if ClaimToRole(claims.Role) == 1 { // temp权限仅用于注册和重设密码临时使用,一经使用立即灭活
|
||
_, err := Kickoff(token)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
}
|
||
return claims, nil // 返回claims
|
||
} else {
|
||
return nil, InvalidRoleError
|
||
}
|
||
}
|
||
return nil, UnknownError
|
||
}
|
||
|
||
/*
|
||
* 灭活token
|
||
* 参数:
|
||
* 1. token string:需要灭活的token字串
|
||
*/
|
||
func Kickoff(token string) (bool, error) {
|
||
err := am_redis.DelValue(token)
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
return true, nil
|
||
}
|
||
|
||
/*
|
||
* JWT中间件
|
||
*/
|
||
func JWTAuthMiddleware(role string, secret string) func(c *gin.Context) {
|
||
return func(c *gin.Context) {
|
||
authHeader := c.Request.Header.Get("Authorization") // 获取请求头中的Authorization字段
|
||
// 如果没有Authorization字段,直接拦截掉,并返回4006代码
|
||
if authHeader == "" {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"code": ErrCodeNoAuthHeader,
|
||
"msg": "No Authorization header",
|
||
})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 请求头中的token必须是Bearer-Token的格式,否则拦截,返回4007代码
|
||
parts := strings.SplitN(authHeader, " ", 2)
|
||
if !(len(parts) == 2 && parts[0] == "Bearer") {
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"code": ErrCodeMalformedReq,
|
||
"msg": "Wrong Authorization header",
|
||
})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
// 开始认证
|
||
claims, err := ParseToken(parts[1], ClaimToRole(role), secret)
|
||
if err != nil {
|
||
if errors.Is(err, ExpiredButCanSaveError) { // 如果还可以救一救
|
||
tokenClaims := TokenClaims{
|
||
Email: claims.Email,
|
||
Role: claims.Role,
|
||
Exp: claims.Exp,
|
||
Issuer: claims.Issuer,
|
||
SECRET: claims.SECRET,
|
||
}
|
||
|
||
fmt.Println(tokenClaims) // test print
|
||
|
||
token, err := GenToken(&tokenClaims) // 重新生成token
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"code": InternalServerErrorCode,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
}
|
||
|
||
c.Writer.Header().Set("Token", token) // 将token放入请求头后放行
|
||
c.Set("email", claims.Email)
|
||
c.Set("role", claims.Role)
|
||
c.Next()
|
||
return
|
||
} else if errors.Is(err, ExpiredAndDiedError) { // 救不了了
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"code": ErrCodeExpired,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
} else if errors.Is(err, MalformedError) { // 格式不正确
|
||
c.JSON(http.StatusBadRequest, gin.H{
|
||
"code": ErrCodeMalformed,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
} else if errors.Is(err, InvalidRoleError) { // 权限不符
|
||
c.JSON(http.StatusForbidden, gin.H{
|
||
"code": ErrCodeInvalidRole,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
} else if errors.Is(err, NotValidError) {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"code": ErrCodeNotValidYet,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
} else if errors.Is(err, SignatureError) {
|
||
c.JSON(http.StatusUnauthorized, gin.H{
|
||
"code": ErrCodeSignatureInvalid,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
} else if errors.Is(err, UnknownError) {
|
||
c.JSON(http.StatusInternalServerError, gin.H{
|
||
"code": InternalServerErrorCode,
|
||
"msg": err.Error(),
|
||
})
|
||
c.Abort()
|
||
return
|
||
}
|
||
}
|
||
c.Set("email", claims.Email)
|
||
c.Set("role", claims.Role)
|
||
c.Next()
|
||
}
|
||
}
|