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