完善登录与鉴权机制
This commit is contained in:
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-web,1-native,2-browser';
|
comment on column client.spec is '安全规范:0-web,1-native,2-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
5
test/test-api.http
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
GET http://api:123456@110.40.82.248:9990/port/active
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
47
web/auth.go
47
web/auth.go
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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-web,1-native,2-browser" json:"spec"` // 安全规范:0-web,1-native,2-browser
|
Spec int32 `gorm:"column:spec;not null;comment:安全规范:0-web,1-native,2-browser" json:"spec"` // 安全规范:0-web,1-native,2-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"` // 更新时间
|
||||||
|
|||||||
@@ -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-web,1-native,2-browser
|
Spec field.Int32 // 安全规范:0-web,1-native,2-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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 定义负载类型
|
||||||
|
|||||||
Reference in New Issue
Block a user