完善登录与鉴权机制

This commit is contained in:
2025-03-28 15:01:30 +08:00
parent e61f0bef32
commit edec734b71
11 changed files with 132 additions and 108 deletions

View File

@@ -7,6 +7,8 @@ import (
"platform/pkg/orm" "platform/pkg/orm"
"platform/web/models" "platform/web/models"
q "platform/web/queries" q "platform/web/queries"
"golang.org/x/crypto/bcrypt"
) )
func main() { func main() {
@@ -14,41 +16,59 @@ func main() {
logs.Init() logs.Init()
orm.Init() orm.Init()
q.User.Select( q.User.
q.User.Phone, Select(
).Create(&models.User{ q.User.Phone).
Phone: "12312341234", Create(&models.User{
}) Phone: "12312341234"})
q.Proxy.Select( q.Proxy.
q.Proxy.Version, Select(
q.Proxy.Name, q.Proxy.Version,
q.Proxy.Host, q.Proxy.Name,
q.Proxy.Type, q.Proxy.Host,
).Create(&models.Proxy{ q.Proxy.Type).
Version: 1, Create(&models.Proxy{
Name: "7a17e8b4-cdc3-4500-bf16-4a665991a7f6", Version: 1,
Host: "110.40.82.248", Name: "7a17e8b4-cdc3-4500-bf16-4a665991a7f6",
Type: 1, Host: "110.40.82.248",
}) Type: 1})
q.Node.Select( q.Node.
q.Node.Version, Select(
q.Node.Name, q.Node.Version,
q.Node.Host, q.Node.Name,
q.Node.Isp, q.Node.Host,
q.Node.Prov, q.Node.Isp,
q.Node.City, q.Node.Prov,
q.Node.Status, q.Node.City,
).Create(&models.Node{ q.Node.Status).
Version: 1, Create(&models.Node{
Name: "test-node", Version: 1,
Host: "123", Name: "test-node",
Isp: "test-isp", Host: "123",
Prov: "test-prov", Isp: "test-isp",
City: "test-city", Prov: "test-prov",
Status: 1, City: "test-city",
}) Status: 1})
var secret, _ = bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost)
q.Client.
Select(
q.Client.ClientID,
q.Client.ClientSecret,
q.Client.GrantClient,
q.Client.GrantRefresh,
q.Client.Spec,
q.Client.Name).
Create(&models.Client{
ClientID: "test",
ClientSecret: string(secret),
GrantClient: true,
GrantRefresh: true,
Spec: 0,
Name: "默认客户端",
})
slog.Info("✔ Data inserted successfully") slog.Info("✔ Data inserted successfully")
} }

View File

@@ -184,7 +184,7 @@ create table client (
grant_refresh bool not null default false, grant_refresh bool not null default false,
spec int not null, spec int not null,
name varchar(255) not null, name varchar(255) not null,
version int not null, icon varchar(255),
status int not null default 1, status int not null default 1,
created_at timestamp default current_timestamp, created_at timestamp default current_timestamp,
updated_at timestamp default current_timestamp, updated_at timestamp default current_timestamp,
@@ -206,7 +206,7 @@ comment on column client.grant_client is '允许客户端凭证授予';
comment on column client.grant_refresh is '允许刷新令牌授予'; comment on column client.grant_refresh is '允许刷新令牌授予';
comment on column client.spec is '安全规范0-web1-native2-browser'; comment on column client.spec is '安全规范0-web1-native2-browser';
comment on column client.name is '名称'; comment on column client.name is '名称';
comment on column client.version is '版本'; comment on column client.icon is '图标URL';
comment on column client.status is '状态1-正常0-禁用'; comment on column client.status is '状态1-正常0-禁用';
comment on column client.created_at is '创建时间'; comment on column client.created_at is '创建时间';
comment on column client.updated_at is '更新时间'; comment on column client.updated_at is '更新时间';

5
test/test-api.http Normal file
View File

@@ -0,0 +1,5 @@
GET http://api:123456@110.40.82.248:9990/port/active
Accept: application/json
###

View File

@@ -56,3 +56,50 @@ func PermitUser(permissions ...string) fiber.Handler {
return c.Next() return c.Next()
} }
} }
func PermitDevice(permissions ...string) fiber.Handler {
return func(c *fiber.Ctx) error {
// 获取令牌
var header = c.Get("Authorization")
var token = strings.TrimPrefix(header, "Bearer ")
if token == "" {
return c.Status(fiber.StatusUnauthorized).JSON(common.ErrResp{
Error: true,
Message: "没有权限",
})
}
// 验证令牌
auth, err := services.Session.Find(c.Context(), token)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(common.ErrResp{
Error: true,
Message: "没有权限",
})
}
// 检查权限
switch auth.Payload.Type {
case services.PayloadAdmin:
// 管理员不需要权限检查
case services.PayloadClientPublic, services.PayloadClientConfidential:
if len(permissions) > 0 && !auth.AnyPermission(permissions...) {
return c.Status(fiber.StatusForbidden).JSON(common.ErrResp{
Error: true,
Message: "拒绝访问",
})
}
default:
return c.Status(fiber.StatusForbidden).JSON(common.ErrResp{
Error: true,
Message: "拒绝访问",
})
}
// 将认证信息存储在上下文中
c.Locals("auth", auth)
c.Locals("access_token", token) // 存储原始令牌,便于后续操作
return c.Next()
}
}

