Compare commits

...

7 Commits

7 changed files with 187 additions and 49 deletions

View File

@ -28,7 +28,7 @@ func New(secret string) {
func GenerateTokenAes(id uint, identity, client, role string, owner any, extend map[string]string) (string, error) {
if !(JwtSecretLen == 16 || JwtSecretLen == 24 || JwtSecretLen == 32) {
return "", errcode.ErrJWTSecretKey
return "", errcode.ErrTokenSecretKey
}
expireTime := time.Now().Add(vars.JwtExpire)
claims := types.JwtClaims{
@ -43,7 +43,7 @@ func GenerateTokenAes(id uint, identity, client, role string, owner any, extend
byte, err := json.Marshal(claims)
if err != nil {
return "", errcode.ErrJWTJsonEncode
return "", errcode.ErrTokenJsonEncode
}
token, err := AesEncryptCBC(byte)
@ -59,7 +59,7 @@ func AesEncryptCBC(plan []byte) (string, error) {
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, err := aes.NewCipher(JwtSecret)
if err != nil {
return "", errcode.ErrJWTSecretKey
return "", errcode.ErrTokenSecretKey
}
// 获取秘钥块的长度
blockSize := block.BlockSize()
@ -76,17 +76,17 @@ func AesEncryptCBC(plan []byte) (string, error) {
func AesDecryptCBC(cryted string) (b []byte, err error) {
if (JwtSecretLen == 16 || JwtSecretLen == 24 || JwtSecretLen == 32) == false {
return nil, errcode.ErrJWTSecretKey
return nil, errcode.ErrTokenSecretKey
}
// 转成字节数组
crytedByte, err := base64.StdEncoding.DecodeString(cryted)
if err != nil {
return nil, errcode.ErrJWTBase64Decode
return nil, errcode.ErrTokenBase64Decode
}
// 分组秘钥
block, err := aes.NewCipher(JwtSecret)
if err != nil {
return nil, errcode.ErrJWTSecretKey
return nil, errcode.ErrTokenSecretKey
}
// 获取秘钥块的长度
blockSize := block.BlockSize()
@ -99,7 +99,7 @@ func AesDecryptCBC(cryted string) (b []byte, err error) {
// 去补全码
orig = PKCS7UnPadding(orig, blockSize)
if orig == nil {
return nil, errcode.ErrJWTAuthParseFail
return nil, errcode.ErrTokenAuthParseFail
}
return orig, nil
}
@ -152,12 +152,12 @@ func ParseTokenAes(token string) (*types.JwtClaims, error) {
var ac *types.JwtClaims
err = json.Unmarshal(data, &ac)
if err != nil {
return nil, errcode.ErrJWTAuthParseFail
return nil, errcode.ErrTokenAuthParseFail
}
expireTime := time.Now().Unix()
if expireTime > ac.ExpiresAt {
return nil, errcode.ErrJWTAuthExpire
return nil, errcode.ErrTokenAuthExpire
}
return ac, nil

98
crypto/token/jwt.go Normal file
View File

@ -0,0 +1,98 @@
package token
import (
"encoding/base64"
"encoding/json"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/vars"
"github.com/golang-jwt/jwt/v5"
)
type Claims struct {
ID uint `json:"id"`
Identity string `json:"identity"`
Extend map[string]string `json:"extend"`
Client string `json:"client"`
Owner any `json:"owner"`
Role string `json:"role"`
jwt.RegisteredClaims // v5版本新加的方法
}
type tokenJwt struct {
SecretKey string
}
func New(secretKey string) *tokenJwt {
return &tokenJwt{SecretKey: secretKey}
}
// 生成JWT
func (t *tokenJwt) GenerateJwt(id uint, identity, client, role string, owner any, extend map[string]string) (string, error) {
keyLen := len(t.SecretKey)
if !(keyLen == 16 || keyLen == 24 || keyLen == 32) {
return "", errcode.ErrTokenSecretKey
}
now := time.Now()
claims := Claims{
ID: id,
Identity: identity,
Client: client,
Extend: extend,
Owner: owner,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(now.Add(vars.JwtExpire)), // 过期时间24小时
IssuedAt: jwt.NewNumericDate(now), // 签发时间
NotBefore: jwt.NewNumericDate(now), // 生效时间
},
}
// 使用HS256签名算法
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
s, err := token.SignedString([]byte(t.SecretKey))
if err != nil {
return "", errcode.String(errcode.ErrTokenGenerate, err.Error())
}
return s, nil
}
// 解析JWT
func (t *tokenJwt) ParseJwt(tokenstring string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenstring, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(t.SecretKey), nil
})
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
} else {
return nil, errcode.String(errcode.ErrTokenParse, err.Error())
}
}
// 验证JWT是否过期
func (t *tokenJwt) IsExpired(tokenstring string) (bool, error) {
// 分割JWT的三个部分
parts := strings.Split(tokenstring, ".")
if len(parts) != 3 {
return true, errcode.ErrTokenDataInvalid
}
// 解码Payload部分
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return true, errcode.String(errcode.ErrTokenBase64Decode, err.Error())
}
// 解析JSON
var claims jwt.RegisteredClaims
if err := json.Unmarshal(payload, &claims); err != nil {
return true, errcode.String(errcode.ErrTokenJsonDecode, err.Error())
}
// 检查过期时间
currentTime := time.Now().Unix()
return claims.ExpiresAt.Unix() < currentTime, nil
}

View File

@ -3,8 +3,6 @@
package errcode
import (
"strings"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@ -33,18 +31,22 @@ var (
ErrRecordNotFound = NewError(1112, "Record Not Found") // 记录未找到
)
// JWT认证相关错误码起始码:1300
// Token认证相关错误码,起始码:1300
var (
ErrJWTAuthNotFound = NewError(1301, "JWT Authorization Not Found") // JWT授权未找到
ErrJWTBase64Decode = NewError(1302, "JWT Authorization Base64 Decode Error") // JWT Base64解码错误
ErrJWTAuthParseFail = NewError(1303, "JWT Authorization Fail") // JWT授权解析失败
ErrJWTAuthKeyId = NewError(1304, "JWT Key:Id Incorrect") // JWT密钥ID错误
ErrJWTAuthKeyIdentity = NewError(1305, "JWT Key:Identity Incorrect") // JWT密钥身份错误
ErrJWTAuthTokenChanged = NewError(1306, "JWT Authorization Changed") // JWT授权已变更
ErrJWTAuthExpire = NewError(1307, "JWT Authorization Expire") // JWT授权已过期
ErrJWTJsonDecode = NewError(1308, "JWT Authorization JSON Decode Error") // JWT JSON解码错误
ErrJWTJsonEncode = NewError(1309, "JWT Authorization JSON Encode Error") // JWT JSON编码错误
ErrJWTSecretKey = NewError(1310, "JWT SecretKey Error") // JWT密钥错误
ErrTokenAuthNotFound = NewError(1301, "Token Authorization Not Found") // Token授权未找到
ErrTokenDataInvalid = NewError(1302, "Token Authorization Data Invalid") // Token授权数据无效
ErrTokenBase64Decode = NewError(1303, "Token Authorization Base64 Decode Error") // Token Base64解码错误
ErrTokenAuthParseFail = NewError(1304, "Token Authorization Fail") // Token授权解析失败
ErrTokenAuthKeyId = NewError(1305, "Token Key:Id Incorrect") // Token密钥ID错误
ErrTokenAuthKeyIdentity = NewError(1306, "Token Key:Identity Incorrect") // Token密钥身份错误
ErrTokenAuthTokenChanged = NewError(1307, "Token Authorization Changed") // Token授权已变更
ErrTokenAuthExpire = NewError(1308, "Token Authorization Expire") // Token授权已过期
ErrTokenJsonDecode = NewError(1309, "Token Authorization JSON Decode Error") // Token JSON解码错误
ErrTokenJsonEncode = NewError(1310, "Token Authorization JSON Encode Error") // Token JSON编码错误
ErrTokenSecretKey = NewError(1311, "Token SecretKey Error") // Token密钥错误
ErrTokenSecretKeyNotFound = NewError(1312, "Token SecretKey Not Found") // Token密钥未找到
ErrTokenGenerate = NewError(1313, "Generate Token Fail") // 生成令牌失败
ErrTokenParse = NewError(1314, "Parse Token Fail") // 解析令牌失败
)
// 基础设施相关错误码,起始码:1500
@ -98,6 +100,15 @@ func ErrFatal(code int, msg string) error {
// code: 错误码
// msg: 错误消息,会自动转换为大写
func ErrNotFound(code int, msg string) error {
AllErrors[code] = strings.ToUpper(msg)
return status.New(codes.Code(code), strings.ToUpper(msg)).Err()
AllErrors[code] = msg
return status.New(codes.Code(code), msg).Err()
}
// IsErr 检查错误是否与指定的错误匹配
func IsErr(err, target error) bool {
return status.Code(err) == status.Code(target)
}
func String(err error, msg string) error {
return status.New(status.Code(err), err.Error()+", "+msg).Err()
}

View File

@ -3,6 +3,8 @@
package infra
import (
"time"
"github.com/gin-gonic/gin"
"google.golang.org/grpc/status"
)
@ -13,7 +15,8 @@ var Response Reply
type Reply struct {
Code int32 `json:"code"` // 响应码
Message string `json:"message"` // 响应消息
Result any `json:"result"` // 响应数据
Details any `json:"details"` // 响应数据
Timeseq int64 `json:"timeseq"` // 时间戳序列
}
// Success 返回成功响应
@ -21,10 +24,11 @@ type Reply struct {
// data: 响应数据
func (reply *Reply) Success(ctx *gin.Context, data any) {
reply.Code = 0
reply.Result = data
reply.Details = data
reply.Message = ""
reply.Timeseq = time.Now().UnixMilli()
if data == nil {
reply.Result = ""
reply.Details = ""
}
ctx.JSON(200, reply)
}
@ -34,7 +38,7 @@ func (reply *Reply) Success(ctx *gin.Context, data any) {
// err: 错误对象
func (reply *Reply) Error(ctx *gin.Context, err error) {
reply.Code = 500
reply.Result = ""
reply.Details = ""
// 默认状态码为500
e, ok := status.FromError(err)
if ok {

View File

@ -41,6 +41,23 @@ var (
once sync.Once
)
// 初始化Logger配置
func New(cfg *conf.LogConf) {
if cfg == nil {
cfg = &conf.LogConf{
Name: strings.ToLower(vars.ServiceKey),
Level: vars.LogLevel(vars.DEBUG),
Dir: "./logs/",
Endpoint: "",
Console: true,
File: true,
Remote: false,
}
}
InitLogger(cfg)
}
// InitLogger 初始化全局日志器
func InitLogger(cfg *conf.LogConf) error {
var err error
@ -70,7 +87,7 @@ func NewLogger(cfg *conf.LogConf) (*Logger, error) {
multiWriter := io.MultiWriter(consoleWriter, fileWriter)
logger := &Logger{
level: vars.LogLevel(cfg.Level),
level: cfg.Level,
fileWriter: fileWriter,
consoleWriter: consoleWriter,
logDir: cfg.Dir,

View File

@ -6,9 +6,9 @@ import (
"encoding/json"
"log"
"net/http"
"time"
"git.apinb.com/bsm-sdk/core/crypto/encipher"
"git.apinb.com/bsm-sdk/core/crypto/token"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"github.com/gin-gonic/gin"
@ -27,19 +27,18 @@ func JwtAuth(time_verify bool) gin.HandlerFunc {
c.Abort()
return
}
// 提取Token
claims, err := encipher.ParseTokenAes(authHeader)
if err != nil || claims == nil {
log.Printf("提取token异常:%v\n", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is required"})
c.Abort()
return
}
// 检测是否需要验证token时间
if time_verify {
// 判断时间claims.ExpiresAt
if time.Now().Unix() > claims.ExpiresAt {
isExpire, err := token.New(env.Runtime.JwtSecretKey).IsExpired(authHeader)
if err != nil {
log.Println("token解析异常:", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is required"})
c.Abort()
return
}
if isExpire {
log.Println("token过期请重新获取:", "Token has expired")
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token has expired"})
c.Abort()
@ -47,6 +46,15 @@ func JwtAuth(time_verify bool) gin.HandlerFunc {
}
}
// 提取Token
claims, err := token.New(env.Runtime.JwtSecretKey).ParseJwt(authHeader)
if err != nil || claims == nil {
log.Printf("提取token异常:%v\n", err)
c.JSON(http.StatusUnauthorized, gin.H{"error": "Token is required"})
c.Abort()
return
}
// 将解析后的 Token 存储到上下文中
c.Set("Auth", claims)
// 如果 Token 有效,继续处理请求
@ -60,8 +68,8 @@ func JwtAuth(time_verify bool) gin.HandlerFunc {
func ParseAuth(c *gin.Context) (*types.JwtClaims, error) {
claims, ok := c.Get("Auth")
if !ok {
log.Printf("获取登录信息异常: %v", errcode.ErrJWTAuthNotFound)
return nil, errcode.ErrJWTAuthNotFound
log.Printf("获取登录信息异常: %v", errcode.ErrTokenAuthNotFound)
return nil, errcode.ErrTokenAuthNotFound
}
json_claims, err := json.Marshal(claims)

View File

@ -3,9 +3,9 @@ package service
import (
"context"
"git.apinb.com/bsm-sdk/core/crypto/encipher"
"git.apinb.com/bsm-sdk/core/crypto/token"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/types"
"git.apinb.com/bsm-sdk/core/utils"
"google.golang.org/grpc/metadata"
)
@ -16,19 +16,19 @@ type ParseOptions struct {
MustPrivateAllow bool // 是否只允许私有IP访问
}
func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*types.JwtClaims, error) {
func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*token.Claims, error) {
// 解析metada中的信息并验证
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, errcode.ErrJWTAuthNotFound
return nil, errcode.ErrTokenAuthNotFound
}
var Authorizations []string = md.Get("authorization")
if len(Authorizations) == 0 || Authorizations[0] == "" {
return nil, errcode.ErrJWTAuthNotFound
return nil, errcode.ErrTokenAuthNotFound
}
claims, err := encipher.ParseTokenAes(Authorizations[0])
claims, err := token.New(env.Runtime.JwtSecretKey).ParseJwt(Authorizations[0])
if err != nil {
return nil, err
}
@ -48,7 +48,7 @@ func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*types.JwtClaims, er
}
func checkRole(claims *types.JwtClaims, roleKey, roleValue string) bool {
func checkRole(claims *token.Claims, roleKey, roleValue string) bool {
if roleValue == "" {
return true
}