feat(app): 添加 token 校验功能和用户信息获取接口

- 新增 AuthToken 方法用于校验用户 token
- 在 app 接口中添加 AuthToken 方法定义
- 在 defaultApp 结构体中实现 AuthToken 方法
- 修改 GetVerifyCode、Login、Register 接口的 tags 为 user_public
- 新增 /user POST 接口用于获取用户详细信息
- 在 app.pb.go 文件中新增 AuthInfoResp、AuthReq 等相关结构体定义
- 更新 proto 文件中的消息类型索引以适应新增结构体
- 新增 ClaimStrings 和 NumericDate 结构体支持 JWT 相关字段
- 新增 RegisteredClaims 结构体用于表示 JWT 声明集
This commit is contained in:
huanglei19951029 2025-12-12 17:54:55 +08:00
parent 538f6e7e39
commit 67eca4d18e
23 changed files with 1067 additions and 155 deletions

View File

@ -22,7 +22,7 @@
"post": { "post": {
"description": "GetVerifyCode | 获取验证码", "description": "GetVerifyCode | 获取验证码",
"tags": [ "tags": [
"user" "user_public"
], ],
"summary": "GetVerifyCode | 获取验证码", "summary": "GetVerifyCode | 获取验证码",
"operationId": "GetVerifyCode", "operationId": "GetVerifyCode",
@ -68,7 +68,7 @@
"post": { "post": {
"description": "Login | 登录", "description": "Login | 登录",
"tags": [ "tags": [
"user" "user_public"
], ],
"summary": "Login | 登录", "summary": "Login | 登录",
"operationId": "Login", "operationId": "Login",
@ -96,7 +96,7 @@
"post": { "post": {
"description": "Register | 注册", "description": "Register | 注册",
"tags": [ "tags": [
"user" "user_public"
], ],
"summary": "Register | 注册", "summary": "Register | 注册",
"operationId": "Register", "operationId": "Register",
@ -119,6 +119,34 @@
} }
} }
} }
},
"/user": {
"post": {
"description": "Get userInfo detail by ID | 获取用户信息",
"tags": [
"user"
],
"summary": "Get userInfo detail by ID | 获取用户信息",
"operationId": "GetUserInfoById",
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"type": "object",
"$ref": "#/definitions/IDReq"
}
}
],
"responses": {
"200": {
"description": "UserInfo",
"schema": {
"$ref": "#/definitions/UserInfo"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
@ -868,6 +896,10 @@
}, },
"x-go-package": "mingyang-admin-app-api/internal/types" "x-go-package": "mingyang-admin-app-api/internal/types"
}, },
"UserInfoReq": {
"type": "object",
"x-go-package": "mingyang-admin-app-api/internal/types"
},
"VerifyCodeReq": { "VerifyCodeReq": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -50,7 +50,6 @@ type (
} }
RegisterResp{ RegisterResp{
// token信息 // token信息
AuthToken *AuthToken `json:"authToken,optional"` AuthToken *AuthToken `json:"authToken,optional"`
@ -109,13 +108,16 @@ type (
AuthToken *AuthToken AuthToken *AuthToken
UserInfo *UserInfo UserInfo *UserInfo
} }
UserInfoReq{
}
) )
@server ( @server (
jwt: Auth group: user_public
group: user
middleware: Authority
) )
service App { service App {
// GetVerifyCode | 获取验证码 // GetVerifyCode | 获取验证码
@handler getVerifyCode @handler getVerifyCode
@ -128,3 +130,15 @@ service App {
post /login (LoginReq) returns (LoginResp) post /login (LoginReq) returns (LoginResp)
} }
@server (
group: user
middleware: Authority
)
service App {
// Get userInfo detail by ID | 获取用户信息
@handler getUserInfoById
post /user (IDReq) returns (UserInfo)
}

View File

@ -3,7 +3,6 @@ Host: 0.0.0.0
Port: 9902 Port: 9902
Timeout: 4000 Timeout: 4000
CROSConf: CROSConf:
Address: '*' Address: '*'
@ -29,6 +28,9 @@ AppRpc:
Target: 127.0.0.1:9901 Target: 127.0.0.1:9901
Enabled: true Enabled: true
JWTConf:
AccessSecret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
I18nConf: I18nConf:
Dir: Dir:

View File

@ -3,6 +3,8 @@ module mingyang-admin-app-api
go 1.25.3 go 1.25.3
require ( require (
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/pkg/errors v0.9.1
github.com/redis/go-redis/v9 v9.16.0 github.com/redis/go-redis/v9 v9.16.0
github.com/saas-mingyang/mingyang-admin-common v0.3.3 github.com/saas-mingyang/mingyang-admin-common v0.3.3
github.com/zeromicro/go-zero v1.9.1 github.com/zeromicro/go-zero v1.9.1

View File

@ -88,6 +88,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
@ -149,6 +151,8 @@ github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7s
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c= github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

View File

@ -16,4 +16,9 @@ type Config struct {
CROSConf config.CROSConf CROSConf config.CROSConf
I18nConf i18n.Conf I18nConf i18n.Conf
AppRpc zrpc.RpcClientConf AppRpc zrpc.RpcClientConf
Auth JWTConfig `json:"JWTConf"`
}
type JWTConfig struct {
AccessSecret string
} }

View File

@ -8,6 +8,7 @@ import (
base "mingyang-admin-app-api/internal/handler/base" base "mingyang-admin-app-api/internal/handler/base"
user "mingyang-admin-app-api/internal/handler/user" user "mingyang-admin-app-api/internal/handler/user"
user_public "mingyang-admin-app-api/internal/handler/user_public"
"mingyang-admin-app-api/internal/svc" "mingyang-admin-app-api/internal/svc"
"github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/rest"
@ -24,27 +25,36 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
}, },
) )
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodPost,
Path: "/get/verifyCode",
Handler: user_public.GetVerifyCodeHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/register",
Handler: user_public.RegisterHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/login",
Handler: user_public.LoginHandler(serverCtx),
},
},
)
server.AddRoutes( server.AddRoutes(
rest.WithMiddlewares( rest.WithMiddlewares(
[]rest.Middleware{serverCtx.Authority}, []rest.Middleware{serverCtx.Authority},
[]rest.Route{ []rest.Route{
{ {
Method: http.MethodPost, Method: http.MethodPost,
Path: "/get/verifyCode", Path: "/user",
Handler: user.GetVerifyCodeHandler(serverCtx), Handler: user.GetUserInfoByIdHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/register",
Handler: user.RegisterHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/login",
Handler: user.LoginHandler(serverCtx),
}, },
}..., }...,
), ),
//rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
) )
} }