View File

@@ -1,52 +0,0 @@
package handlers
import (
"platform/web/models"
q "platform/web/queries"
"time"
"github.com/gofiber/fiber/v2"
"golang.org/x/crypto/bcrypt"
)
type CreateClientReq struct {
ClientID string `query:"client_id"`
ClientSecret string `query:"client_secret"`
}
func CreateClient(c *fiber.Ctx) error {
// 验证请求参数
req := new(CreateClientReq)
if err := c.QueryParser(req); err != nil {
return err
}
if req.ClientID == "" {
return fiber.NewError(fiber.StatusBadRequest, "client_id不能为空")
}
if req.ClientSecret == "" {
return fiber.NewError(fiber.StatusBadRequest, "client_secret不能为空")
}
// 创建客户端
hashedSecret, err := bcrypt.GenerateFromPassword([]byte(req.ClientSecret), bcrypt.DefaultCost)
if err != nil {
return err
}
client := &models.Client{
ClientID: req.ClientID,
ClientSecret: string(hashedSecret),
Name: "默认客户端 - " + time.Now().String(),
Spec: 0,
GrantCode: true,
GrantClient: true,
GrantRefresh: true,
Version: 0,
}
err = q.Client.Create(client)
if err != nil {
return err
}
return c.JSON(client)
}

View File

@@ -18,8 +18,9 @@ type LoginReq struct {
} }
type LoginResp struct { type LoginResp struct {
Token string `json:"token"` Token string `json:"token"`
Expires int64 `json:"expires"` Expires int64 `json:"expires"`
Auth services.AuthContext `json:"auth"`
} }
func Login(c *fiber.Ctx) error { func Login(c *fiber.Ctx) error {
@@ -65,6 +66,7 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
if user == nil { if user == nil {
user = &models.User{ user = &models.User{
Phone: req.Username, Phone: req.Username,
Name: req.Username,
} }
} }
@@ -87,8 +89,10 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
"user": {}, "user": {},
}, },
Payload: services.Payload{ Payload: services.Payload{
Type: services.PayloadUser, Id: user.ID,
Id: user.ID, Type: services.PayloadUser,
Name: user.Name,
Avatar: user.Avatar,
}, },
} }
duration := time.Hour * 24 duration := time.Hour * 24
@@ -103,5 +107,6 @@ func loginByPhone(c *fiber.Ctx, req *LoginReq) error {
return c.JSON(LoginResp{ return c.JSON(LoginResp{
Token: token.AccessToken, Token: token.AccessToken,
Expires: token.AccessTokenExpires.Unix(), Expires: token.AccessTokenExpires.Unix(),
Auth: auth,
}) })
} }

View File

