master
parent
1cef450347
commit
2dd0a0002d
21 changed files with 486 additions and 57 deletions
@ -0,0 +1,60 @@ |
||||
package user |
||||
|
||||
import ( |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"git.diulo.com/mogfee/kit/response" |
||||
"github.com/gin-gonic/gin" |
||||
"context" |
||||
"git.diulo.com/mogfee/kit/middleware" |
||||
) |
||||
|
||||
func RegisterUserHandler(app *gin.Engine, srv UserServer, m ...middleware.Middleware) { |
||||
app.GET("/api/v1/user/list", httpListHandler(srv, m...)) |
||||
app.POST("/api/v1/user/login", httpLoginHandler(srv, m...)) |
||||
} |
||||
func httpListHandler(srv UserServer, m ...middleware.Middleware) func(c *gin.Context) { |
||||
return func(c *gin.Context) { |
||||
var post LoginRequest |
||||
resp := response.New(c) |
||||
if err := resp.BindQuery(&post); err != nil { |
||||
resp.Error(err) |
||||
return |
||||
} |
||||
h := func(ctx context.Context, a any) (any, error) { |
||||
return srv.List(ctx, a.(*LoginRequest)) |
||||
} |
||||
out, err := middleware.HttpMiddleware(c, h, m...)(c, &post) |
||||
if err != nil { |
||||
resp.Error(err) |
||||
} else { |
||||
if v, ok := out.(*LoginResponse); ok { |
||||
resp.Success(v) |
||||
} else { |
||||
resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
func httpLoginHandler(srv UserServer, m ...middleware.Middleware) func(c *gin.Context) { |
||||
return func(c *gin.Context) { |
||||
var post LoginRequest |
||||
resp := response.New(c) |
||||
if err := resp.BindJSON(&post); err != nil { |
||||
resp.Error(err) |
||||
return |
||||
} |
||||
h := func(ctx context.Context, a any) (any, error) { |
||||
return srv.Login(ctx, a.(*LoginRequest)) |
||||
} |
||||
out, err := middleware.HttpMiddleware(c, h, m...)(c, &post) |
||||
if err != nil { |
||||
resp.Error(err) |
||||
} else { |
||||
if v, ok := out.(*LoginResponse); ok { |
||||
resp.Success(v) |
||||
} else { |
||||
resp.Error(errors.InternalServer("RESULT_TYPE_ERROR", "LoginResponse")) |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,92 @@ |
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.17.3
|
||||
// source: third_party/auth/auth.proto
|
||||
|
||||
package auth |
||||
|
||||
import ( |
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect" |
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl" |
||||
descriptorpb "google.golang.org/protobuf/types/descriptorpb" |
||||
reflect "reflect" |
||||
) |
||||
|
||||
const ( |
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) |
||||
) |
||||
|
||||
var file_third_party_auth_auth_proto_extTypes = []protoimpl.ExtensionInfo{ |
||||
{ |
||||
ExtendedType: (*descriptorpb.MethodOptions)(nil), |
||||
ExtensionType: (*string)(nil), |
||||
Field: 1111, |
||||
Name: "auth.auth_key", |
||||
Tag: "bytes,1111,opt,name=auth_key", |
||||
Filename: "third_party/auth/auth.proto", |
||||
}, |
||||
} |
||||
|
||||
// Extension fields to descriptorpb.MethodOptions.
|
||||
var ( |
||||
// optional string auth_key = 1111;
|
||||
E_AuthKey = &file_third_party_auth_auth_proto_extTypes[0] |
||||
) |
||||
|
||||
var File_third_party_auth_auth_proto protoreflect.FileDescriptor |
||||
|
||||
var file_third_party_auth_auth_proto_rawDesc = []byte{ |
||||
0x0a, 0x1b, 0x74, 0x68, 0x69, 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x2f, 0x61, 0x75, |
||||
0x74, 0x68, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x61, |
||||
0x75, 0x74, 0x68, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, |
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, |
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x3a, 0x3a, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6b, 0x65, |
||||
0x79, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, |
||||
0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, |
||||
0x73, 0x18, 0xd7, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x75, 0x74, 0x68, 0x4b, 0x65, |
||||
0x79, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x2e, 0x64, 0x69, 0x75, 0x6c, 0x6f, 0x2e, 0x63, |
||||
0x6f, 0x6d, 0x2f, 0x6d, 0x6f, 0x67, 0x66, 0x65, 0x65, 0x2f, 0x6b, 0x69, 0x74, 0x2f, 0x74, 0x68, |
||||
0x69, 0x72, 0x64, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x79, 0x2f, 0x61, 0x75, 0x74, 0x68, 0x62, 0x06, |
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, |
||||
} |
||||
|
||||
var file_third_party_auth_auth_proto_goTypes = []interface{}{ |
||||
(*descriptorpb.MethodOptions)(nil), // 0: google.protobuf.MethodOptions
|
||||
} |
||||
var file_third_party_auth_auth_proto_depIdxs = []int32{ |
||||
0, // 0: auth.auth_key:extendee -> google.protobuf.MethodOptions
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
0, // [0:1] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
} |
||||
|
||||
func init() { file_third_party_auth_auth_proto_init() } |
||||
func file_third_party_auth_auth_proto_init() { |
||||
if File_third_party_auth_auth_proto != nil { |
||||
return |
||||
} |
||||
type x struct{} |
||||
out := protoimpl.TypeBuilder{ |
||||
File: protoimpl.DescBuilder{ |
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |
||||
RawDescriptor: file_third_party_auth_auth_proto_rawDesc, |
||||
NumEnums: 0, |
||||
NumMessages: 0, |
||||
NumExtensions: 1, |
||||
NumServices: 0, |
||||
}, |
||||
GoTypes: file_third_party_auth_auth_proto_goTypes, |
||||
DependencyIndexes: file_third_party_auth_auth_proto_depIdxs, |
||||
ExtensionInfos: file_third_party_auth_auth_proto_extTypes, |
||||
}.Build() |
||||
File_third_party_auth_auth_proto = out.File |
||||
file_third_party_auth_auth_proto_rawDesc = nil |
||||
file_third_party_auth_auth_proto_goTypes = nil |
||||
file_third_party_auth_auth_proto_depIdxs = nil |
||||
} |
@ -0,0 +1,57 @@ |
||||
package token |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/aes" |
||||
"crypto/cipher" |
||||
"encoding/base64" |
||||
) |
||||
|
||||
// 加密 aes_128_cbc
|
||||
func Encrypt(encryptStr string, key []byte, iv string) (string, error) { |
||||
encryptBytes := []byte(encryptStr) |
||||
block, err := aes.NewCipher(key) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
blockSize := block.BlockSize() |
||||
encryptBytes = pkcs5Padding(encryptBytes, blockSize) |
||||
|
||||
blockMode := cipher.NewCBCEncrypter(block, []byte(iv)) |
||||
encrypted := make([]byte, len(encryptBytes)) |
||||
blockMode.CryptBlocks(encrypted, encryptBytes) |
||||
return base64.URLEncoding.EncodeToString(encrypted), nil |
||||
} |
||||
|
||||
// 解密
|
||||
func Decrypt(decryptStr string, key []byte, iv string) (string, error) { |
||||
decryptBytes, err := base64.URLEncoding.DecodeString(decryptStr) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
block, err := aes.NewCipher(key) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
blockMode := cipher.NewCBCDecrypter(block, []byte(iv)) |
||||
decrypted := make([]byte, len(decryptBytes)) |
||||
|
||||
blockMode.CryptBlocks(decrypted, decryptBytes) |
||||
decrypted = pkcs5UnPadding(decrypted) |
||||
return string(decrypted), nil |
||||
} |
||||
|
||||
func pkcs5Padding(cipherText []byte, blockSize int) []byte { |
||||
padding := blockSize - len(cipherText)%blockSize |
||||
padText := bytes.Repeat([]byte{byte(padding)}, padding) |
||||
return append(cipherText, padText...) |
||||
} |
||||
|
||||
func pkcs5UnPadding(decrypted []byte) []byte { |
||||
length := len(decrypted) |
||||
unPadding := int(decrypted[length-1]) |
||||
return decrypted[:(length - unPadding)] |
||||
} |
@ -0,0 +1,85 @@ |
||||
package token |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"git.diulo.com/mogfee/kit/internal/xuuid" |
||||
"github.com/golang-jwt/jwt/v5" |
||||
"time" |
||||
) |
||||
|
||||
// iss : jwt签发者
|
||||
// sub : 主题名称
|
||||
// aud : 面向的用户,一般都是通过ip或者域名控制
|
||||
// exp : jwt的有效时间(过期时间),这个有效时间必须要大于签发时间,对于交互接口来说,建议是预设5秒
|
||||
// nbf : 在什么时候jwt开始生效(在此之前不可用)
|
||||
// iat : jwt的签发时间
|
||||
// jti : 唯一标识,主要用来回避被重复使用攻击
|
||||
|
||||
type UserInfo struct { |
||||
UserId int64 |
||||
UserName string |
||||
UserType string |
||||
Permissions []string |
||||
UniqueId string |
||||
} |
||||
|
||||
func GetToken(key string, info *UserInfo) (token string, uniqId string, err error) { |
||||
uniqId = xuuid.UUID() |
||||
info.UniqueId = uniqId |
||||
var tokenStr string |
||||
ctime := time.Now().Unix() |
||||
tokenStr, err = jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.MapClaims{ |
||||
//jwt签发者
|
||||
//"iss": "auth.diulo.com",
|
||||
//主题名称
|
||||
//"sub": "www.diulo.com",
|
||||
// 面向的用户,一般都是通过ip或者域名控制
|
||||
//"aud": "api.diulo.com",
|
||||
//jwt的有效时间(过期时间),这个有效时间必须要大于签发时间,对于交互接口来说,建议是预设5秒
|
||||
"exp": ctime + 7200, |
||||
//在什么时候jwt开始生效(在此之前不可用)
|
||||
"nbf": ctime, |
||||
//jwt的签发时间
|
||||
"iat": ctime, |
||||
//唯一标识,主要用来回避被重复使用攻击
|
||||
"jti": uniqId, |
||||
"info": info, |
||||
}).SignedString([]byte(key)) |
||||
if err != nil { |
||||
return |
||||
} |
||||
token, err = Encrypt(tokenStr, []byte(key), key) |
||||
return |
||||
} |
||||
|
||||
func Parse(key string, tokenStr string) (*UserInfo, error) { |
||||
if tokenStr == "" { |
||||
return nil, errors.Unauthorized("TOKEN_ERROR", "") |
||||
} |
||||
str, err := Decrypt(tokenStr, []byte(key), key) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
token, err := jwt.Parse(str, func(token *jwt.Token) (interface{}, error) { |
||||
return []byte(key), nil |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if token.Valid { |
||||
row := struct { |
||||
Info *UserInfo |
||||
}{} |
||||
b, _ := json.Marshal(token.Claims) |
||||
if err = json.Unmarshal(b, &row); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return row.Info, nil |
||||
} else if errors.Is(err, jwt.ErrTokenExpired) || errors.Is(err, jwt.ErrTokenNotValidYet) { |
||||
return nil, errors.Unauthorized("TOKEN_EXPIRED", "") |
||||
} |
||||
return nil, errors.Unauthorized("TOKEN_ERROR", "") |
||||
} |
@ -0,0 +1,18 @@ |
||||
package token |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func TestGetToken(t *testing.T) { |
||||
tokenKey := "JssLx22bjQwnyqby" |
||||
token, uniqId, err := GetToken(tokenKey, &UserInfo{ |
||||
UserId: 111, |
||||
UserType: "admin", |
||||
Permissions: []string{"user:search"}, |
||||
}) |
||||
fmt.Println(token) |
||||
fmt.Println(uniqId, err) |
||||
fmt.Println(Parse(tokenKey, token)) |
||||
} |
@ -0,0 +1,14 @@ |
||||
package xuuid |
||||
|
||||
import ( |
||||
"github.com/google/uuid" |
||||
"strings" |
||||
) |
||||
|
||||
func UUID() string { |
||||
u, err := uuid.NewUUID() |
||||
if err != nil { |
||||
return "" |
||||
} |
||||
return strings.Join(strings.Split(u.String(), "-"), "") |
||||
} |
@ -1,28 +0,0 @@ |
||||
package middleware |
||||
|
||||
import ( |
||||
"context" |
||||
"git.diulo.com/mogfee/kit/constants" |
||||
"google.golang.org/grpc/metadata" |
||||
) |
||||
|
||||
type ValidateUser interface { |
||||
ValidateUser(string) (int64, error) |
||||
} |
||||
|
||||
func JWT(validate ValidateUser) Middleware { |
||||
return func(handler Handler) Handler { |
||||
return func(ctx context.Context, a any) (any, error) { |
||||
var token string |
||||
if md, ok := metadata.FromIncomingContext(ctx); ok { |
||||
token = md.Get("token")[0] |
||||
} |
||||
userId, err := validate.ValidateUser(ctx.Value(token).(string)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
ctx = context.WithValue(ctx, constants.UserIdKey{}, userId) |
||||
return handler(ctx, a) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,96 @@ |
||||
package jwt |
||||
|
||||
import ( |
||||
"context" |
||||
"git.diulo.com/mogfee/kit/errors" |
||||
"git.diulo.com/mogfee/kit/internal/token" |
||||
"git.diulo.com/mogfee/kit/middleware" |
||||
"git.diulo.com/mogfee/kit/transport" |
||||
) |
||||
|
||||
type userIdKey struct{} |
||||
type authKey struct { |
||||
} |
||||
type JwtOption func(o *options) |
||||
|
||||
func WithJwtKey(jwtKey string) JwtOption { |
||||
return func(o *options) { |
||||
o.jwtKey = jwtKey |
||||
} |
||||
} |
||||
|
||||
func WithValidate(validate func(authKey string) error) JwtOption { |
||||
return func(o *options) { |
||||
o.validate = validate |
||||
} |
||||
} |
||||
|
||||
func WithValidatePermission(validatePermission func(permissions []string, key string) bool) JwtOption { |
||||
return func(o *options) { |
||||
o.validatePermission = validatePermission |
||||
} |
||||
} |
||||
|
||||
type options struct { |
||||
jwtKey string |
||||
validate func(authKey string) error |
||||
validatePermission func(validatePermission []string, key string) bool |
||||
} |
||||
|
||||
func JWT(opts ...JwtOption) middleware.Middleware { |
||||
var cfg = &options{ |
||||
jwtKey: "JssLx22bjQwnyqby", |
||||
validatePermission: InSlice, |
||||
} |
||||
for _, o := range opts { |
||||
o(cfg) |
||||
} |
||||
|
||||
return func(handler middleware.Handler) middleware.Handler { |
||||
return func(ctx context.Context, a any) (any, error) { |
||||
var tokenStr string |
||||
if tr, ok := transport.FromServerContext(ctx); ok { |
||||
tokenStr = tr.RequestHeader().Get("token") |
||||
} |
||||
userInfo, err := token.Parse(cfg.jwtKey, tokenStr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
permission := FromAuthKeyContext(ctx) |
||||
if permission != "" { |
||||
if !cfg.validatePermission(userInfo.Permissions, permission) { |
||||
return nil, errors.Unauthorized("TOKEN_PERMISSION_BAD", "") |
||||
} |
||||
} |
||||
if cfg.validate != nil { |
||||
if err = cfg.validate(userInfo.UniqueId); err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
ctx = SetUserContext(ctx, userInfo) |
||||
return handler(ctx, a) |
||||
} |
||||
} |
||||
} |
||||
func InSlice(validatePermission []string, key string) bool { |
||||
for _, e := range validatePermission { |
||||
if e == key { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
func SetUserContext(ctx context.Context, user *token.UserInfo) context.Context { |
||||
return context.WithValue(ctx, userIdKey{}, user) |
||||
} |
||||
func FromUserContext(ctx context.Context) (user *token.UserInfo, ok bool) { |
||||
user, ok = ctx.Value(userIdKey{}).(*token.UserInfo) |
||||
return |
||||
} |
||||
|
||||
func SetAuthKeyContext(ctx context.Context, key string) context.Context { |
||||
return context.WithValue(ctx, authKey{}, key) |
||||
} |
||||
func FromAuthKeyContext(ctx context.Context) string { |
||||
return ctx.Value(authKey{}).(string) |
||||
} |
Loading…
Reference in new issue