View File

@ -0,0 +1,45 @@
package user
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
)
// swagger:route post /user user GetUserInfoById
//
// Get userInfo detail by ID | 获取用户信息
//
// Get userInfo detail by ID | 获取用户信息
//
// Parameters:
// + name: body
// require: true
// in: body
// type: IDReq
//
// Responses:
// 200: UserInfo
func GetUserInfoByIdHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.IDReq
if err := httpx.Parse(r, &req, true); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := user.NewGetUserInfoByIdLogic(r.Context(), svcCtx)
resp, err := l.GetUserInfoById(&req)
if err != nil {
err = svcCtx.Trans.TransError(r.Context(), err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,11 +1,11 @@
package user package user
import ( import (
"mingyang-admin-app-api/internal/logic/user"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user"
"mingyang-admin-app-api/internal/svc" "mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types" "mingyang-admin-app-api/internal/types"
) )

View File

@ -0,0 +1,45 @@
package user
import (
"mingyang-admin-app-api/internal/logic/user"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
)
// swagger:route post /login user Login
//
// Login | 登录
//
// Login | 登录
//
// Parameters:
// + name: body
// require: true
// in: body
// type: LoginReq
//
// Responses:
// 200: LoginResp
func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.LoginReq
if err := httpx.Parse(r, &req, true); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := user.NewLoginLogic(r.Context(), svcCtx)
resp, err := l.Login(&req)
if err != nil {
err = svcCtx.Trans.TransError(r.Context(), err)
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,11 +1,11 @@
package user package user
import ( import (
"mingyang-admin-app-api/internal/logic/user"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"mingyang-admin-app-api/internal/logic/user"
"mingyang-admin-app-api/internal/svc" "mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types" "mingyang-admin-app-api/internal/types"
) )

View File

@ -0,0 +1,30 @@
package user
import (
"context"
"mingyang-admin-app-api/internal/svc"
"mingyang-admin-app-api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type GetUserInfoByIdLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUserInfoByIdLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserInfoByIdLogic {
return &GetUserInfoByIdLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserInfoByIdLogic) GetUserInfoById(req *types.IDReq) (resp *types.UserInfo, err error) {
// todo: add your logic here and delete this line
return
}

View File

@ -1,28 +1,170 @@
// internal/middleware/authority.go
package middleware package middleware
import ( import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"mingyang-admin-app-api/internal/types"
"mingyang-admin-app-rpc/appclient"
"mingyang-admin-app-rpc/types/app"
"net/http" "net/http"
"strings"
"time"
)
// 定义上下文键类型
type contextKey string
const (
UserIDKey contextKey = "user_id"
UserInfoKey contextKey = "user_info"
) )
type AuthorityMiddleware struct { type AuthorityMiddleware struct {
Rds redis.UniversalClient Rds redis.UniversalClient
AppRpc appclient.App
} }
func NewAuthorityMiddleware(rds redis.UniversalClient) *AuthorityMiddleware { // NewAuthorityMiddleware 创建认证中间件
func NewAuthorityMiddleware(appRpc appclient.App, rds redis.UniversalClient) *AuthorityMiddleware {
return &AuthorityMiddleware{ return &AuthorityMiddleware{
Rds: rds, AppRpc: appRpc,
Rds: rds,
} }
} }
// writeResult 写入统一格式的响应
func writeResult(w http.ResponseWriter, statusCode int, result *types.BaseDataInfo) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(result)
}
// writeError 写入错误响应(简化版)
func writeError(w http.ResponseWriter, statusCode int, message string) {
writeResult(w, statusCode, &types.BaseDataInfo{
Msg: message,
Code: 500,
})
}
// Handle 中间件处理函数
func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { func (m *AuthorityMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
/* // get the path startTime := time.Now()
obj := r.URL.Path
// get the method // 检查是否为公开路径(可选)
act := r.Method if m.isPublicPath(r.URL.Path) {
// get the role id next(w, r)
roleIds := strings.Split(r.Context().Value("roleId").(string), ",")*/ return
}
// 1. 从 Authorization Header 中提取 Bearer Token
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
writeError(w, 401, "Authorization header is required")
return
}
// 2. 验证 Bearer Token 格式
if !strings.HasPrefix(authHeader, "Bearer ") {
writeError(w, 401, "Invalid authorization format, must be 'Bearer <token>'")
return
}
// 3. 提取 Token
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
if tokenString == "" {
writeError(w, 401, "Token cannot be empty")
return
}
// 4. 检查 Redis 缓存(可选)
if m.Rds != nil {
cacheKey := fmt.Sprintf("token:%s", tokenString)
cachedUserID, err := m.Rds.Get(r.Context(), cacheKey).Result()
if err == nil && cachedUserID != "" {
// 从缓存中获取到用户ID
ctx := context.WithValue(r.Context(), UserIDKey, cachedUserID)
r = r.WithContext(ctx)
next(w, r)
return
}
}
// 5. 调用 RPC 验证 Token
fmt.Printf("Validating token: %s\n", tokenString)
token, err := m.AppRpc.AuthToken(r.Context(), &app.AuthReq{Token: tokenString})
if err != nil {
fmt.Printf("Error validating token: %v\n", err)
// 根据错误类型返回不同的错误信息
var jwtErr *jwt.ValidationError
if errors.As(err, &jwtErr) {
switch {
case jwtErr.Errors&jwt.ValidationErrorExpired != 0:
writeError(w, 401, "Token has expired")
case jwtErr.Errors&jwt.ValidationErrorMalformed != 0:
writeError(w, 401, "Invalid token format")
case jwtErr.Errors&jwt.ValidationErrorSignatureInvalid != 0:
writeError(w, 401, "Invalid token signature")
case jwtErr.Errors&jwt.ValidationErrorNotValidYet != 0:
writeError(w, 401, "Token not valid yet")
default:
writeError(w, 401, "Invalid token")
}
} else {
// 网络错误或其他错误
writeError(w, 500, "Token validation service unavailable")
}
return
}
// 6. 获取用户ID
id := token.UserId
// 7. 缓存到 Redis可选
if m.Rds != nil {
cacheKey := fmt.Sprintf("token:%s", tokenString)
// 设置缓存过期时间30分钟
m.Rds.Set(r.Context(), cacheKey, id, 30*time.Minute)
}
// 8. 设置到上下文
ctx := r.Context()
ctx = context.WithValue(ctx, UserIDKey, id)
ctx = context.WithValue(ctx, UserInfoKey, token)
// 9. 记录请求日志(可选)
fmt.Printf("[%s] %s - UserID: %d - Duration: %v\n",
time.Now().Format("2006-01-02 15:04:05"),
r.URL.Path,
id,
time.Since(startTime))
// 10. 继续处理请求
r = r.WithContext(ctx)
next(w, r) next(w, r)
} }
} }
// isPublicPath 检查是否为公开路径
func (m *AuthorityMiddleware) isPublicPath(path string) bool {
publicPaths := []string{
"/api/login",
"/api/register",
"/api/public/",
"/health",
"/metrics",
}
for _, publicPath := range publicPaths {
if strings.HasPrefix(path, publicPath) {
return true
}
}
return false
}