@@ -23,7 +23,7 @@ type Client struct {
GrantRefresh bool `gorm:"column:grant_refresh;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予 GrantRefresh bool `gorm:"column:grant_refresh;not null;comment:允许刷新令牌授予" json:"grant_refresh"` // 允许刷新令牌授予
Spec int32 `gorm:"column:spec;not null;comment:安全规范0-web1-native2-browser" json:"spec"` // 安全规范0-web1-native2-browser Spec int32 `gorm:"column:spec;not null;comment:安全规范0-web1-native2-browser" json:"spec"` // 安全规范0-web1-native2-browser
Name string `gorm:"column:name;not null;comment:名称" json:"name"` // 名称 Name string `gorm:"column:name;not null;comment:名称" json:"name"` // 名称
Version int32 `gorm:"column:version;not null;comment:版本" json:"version"` // 版本 Icon string `gorm:"column:icon;comment:图标URL" json:"icon"` // 图标URL
Status int32 `gorm:"column:status;not null;default:1;comment:状态1-正常0-禁用" json:"status"` // 状态1-正常0-禁用 Status int32 `gorm:"column:status;not null;default:1;comment:状态1-正常0-禁用" json:"status"` // 状态1-正常0-禁用
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间 CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间 UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间

View File

@@ -36,7 +36,7 @@ func newClient(db *gorm.DB, opts ...gen.DOOption) client {
_client.GrantRefresh = field.NewBool(tableName, "grant_refresh") _client.GrantRefresh = field.NewBool(tableName, "grant_refresh")
_client.Spec = field.NewInt32(tableName, "spec") _client.Spec = field.NewInt32(tableName, "spec")
_client.Name = field.NewString(tableName, "name") _client.Name = field.NewString(tableName, "name")
_client.Version = field.NewInt32(tableName, "version") _client.Icon = field.NewString(tableName, "icon")
_client.Status = field.NewInt32(tableName, "status") _client.Status = field.NewInt32(tableName, "status")
_client.CreatedAt = field.NewTime(tableName, "created_at") _client.CreatedAt = field.NewTime(tableName, "created_at")
_client.UpdatedAt = field.NewTime(tableName, "updated_at") _client.UpdatedAt = field.NewTime(tableName, "updated_at")
@@ -60,7 +60,7 @@ type client struct {
GrantRefresh field.Bool // 允许刷新令牌授予 GrantRefresh field.Bool // 允许刷新令牌授予
Spec field.Int32 // 安全规范0-web1-native2-browser Spec field.Int32 // 安全规范0-web1-native2-browser
Name field.String // 名称 Name field.String // 名称
Version field.Int32 // 版本 Icon field.String // 图标URL
Status field.Int32 // 状态1-正常0-禁用 Status field.Int32 // 状态1-正常0-禁用
CreatedAt field.Time // 创建时间 CreatedAt field.Time // 创建时间
UpdatedAt field.Time // 更新时间 UpdatedAt field.Time // 更新时间
@@ -90,7 +90,7 @@ func (c *client) updateTableName(table string) *client {
c.GrantRefresh = field.NewBool(table, "grant_refresh") c.GrantRefresh = field.NewBool(table, "grant_refresh")
c.Spec = field.NewInt32(table, "spec") c.Spec = field.NewInt32(table, "spec")
c.Name = field.NewString(table, "name") c.Name = field.NewString(table, "name")
c.Version = field.NewInt32(table, "version") c.Icon = field.NewString(table, "icon")
c.Status = field.NewInt32(table, "status") c.Status = field.NewInt32(table, "status")
c.CreatedAt = field.NewTime(table, "created_at") c.CreatedAt = field.NewTime(table, "created_at")
c.UpdatedAt = field.NewTime(table, "updated_at") c.UpdatedAt = field.NewTime(table, "updated_at")
@@ -121,7 +121,7 @@ func (c *client) fillFieldMap() {
c.fieldMap["grant_refresh"] = c.GrantRefresh c.fieldMap["grant_refresh"] = c.GrantRefresh
c.fieldMap["spec"] = c.Spec c.fieldMap["spec"] = c.Spec
c.fieldMap["name"] = c.Name c.fieldMap["name"] = c.Name
c.fieldMap["version"] = c.Version c.fieldMap["icon"] = c.Icon
c.fieldMap["status"] = c.Status c.fieldMap["status"] = c.Status
c.fieldMap["created_at"] = c.CreatedAt c.fieldMap["created_at"] = c.CreatedAt
c.fieldMap["updated_at"] = c.UpdatedAt c.fieldMap["updated_at"] = c.UpdatedAt

View File

@@ -11,14 +11,10 @@ func ApplyRouters(app *fiber.App) {
// 认证 // 认证
auth := api.Group("/auth") auth := api.Group("/auth")
auth.Post("/verify/sms", PermitUser(), handlers.SmsCode) auth.Post("/verify/sms", PermitDevice(), handlers.SmsCode)
auth.Post("/login/sms", PermitUser(), handlers.Login) auth.Post("/login/sms", PermitDevice(), handlers.Login)
auth.Post("/token", handlers.Token) auth.Post("/token", handlers.Token)
// 客户端
client := api.Group("/client")
client.Get("/test/create", handlers.CreateClient)
// 通道 // 通道
channel := api.Group("/channel", PermitUser()) channel := api.Group("/channel", PermitUser())
channel.Post("/create", handlers.CreateChannel) channel.Post("/create", handlers.CreateChannel)

View File

@@ -59,8 +59,9 @@ func (s *authService) OauthClientCredentials(ctx context.Context, client *models
auth := AuthContext{ auth := AuthContext{
Permissions: permissions, Permissions: permissions,
Payload: Payload{ Payload: Payload{
Type: clientType,
Id: client.ID, Id: client.ID,
Type: clientType,
Name: client.Name,
}, },
} }

View File

@@ -238,15 +238,17 @@ func mergeConfig(defaultCfg SessionConfig, customCfg SessionConfig) SessionConfi
// AuthContext 定义认证信息 // AuthContext 定义认证信息
type AuthContext struct { type AuthContext struct {
Payload Payload Payload Payload `json:"payload"`
Permissions map[string]struct{} Permissions map[string]struct{} `json:"permissions,omitempty"`
Metadata map[string]interface{} Metadata map[string]interface{} `json:"metadata,omitempty"`
} }
// Payload 定义负载信息 // Payload 定义负载信息
type Payload struct { type Payload struct {
Type PayloadType Id int32 `json:"id,omitempty"`
Id int32 Type PayloadType `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Avatar string `json:"avatar,omitempty"`
} }
// PayloadType 定义负载类型 // PayloadType 定义负载类型