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:
parent
538f6e7e39
commit
67eca4d18e
38
api/app.json
38
api/app.json
|
|
@ -22,7 +22,7 @@
|
|||
"post": {
|
||||
"description": "GetVerifyCode | 获取验证码",
|
||||
"tags": [
|
||||
"user"
|
||||
"user_public"
|
||||
],
|
||||
"summary": "GetVerifyCode | 获取验证码",
|
||||
"operationId": "GetVerifyCode",
|
||||
|
|
@ -68,7 +68,7 @@
|
|||
"post": {
|
||||
"description": "Login | 登录",
|
||||
"tags": [
|
||||
"user"
|
||||
"user_public"
|
||||
],
|
||||
"summary": "Login | 登录",
|
||||
"operationId": "Login",
|
||||
|
|
@ -96,7 +96,7 @@
|
|||
"post": {
|
||||
"description": "Register | 注册",
|
||||
"tags": [
|
||||
"user"
|
||||
"user_public"
|
||||
],
|
||||
"summary": "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": {
|
||||
|
|
@ -868,6 +896,10 @@
|
|||
},
|
||||
"x-go-package": "mingyang-admin-app-api/internal/types"
|
||||
},
|
||||
"UserInfoReq": {
|
||||
"type": "object",
|
||||
"x-go-package": "mingyang-admin-app-api/internal/types"
|
||||
},
|
||||
"VerifyCodeReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ type (
|
|||
}
|
||||
|
||||
|
||||
|
||||
RegisterResp{
|
||||
// token信息
|
||||
AuthToken *AuthToken `json:"authToken,optional"`
|
||||
|
|
@ -109,13 +108,16 @@ type (
|
|||
AuthToken *AuthToken
|
||||
UserInfo *UserInfo
|
||||
}
|
||||
|
||||
UserInfoReq{
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
@server (
|
||||
jwt: Auth
|
||||
group: user
|
||||
middleware: Authority
|
||||
group: user_public
|
||||
)
|
||||
|
||||
service App {
|
||||
// GetVerifyCode | 获取验证码
|
||||
@handler getVerifyCode
|
||||
|
|
@ -128,3 +130,15 @@ service App {
|
|||
post /login (LoginReq) returns (LoginResp)
|
||||
}
|
||||
|
||||
|
||||
@server (
|
||||
group: user
|
||||
middleware: Authority
|
||||
)
|
||||
|
||||
service App {
|
||||
// Get userInfo detail by ID | 获取用户信息
|
||||
@handler getUserInfoById
|
||||
post /user (IDReq) returns (UserInfo)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ Host: 0.0.0.0
|
|||
Port: 9902
|
||||
Timeout: 4000
|
||||
|
||||
|
||||
CROSConf:
|
||||
Address: '*'
|
||||
|
||||
|
|
@ -29,6 +28,9 @@ AppRpc:
|
|||
Target: 127.0.0.1:9901
|
||||
Enabled: true
|
||||
|
||||
JWTConf:
|
||||
AccessSecret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
|
||||
|
||||
|
||||
I18nConf:
|
||||
Dir:
|
||||
|
|
@ -3,6 +3,8 @@ module mingyang-admin-app-api
|
|||
go 1.25.3
|
||||
|
||||
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/saas-mingyang/mingyang-admin-common v0.3.3
|
||||
github.com/zeromicro/go-zero v1.9.1
|
||||
|
|
|
|||
|
|
@ -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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
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/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
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/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/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.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
|
|
|||
|
|
@ -16,4 +16,9 @@ type Config struct {
|
|||
CROSConf config.CROSConf
|
||||
I18nConf i18n.Conf
|
||||
AppRpc zrpc.RpcClientConf
|
||||
Auth JWTConfig `json:"JWTConf"`
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
AccessSecret string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
base "mingyang-admin-app-api/internal/handler/base"
|
||||
user "mingyang-admin-app-api/internal/handler/user"
|
||||
user_public "mingyang-admin-app-api/internal/handler/user_public"
|
||||
"mingyang-admin-app-api/internal/svc"
|
||||
|
||||
"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(
|
||||
rest.WithMiddlewares(
|
||||
[]rest.Middleware{serverCtx.Authority},
|
||||
[]rest.Route{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/get/verifyCode",
|
||||
Handler: user.GetVerifyCodeHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/register",
|
||||
Handler: user.RegisterHandler(serverCtx),
|
||||
},
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Path: "/login",
|
||||
Handler: user.LoginHandler(serverCtx),
|
||||
Path: "/user",
|
||||
Handler: user.GetUserInfoByIdHandler(serverCtx),
|
||||
},
|
||||
}...,
|
||||
),
|
||||
//rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"mingyang-admin-app-api/internal/logic/user"
|
||||
"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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"mingyang-admin-app-api/internal/logic/user"
|
||||
"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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -1,28 +1,170 @@
|
|||
// internal/middleware/authority.go
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"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"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 定义上下文键类型
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
UserIDKey contextKey = "user_id"
|
||||
UserInfoKey contextKey = "user_info"
|
||||
)
|
||||
|
||||
type AuthorityMiddleware struct {
|
||||
Rds redis.UniversalClient
|
||||
AppRpc appclient.App
|
||||
}
|
||||
|
||||
func NewAuthorityMiddleware(rds redis.UniversalClient) *AuthorityMiddleware {
|
||||
// NewAuthorityMiddleware 创建认证中间件
|
||||
func NewAuthorityMiddleware(appRpc appclient.App, rds redis.UniversalClient) *AuthorityMiddleware {
|
||||
return &AuthorityMiddleware{
|
||||
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 {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
/* // get the path
|
||||
obj := r.URL.Path
|
||||
// get the method
|
||||
act := r.Method
|
||||
// get the role id
|
||||
roleIds := strings.Split(r.Context().Value("roleId").(string), ",")*/
|
||||
startTime := time.Now()
|
||||
|
||||
// 检查是否为公开路径(可选)
|
||||
if m.isPublicPath(r.URL.Path) {
|
||||
next(w, r)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package svc
|
||||
|
||||
import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/saas-mingyang/mingyang-admin-common/i18n"
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
"github.com/zeromicro/go-zero/zrpc"
|
||||
|
|
@ -15,15 +16,18 @@ type ServiceContext struct {
|
|||
Trans *i18n.Translator
|
||||
Authority rest.Middleware
|
||||
AppRpc appclient.App
|
||||
Redis redis.UniversalClient
|
||||
}
|
||||
|
||||
func NewServiceContext(c config.Config) *ServiceContext {
|
||||
trans := i18n.NewTranslator(c.I18nConf, i18n2.LocaleFS)
|
||||
appRpc := appclient.NewApp(zrpc.NewClientIfEnable(c.AppRpc))
|
||||
rds := c.RedisConf.MustNewUniversalRedis()
|
||||
return &ServiceContext{
|
||||
Authority: middleware.NewAuthorityMiddleware(rds).Handle,
|
||||
Authority: middleware.NewAuthorityMiddleware(appRpc, rds).Handle,
|
||||
Config: c,
|
||||
Redis: rds,
|
||||
Trans: trans,
|
||||
AppRpc: appclient.NewApp(zrpc.NewClientIfEnable(c.AppRpc)),
|
||||
AppRpc: appRpc,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -362,3 +362,7 @@ type LoginResp struct {
|
|||
AuthToken *AuthToken
|
||||
UserInfo *UserInfo
|
||||
}
|
||||
|
||||
// swagger:model UserInfoReq
|
||||
type UserInfoReq struct {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,18 @@ enum VerifyCodeType {
|
|||
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 {
|
||||
string access_token = 1;
|
||||
|
|
@ -54,6 +66,11 @@ message BaseUUIDResp {
|
|||
string msg = 2;
|
||||
}
|
||||
|
||||
// ClaimStrings 用于表示 JWT 中的 audience 字段
|
||||
message ClaimStrings {
|
||||
repeated string values = 1;
|
||||
}
|
||||
|
||||
// base message
|
||||
message Empty {}
|
||||
|
||||
|
|
@ -78,6 +95,11 @@ message LoginResponse {
|
|||
AuthToken auth_token = 2;
|
||||
}
|
||||
|
||||
// NumericDate 使用 timestamp 表示 JWT 中的时间字段
|
||||
message NumericDate {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
}
|
||||
|
||||
message PageInfoReq {
|
||||
uint64 page = 1;
|
||||
uint64 page_size = 2;
|
||||
|
|
@ -118,6 +140,24 @@ message RegisterUserResponse {
|
|||
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 {
|
||||
string id = 1;
|
||||
}
|
||||
|
|
@ -162,6 +202,9 @@ message VerifyCodeResp {
|
|||
|
||||
// App 服务定义
|
||||
service App {
|
||||
// 校验token
|
||||
// group: auth
|
||||
rpc AuthToken(AuthReq) returns (AuthInfoResp);
|
||||
// 获取验证码
|
||||
// group: code
|
||||
rpc GetVerifyCode(VerifyCodeReq) returns (VerifyCodeResp);
|
||||
|
|
|
|||
|
|
@ -13,21 +13,26 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
AuthInfoResp = app.AuthInfoResp
|
||||
AuthReq = app.AuthReq
|
||||
AuthToken = app.AuthToken
|
||||
BaseIDResp = app.BaseIDResp
|
||||
BaseMsg = app.BaseMsg
|
||||
BaseResp = app.BaseResp
|
||||
BaseUUIDResp = app.BaseUUIDResp
|
||||
ClaimStrings = app.ClaimStrings
|
||||
Empty = app.Empty
|
||||
IDReq = app.IDReq
|
||||
IDsReq = app.IDsReq
|
||||
LoginRequest = app.LoginRequest
|
||||
LoginResponse = app.LoginResponse
|
||||
NumericDate = app.NumericDate
|
||||
PageInfoReq = app.PageInfoReq
|
||||
PageUserRequest = app.PageUserRequest
|
||||
PageUserResponse = app.PageUserResponse
|
||||
RegisterUserRequest = app.RegisterUserRequest
|
||||
RegisterUserResponse = app.RegisterUserResponse
|
||||
RegisteredClaims = app.RegisteredClaims
|
||||
UUIDReq = app.UUIDReq
|
||||
UUIDsReq = app.UUIDsReq
|
||||
UserInfo = app.UserInfo
|
||||
|
|
@ -35,6 +40,8 @@ type (
|
|||
VerifyCodeResp = app.VerifyCodeResp
|
||||
|
||||
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)
|
||||
// 用户注册
|
||||
|
|
@ -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) {
|
||||
client := app.NewAppClient(m.cli.Conn())
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -21,10 +21,10 @@ RedisConf:
|
|||
Db: 0
|
||||
|
||||
JWTConf:
|
||||
access_token_secret: OAMDAascvzcvasdf
|
||||
refresh_token_secret: ASDZCpajbvasdfasf
|
||||
access_token_expiry: 24h
|
||||
refresh_token_expiry: 720h
|
||||
access_token_secret: KdAj3ZnmHpVvGKBthWXmTNPRcdZaWP7cnbsvmJSYRadN8PebaaAQENVKDD6WCm
|
||||
refresh_token_secret: J8WRjFcuGpeAnymn5GNvbTJKn2uQsXdjvCFT4dK4dY5TtH88SNwzGH7btJ6ck
|
||||
access_token_expiry: 30m
|
||||
refresh_token_expiry: 24h
|
||||
issuer: user-system
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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: ×tamppb.Timestamp{
|
||||
Seconds: token.IssuedAt.Unix(), // 转换为 Unix 时间戳
|
||||
Nanos: 0,
|
||||
},
|
||||
},
|
||||
ExpiresAt: &app.NumericDate{
|
||||
Timestamp: ×tamppb.Timestamp{
|
||||
Seconds: token.ExpiresAt.Unix(), // 转换为 Unix 时间戳
|
||||
Nanos: 0,
|
||||
},
|
||||
},
|
||||
Issuer: token.Issuer,
|
||||
Subject: token.Subject,
|
||||
Audience: &app.ClaimStrings{
|
||||
Values: token.Audience,
|
||||
},
|
||||
NotBefore: &app.NumericDate{
|
||||
Timestamp: ×tamppb.Timestamp{
|
||||
Seconds: token.NotBefore.Unix(), // 转换为 Unix 时间戳
|
||||
Nanos: 0,
|
||||
},
|
||||
},
|
||||
Id: token.ID,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ package server
|
|||
import (
|
||||
"context"
|
||||
|
||||
"mingyang-admin-app-rpc/internal/logic/auth"
|
||||
"mingyang-admin-app-rpc/internal/logic/base"
|
||||
"mingyang-admin-app-rpc/internal/logic/code"
|
||||
"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) {
|
||||
l := code.NewGetVerifyCodeLogic(ctx, s.svcCtx)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -19,6 +19,7 @@ import (
|
|||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
App_AuthToken_FullMethodName = "/app.App/AuthToken"
|
||||
App_GetVerifyCode_FullMethodName = "/app.App/GetVerifyCode"
|
||||
App_RegisterUser_FullMethodName = "/app.App/RegisterUser"
|
||||
App_LoginUser_FullMethodName = "/app.App/LoginUser"
|
||||
|
|
@ -32,6 +33,9 @@ const (
|
|||
//
|
||||
// App 服务定义
|
||||
type AppClient interface {
|
||||
// 校验token
|
||||
// group: auth
|
||||
AuthToken(ctx context.Context, in *AuthReq, opts ...grpc.CallOption) (*AuthInfoResp, error)
|
||||
// 获取验证码
|
||||
// group: code
|
||||
GetVerifyCode(ctx context.Context, in *VerifyCodeReq, opts ...grpc.CallOption) (*VerifyCodeResp, error)
|
||||
|
|
@ -56,6 +60,16 @@ func NewAppClient(cc grpc.ClientConnInterface) AppClient {
|
|||
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) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(VerifyCodeResp)
|
||||
|
|
@ -112,6 +126,9 @@ func (c *appClient) InitDatabase(ctx context.Context, in *Empty, opts ...grpc.Ca
|
|||
//
|
||||
// App 服务定义
|
||||
type AppServer interface {
|
||||
// 校验token
|
||||
// group: auth
|
||||
AuthToken(context.Context, *AuthReq) (*AuthInfoResp, error)
|
||||
// 获取验证码
|
||||
// group: code
|
||||
GetVerifyCode(context.Context, *VerifyCodeReq) (*VerifyCodeResp, error)
|
||||
|
|
@ -136,6 +153,9 @@ type AppServer interface {
|
|||
// pointer dereference when methods are called.
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
in := new(VerifyCodeReq)
|
||||
if err := dec(in); err != nil {
|
||||
|
|
@ -269,6 +307,10 @@ var App_ServiceDesc = grpc.ServiceDesc{
|
|||
ServiceName: "app.App",
|
||||
HandlerType: (*AppServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "AuthToken",
|
||||
Handler: _App_AuthToken_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetVerifyCode",
|
||||
Handler: _App_GetVerifyCode_Handler,
|
||||
|
|
|
|||
Loading…
Reference in New Issue