View File

@ -1,6 +1,7 @@
package svc package svc
import ( import (
"github.com/redis/go-redis/v9"
"github.com/saas-mingyang/mingyang-admin-common/i18n" "github.com/saas-mingyang/mingyang-admin-common/i18n"
"github.com/zeromicro/go-zero/rest" "github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc" "github.com/zeromicro/go-zero/zrpc"
@ -15,15 +16,18 @@ type ServiceContext struct {
Trans *i18n.Translator Trans *i18n.Translator
Authority rest.Middleware Authority rest.Middleware
AppRpc appclient.App AppRpc appclient.App
Redis redis.UniversalClient
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
trans := i18n.NewTranslator(c.I18nConf, i18n2.LocaleFS) trans := i18n.NewTranslator(c.I18nConf, i18n2.LocaleFS)
appRpc := appclient.NewApp(zrpc.NewClientIfEnable(c.AppRpc))
rds := c.RedisConf.MustNewUniversalRedis() rds := c.RedisConf.MustNewUniversalRedis()
return &ServiceContext{ return &ServiceContext{
Authority: middleware.NewAuthorityMiddleware(rds).Handle, Authority: middleware.NewAuthorityMiddleware(appRpc, rds).Handle,
Config: c, Config: c,
Redis: rds,
Trans: trans, Trans: trans,
AppRpc: appclient.NewApp(zrpc.NewClientIfEnable(c.AppRpc)), AppRpc: appRpc,
} }
} }

View File

@ -362,3 +362,7 @@ type LoginResp struct {
AuthToken *AuthToken AuthToken *AuthToken
UserInfo *UserInfo UserInfo *UserInfo
} }
// swagger:model UserInfoReq
type UserInfoReq struct {
}

View File

@ -27,6 +27,18 @@ enum VerifyCodeType {
CHANGE_PAY_PASSWORD = 8; CHANGE_PAY_PASSWORD = 8;
} }
// AuthInfoResp
message AuthInfoResp {
uint64 user_id = 1;
string type = 2;
RegisteredClaims claims = 3;
}
message AuthReq {
string token = 1;
google.protobuf.Timestamp time = 2;
}
// //
message AuthToken { message AuthToken {
string access_token = 1; string access_token = 1;
@ -54,6 +66,11 @@ message BaseUUIDResp {
string msg = 2; string msg = 2;
} }
// ClaimStrings JWT audience
message ClaimStrings {
repeated string values = 1;
}
// base message // base message
message Empty {} message Empty {}
@ -78,6 +95,11 @@ message LoginResponse {
AuthToken auth_token = 2; AuthToken auth_token = 2;
} }
// NumericDate 使 timestamp JWT
message NumericDate {
google.protobuf.Timestamp timestamp = 1;
}
message PageInfoReq { message PageInfoReq {
uint64 page = 1; uint64 page = 1;
uint64 page_size = 2; uint64 page_size = 2;
@ -118,6 +140,24 @@ message RegisterUserResponse {
bool phone_verification_required = 4; bool phone_verification_required = 4;
} }
// RegisteredClaims JWT
message RegisteredClaims {
// iss (Issuer) -
string issuer = 1;
// sub (Subject) -
string subject = 2;
// aud (Audience) -
ClaimStrings audience = 3;
// exp (Expiration Time) -
NumericDate expires_at = 4;
// nbf (Not Before) -
NumericDate not_before = 5;
// iat (Issued At) -
NumericDate issued_at = 6;
// jti (JWT ID) - JWT ID
string id = 7;
}
message UUIDReq { message UUIDReq {
string id = 1; string id = 1;
} }
@ -162,6 +202,9 @@ message VerifyCodeResp {
// App // App
service App { service App {
// token
// group: auth
rpc AuthToken(AuthReq) returns (AuthInfoResp);
// //
// group: code // group: code
rpc GetVerifyCode(VerifyCodeReq) returns (VerifyCodeResp); rpc GetVerifyCode(VerifyCodeReq) returns (VerifyCodeResp);

View File

@ -13,21 +13,26 @@ import (
) )
type ( type (
AuthInfoResp = app.AuthInfoResp
AuthReq = app.AuthReq
AuthToken = app.AuthToken AuthToken = app.AuthToken
BaseIDResp = app.BaseIDResp BaseIDResp = app.BaseIDResp
BaseMsg = app.BaseMsg BaseMsg = app.BaseMsg
BaseResp = app.BaseResp BaseResp = app.BaseResp
BaseUUIDResp = app.BaseUUIDResp BaseUUIDResp = app.BaseUUIDResp
ClaimStrings = app.ClaimStrings
Empty = app.Empty Empty = app.Empty
IDReq = app.IDReq IDReq = app.IDReq
IDsReq = app.IDsReq IDsReq = app.IDsReq
LoginRequest = app.LoginRequest LoginRequest = app.LoginRequest
LoginResponse = app.LoginResponse LoginResponse = app.LoginResponse
NumericDate = app.NumericDate
PageInfoReq = app.PageInfoReq PageInfoReq = app.PageInfoReq
PageUserRequest = app.PageUserRequest PageUserRequest = app.PageUserRequest
PageUserResponse = app.PageUserResponse PageUserResponse = app.PageUserResponse
RegisterUserRequest = app.RegisterUserRequest RegisterUserRequest = app.RegisterUserRequest
RegisterUserResponse = app.RegisterUserResponse RegisterUserResponse = app.RegisterUserResponse
RegisteredClaims = app.RegisteredClaims
UUIDReq = app.UUIDReq UUIDReq = app.UUIDReq
UUIDsReq = app.UUIDsReq UUIDsReq = app.UUIDsReq
UserInfo = app.UserInfo UserInfo = app.UserInfo
@ -35,6 +40,8 @@ type (
VerifyCodeResp = app.VerifyCodeResp VerifyCodeResp = app.VerifyCodeResp
App interface { App interface {
// 校验token
AuthToken(ctx context.Context, in *AuthReq, opts ...grpc.CallOption) (*AuthInfoResp, error)
// 获取验证码 // 获取验证码
GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error) GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error)
// 用户注册 // 用户注册
@ -57,6 +64,12 @@ func NewApp(cli zrpc.Client) App {
} }
} }
// 校验token
func (m *defaultApp) AuthToken(ctx context.Context, in *AuthReq, opts ...grpc.CallOption) (*AuthInfoResp, error) {
client := app.NewAppClient(m.cli.Conn())
return client.AuthToken(ctx, in, opts...)
}
// 获取验证码 // 获取验证码
func (m *defaultApp) GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error) { func (m *defaultApp) GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error) {
client := app.NewAppClient(m.cli.Conn()) client := app.NewAppClient(m.cli.Conn())

61
rpc/desc/app/auth.proto Normal file
View File

@ -0,0 +1,61 @@
syntax = "proto3";
package app;
option go_package = "./app";
import "google/protobuf/timestamp.proto";
// ClaimStrings JWT audience
message ClaimStrings {
repeated string values = 1;
}
// NumericDate 使 timestamp JWT
message NumericDate {
google.protobuf.Timestamp timestamp = 1;
}
// RegisteredClaims JWT
message RegisteredClaims {
// iss (Issuer) -
string issuer = 1;
// sub (Subject) -
string subject = 2;
// aud (Audience) -
ClaimStrings audience = 3;
// exp (Expiration Time) -
NumericDate expires_at = 4;
// nbf (Not Before) -
NumericDate not_before = 5;
// iat (Issued At) -
NumericDate issued_at = 6;
// jti (JWT ID) - JWT ID
string id = 7;
}
// AuthInfoResp
message AuthInfoResp {
uint64 user_id = 1;
string type = 2;
RegisteredClaims claims = 3;
}
message AuthReq {
string token = 1;
google.protobuf.Timestamp time = 2;
}
// App
service App {
// token
// group: auth
rpc AuthToken(AuthReq) returns (AuthInfoResp);
}

View File

@ -21,10 +21,10 @@ RedisConf:
Db: 0 Db: 0
JWTConf: JWTConf:
access_token_secret: OAMDAascvzcvasdf access_token_secret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
refresh_token_secret: ASDZCpajbvasdfasf refresh_token_secret: J8WRjFcuGpeAnymn5GNvbTJKn2uQsXdjvCFT4dK4dY5TtH88SNwzGH7btJ6ck
access_token_expiry: 24h access_token_expiry: 30m
refresh_token_expiry: 720h refresh_token_expiry: 24h
issuer: user-system issuer: user-system

View File

@ -0,0 +1,68 @@
package auth
import (
"context"
"google.golang.org/protobuf/types/known/timestamppb"
"mingyang-admin-app-rpc/internal/jwt_manager"
"mingyang-admin-app-rpc/internal/svc"
"mingyang-admin-app-rpc/types/app"
"github.com/zeromicro/go-zero/core/logx"
)
type TokenLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
jwtManager *jwt_manager.JWTManager
}
func NewAuthTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TokenLogic {
return &TokenLogic{
ctx: ctx,
svcCtx: svcCtx,
Logger: logx.WithContext(ctx),
jwtManager: jwt_manager.NewJWTManager(&svcCtx.Config.JWTConf),
}
}
// AuthToken 校验token
func (l *TokenLogic) AuthToken(in *app.AuthReq) (*app.AuthInfoResp, error) {
token, err := l.jwtManager.VerifyAccessToken(in.GetToken())
if err != nil {
logx.Errorf("verify access token failed: %v", err)
return nil, err
}
return &app.AuthInfoResp{
UserId: token.UserID,
Type: token.Type,
Claims: &app.RegisteredClaims{
IssuedAt: &app.NumericDate{
Timestamp: &timestamppb.Timestamp{
Seconds: token.IssuedAt.Unix(), // 转换为 Unix 时间戳
Nanos: 0,
},
},
ExpiresAt: &app.NumericDate{
Timestamp: &timestamppb.Timestamp{
Seconds: token.ExpiresAt.Unix(), // 转换为 Unix 时间戳
Nanos: 0,
},
},
Issuer: token.Issuer,
Subject: token.Subject,
Audience: &app.ClaimStrings{
Values: token.Audience,
},
NotBefore: &app.NumericDate{
Timestamp: &timestamppb.Timestamp{
Seconds: token.NotBefore.Unix(), // 转换为 Unix 时间戳
Nanos: 0,
},
},
Id: token.ID,
},
}, nil
}

View File

@ -6,6 +6,7 @@ package server
import ( import (
"context" "context"
"mingyang-admin-app-rpc/internal/logic/auth"
"mingyang-admin-app-rpc/internal/logic/base" "mingyang-admin-app-rpc/internal/logic/base"
"mingyang-admin-app-rpc/internal/logic/code" "mingyang-admin-app-rpc/internal/logic/code"
"mingyang-admin-app-rpc/internal/logic/user" "mingyang-admin-app-rpc/internal/logic/user"
@ -24,6 +25,12 @@ func NewAppServer(svcCtx *svc.ServiceContext) *AppServer {
} }
} }
// 校验token
func (s *AppServer) AuthToken(ctx context.Context, in *app.AuthReq) (*app.AuthInfoResp, error) {
l := auth.NewAuthTokenLogic(ctx, s.svcCtx)
return l.AuthToken(in)
}
// 获取验证码 // 获取验证码
func (s *AppServer) GetVerifyCode(ctx context.Context, in *app.VerifyCodeReq) (*app.VerifyCodeResp, error) { func (s *AppServer) GetVerifyCode(ctx context.Context, in *app.VerifyCodeReq) (*app.VerifyCodeResp, error) {
l := code.NewGetVerifyCodeLogic(ctx, s.svcCtx) l := code.NewGetVerifyCodeLogic(ctx, s.svcCtx)

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@ import (
const _ = grpc.SupportPackageIsVersion9 const _ = grpc.SupportPackageIsVersion9
const ( const (
App_AuthToken_FullMethodName = "/app.App/AuthToken"
App_GetVerifyCode_FullMethodName = "/app.App/GetVerifyCode" App_GetVerifyCode_FullMethodName = "/app.App/GetVerifyCode"
App_RegisterUser_FullMethodName = "/app.App/RegisterUser" App_RegisterUser_FullMethodName = "/app.App/RegisterUser"
App_LoginUser_FullMethodName = "/app.App/LoginUser" App_LoginUser_FullMethodName = "/app.App/LoginUser"
@ -32,6 +33,9 @@ const (
// //
// App 服务定义 // App 服务定义
type AppClient interface { type AppClient interface {
// 校验token
// group: auth
AuthToken(ctx context.Context, in *AuthReq, opts ...grpc.CallOption) (*AuthInfoResp, error)
// 获取验证码 // 获取验证码
// group: code // group: code
GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error) GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error)
@ -56,6 +60,16 @@ func NewAppClient(cc grpc.ClientConnInterface) AppClient {
return &appClient{cc} return &appClient{cc}
} }
func (c *appClient) AuthToken(ctx context.Context, in *AuthReq, opts ...grpc.CallOption) (*AuthInfoResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AuthInfoResp)
err := c.cc.Invoke(ctx, App_AuthToken_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *appClient) GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error) { func (c *appClient) GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(VerifyCodeResp) out := new(VerifyCodeResp)
@ -112,6 +126,9 @@ func (c *appClient) InitDatabase(ctx context.Context, in *Empty, opts ...grpc.Ca
// //
// App 服务定义 // App 服务定义
type AppServer interface { type AppServer interface {
// 校验token
// group: auth
AuthToken(context.Context, *AuthReq) (*AuthInfoResp, error)
// 获取验证码 // 获取验证码
// group: code // group: code
GetVerifyCode(context.Context, *VerifyCodeReq) (*VerifyCodeResp, error) GetVerifyCode(context.Context, *VerifyCodeReq) (*VerifyCodeResp, error)
@ -136,6 +153,9 @@ type AppServer interface {
// pointer dereference when methods are called. // pointer dereference when methods are called.
type UnimplementedAppServer struct{} type UnimplementedAppServer struct{}
func (UnimplementedAppServer) AuthToken(context.Context, *AuthReq) (*AuthInfoResp, error) {
return nil, status.Error(codes.Unimplemented, "method AuthToken not implemented")
}
func (UnimplementedAppServer) GetVerifyCode(context.Context, *VerifyCodeReq) (*VerifyCodeResp, error) { func (UnimplementedAppServer) GetVerifyCode(context.Context, *VerifyCodeReq) (*VerifyCodeResp, error) {
return nil, status.Error(codes.Unimplemented, "method GetVerifyCode not implemented") return nil, status.Error(codes.Unimplemented, "method GetVerifyCode not implemented")
} }
@ -172,6 +192,24 @@ func RegisterAppServer(s grpc.ServiceRegistrar, srv AppServer) {
s.RegisterService(&App_ServiceDesc, srv) s.RegisterService(&App_ServiceDesc, srv)
} }
func _App_AuthToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AuthReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AppServer).AuthToken(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: App_AuthToken_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AppServer).AuthToken(ctx, req.(*AuthReq))
}
return interceptor(ctx, in, info, handler)
}
func _App_GetVerifyCode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _App_GetVerifyCode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(VerifyCodeReq) in := new(VerifyCodeReq)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -269,6 +307,10 @@ var App_ServiceDesc = grpc.ServiceDesc{
ServiceName: "app.App", ServiceName: "app.App",
HandlerType: (*AppServer)(nil), HandlerType: (*AppServer)(nil),
Methods: []grpc.MethodDesc{ Methods: []grpc.MethodDesc{
{
MethodName: "AuthToken",
Handler: _App_AuthToken_Handler,
},
{ {
MethodName: "GetVerifyCode", MethodName: "GetVerifyCode",
Handler: _App_GetVerifyCode_Handler, Handler: _App_GetVerifyCode_Handler,