重构认证授权模块,统一到 auth 包下

This commit is contained in:
2025-12-29 10:18:01 +08:00
parent bf8f001a30
commit 1262a8dae4
8 changed files with 407 additions and 269 deletions

View File

@@ -2,7 +2,7 @@ name: Docker
on: on:
push: push:
branches: [ "main" ] branches: ["v*"]
env: env:
REGISTRY: ghcr.io REGISTRY: ghcr.io
@@ -10,14 +10,12 @@ env:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
packages: write packages: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4

156
web/auth/account.go Normal file
View File

@@ -0,0 +1,156 @@
package auth
import (
"context"
"errors"
"log/slog"
"platform/pkg/u"
"platform/web/core"
m "platform/web/models"
q "platform/web/queries"
s "platform/web/services"
"golang.org/x/crypto/bcrypt"
)
func authClient(clientId string, clientSecrets ...string) (*m.Client, error) {
// 获取客户端信息
client, err := q.Client.
Where(
q.Client.ClientID.Eq(clientId),
q.Client.Status.Eq(1)).
Take()
if err != nil {
return nil, err
}
// 检查客户端密钥
if client.Spec == m.ClientSpecWeb || client.Spec == m.ClientSpecAPI {
if len(clientSecrets) == 0 {
return nil, errors.New("客户端密钥错误")
}
clientSecret := clientSecrets[0]
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
return nil, errors.New("客户端密钥错误")
}
}
// todo 查询客户端关联权限
// 组织授权信息(一次性请求)
return client, nil
}
func authUser(loginType PwdLoginType, username, password string) (user *m.User, err error) {
switch loginType {
case PwdLoginByPhone:
user, err = authUserBySms(q.Q, username, password)
if err != nil {
return nil, err
}
if user == nil {
user = &m.User{
Phone: username,
Username: u.P(username),
Status: m.UserStatusEnabled,
}
}
case PwdLoginByEmail:
user, err = authUserByEmail(q.Q, username, password)
if err != nil {
return nil, err
}
case PwdLoginByPassword:
user, err = authUserByPassword(q.Q, username, password)
if err != nil {
return nil, err
}
default:
return nil, ErrAuthorizeInvalidRequest
}
// 账户状态
if user.Status == m.UserStatusDisabled {
return nil, core.NewBizErr("账号已禁用")
}
return user, nil
}
func authUserBySms(tx *q.Query, username, code string) (*m.User, error) {
// 验证验证码
err := s.Verifier.VerifySms(context.Background(), username, code)
if err != nil {
return nil, core.NewBizErr("短信认证失败:%w", err)
}
// 查找用户
return tx.User.Where(tx.User.Phone.Eq(username)).Take()
}
func authUserByEmail(tx *q.Query, username, code string) (*m.User, error) {
return nil, core.NewServErr("邮箱登录不可用")
}
func authUserByPassword(tx *q.Query, username, password string) (*m.User, error) {
user, err := tx.User.
Where(tx.User.Phone.Eq(username)).
Or(tx.User.Email.Eq(username)).
Or(tx.User.Username.Eq(username)).
Take()
if err != nil {
slog.Debug("查找用户失败", "error", err)
return nil, core.NewBizErr("用户不存在或密码错误")
}
// 验证密码
if user.Password == nil || *user.Password == "" {
slog.Debug("用户未设置密码", "username", username)
return nil, core.NewBizErr("用户不存在或密码错误")
}
if bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) != nil {
slog.Debug("密码验证失败", "username", username)
return nil, core.NewBizErr("用户不存在或密码错误")
}
return user, nil
}
func authAdmin(loginType PwdLoginType, username, password string) (admin *m.Admin, err error) {
switch loginType {
case PwdLoginByPhone, PwdLoginByEmail:
return nil, core.NewServErr("不支持的登录方式:" + string(loginType))
case PwdLoginByPassword:
admin, err = authAdminByPassword(q.Q, username, password)
if err != nil {
return nil, err
}
default:
return nil, ErrAuthorizeInvalidRequest
}
// 账户状态
if admin.Status == m.AdminStatusDisabled {
return nil, core.NewBizErr("账号已禁用")
}
return admin, nil
}
func authAdminByPassword(tx *q.Query, username, password string) (*m.Admin, error) {
admin, err := tx.Admin.Where(tx.Admin.Username.Eq(username)).Take()
if err != nil {
return nil, core.NewBizErr("账号不存在或密码错误")
}
// 验证密码
if admin.Password == "" {
return nil, core.NewBizErr("账号不存在或密码错误")
}
if bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(password)) != nil {
return nil, core.NewBizErr("账号不存在或密码错误")
}
return admin, nil
}

View File

@@ -6,10 +6,8 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"log/slog"
"platform/pkg/env" "platform/pkg/env"
"platform/pkg/u" "platform/pkg/u"
"platform/web/core"
g "platform/web/globals" g "platform/web/globals"
"platform/web/globals/orm" "platform/web/globals/orm"
m "platform/web/models" m "platform/web/models"
@@ -22,67 +20,52 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
type GrantType string // AuthorizeGet 授权端点
func AuthorizeGet(ctx *fiber.Ctx) error {
const ( // 检查请求
GrantAuthorizationCode = GrantType("authorization_code") // 授权码模式 req := new(AuthorizeGetReq)
GrantClientCredentials = GrantType("client_credentials") // 客户端凭证模式 if err := g.Validator.ParseQuery(ctx, req); err != nil {
GrantRefreshToken = GrantType("refresh_token") // 刷新令牌模式 return err
GrantPassword = GrantType("password") // 密码模式(私有扩展)
)
type PasswordGrantType string
const (
GrantPasswordSecret = PasswordGrantType("password") // 账号密码
GrantPasswordPhone = PasswordGrantType("phone_code") // 手机验证码
GrantPasswordEmail = PasswordGrantType("email_code") // 邮箱验证码
)
type TokenReq struct {
GrantType GrantType `json:"grant_type" form:"grant_type"`
ClientID string `json:"client_id" form:"client_id"`
ClientSecret string `json:"client_secret" form:"client_secret"`
Scope string `json:"scope" form:"scope"`
GrantCodeData
GrantClientData
GrantRefreshData
GrantPasswordData
} }
type GrantCodeData struct { // 检查客户端
Code string `json:"code" form:"code"` client, err := authClient(req.ClientID)
RedirectURI string `json:"redirect_uri" form:"redirect_uri"` if err != nil {
CodeVerifier string `json:"code_verifier" form:"code_verifier"` return err
} }
type GrantClientData struct { if client.RedirectURI == nil || *client.RedirectURI != req.RedirectURI {
return errors.New("客户端重定向URI错误")
} }
type GrantRefreshData struct { // todo 检查 scope
RefreshToken string `json:"refresh_token" form:"refresh_token"`
// 授权确认页面
return nil
} }
type GrantPasswordData struct { type AuthorizeGetReq struct {
LoginType PasswordGrantType `json:"login_type" form:"login_type"` ResponseType string `json:"response_type" validate:"eq=code"`
Username string `json:"username" form:"username"` ClientID string `json:"client_id" validate:"required"`
Password string `json:"password" form:"password"` RedirectURI string `json:"redirect_uri" validate:"required"`
Remember bool `json:"remember" form:"remember"` Scope string `json:"scope"`
State string `json:"state"`
} }
type TokenResp struct { func AuthorizePost(ctx *fiber.Ctx) error {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"` // todo 解析用户授权的范围
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"` return nil
Scope string `json:"scope,omitempty"`
} }
type TokenErrResp struct { type AuthorizePostReq struct {
Error string `json:"error"` Accept bool `json:"accept"`
Description string `json:"error_description,omitempty"` Scope string `json:"scope"`
} }
// Token 令牌端点
func Token(c *fiber.Ctx) error { func Token(c *fiber.Ctx) error {
now := time.Now() now := time.Now()
@@ -165,6 +148,75 @@ func Token(c *fiber.Ctx) error {
}) })
} }
type TokenReq struct {
GrantType GrantType `json:"grant_type" form:"grant_type"`
ClientID string `json:"client_id" form:"client_id"`
ClientSecret string `json:"client_secret" form:"client_secret"`
Scope string `json:"scope" form:"scope"`
GrantCodeData
GrantClientData
GrantRefreshData
GrantPasswordData
}
type GrantCodeData struct {
Code string `json:"code" form:"code"`
RedirectURI string `json:"redirect_uri" form:"redirect_uri"`
CodeVerifier string `json:"code_verifier" form:"code_verifier"`
}
type GrantClientData struct {
}
type GrantRefreshData struct {
RefreshToken string `json:"refresh_token" form:"refresh_token"`
}
type GrantPasswordData struct {
LoginType PwdLoginType `json:"login_type" form:"login_type"`
LoginPool PwdLoginPool `json:"login_pool" form:"login_pool"`
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
Remember bool `json:"remember" form:"remember"`
}
type GrantType string
const (
GrantAuthorizationCode = GrantType("authorization_code") // 授权码模式
GrantClientCredentials = GrantType("client_credentials") // 客户端凭证模式
GrantRefreshToken = GrantType("refresh_token") // 刷新令牌模式
GrantPassword = GrantType("password") // 密码模式(私有扩展)
)
type PwdLoginType string
const (
PwdLoginByPassword = PwdLoginType("password") // 账号密码
PwdLoginByPhone = PwdLoginType("phone_code") // 手机验证码
PwdLoginByEmail = PwdLoginType("email_code") // 邮箱验证码
)
type PwdLoginPool string
const (
PwdLoginAsUser = PwdLoginPool("user") // 用户池
PwdLoginAsAdmin = PwdLoginPool("admin") // 管理员池
)
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token,omitempty"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope,omitempty"`
}
type TokenErrResp struct {
Error string `json:"error"`
Description string `json:"error_description,omitempty"`
}
func authAuthorizationCode(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m.Session, error) { func authAuthorizationCode(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m.Session, error) {
// 检查 code 获取用户授权信息 // 检查 code 获取用户授权信息
@@ -226,7 +278,7 @@ func authAuthorizationCode(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.
session.RefreshTokenExpires = u.P(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second)) session.RefreshTokenExpires = u.P(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))
} }
err = SaveSession(session) err = SaveSession(q.Q, session)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -249,7 +301,7 @@ func authClientCredential(c *fiber.Ctx, auth *AuthCtx, _ *TokenReq, now time.Tim
} }
// 保存会话 // 保存会话
err := SaveSession(session) err := SaveSession(q.Q, session)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -261,71 +313,85 @@ func authPassword(c *fiber.Ctx, auth *AuthCtx, req *TokenReq, now time.Time) (*m
ip, _ := orm.ParseInet(c.IP()) // 可空字段,忽略异常 ip, _ := orm.ParseInet(c.IP()) // 可空字段,忽略异常
ua := u.X(c.Get(fiber.HeaderUserAgent)) ua := u.X(c.Get(fiber.HeaderUserAgent))
// 分池认证
var err error
var user *m.User var user *m.User
err := q.Q.Transaction(func(tx *q.Query) (err error) { var admin *m.Admin
switch req.LoginType {
case GrantPasswordPhone: pool := req.LoginPool
user, err = authUserBySms(tx, req.Username, req.Password) if pool == "" {
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { pool = PwdLoginAsUser
return err
} }
if user == nil { switch pool {
case PwdLoginAsUser:
user, err = authUser(req.LoginType, req.Username, req.Password)
if err != nil {
if req.LoginType != PwdLoginByPhone || !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
// 手机号首次登录的自动创建用户
user = &m.User{ user = &m.User{
Phone: req.Username, Phone: req.Username,
Username: u.P(req.Username), Username: u.P(req.Username),
Status: m.UserStatusEnabled, Status: m.UserStatusEnabled,
} }
} }
case GrantPasswordEmail:
user, err = authUserByEmail(tx, req.Username, req.Password)
if err != nil {
return err
}
case GrantPasswordSecret:
user, err = authUserByPassword(tx, req.Username, req.Password)
if err != nil {
return err
}
default:
return ErrAuthorizeInvalidRequest
}
// 账户状态
if user.Status == m.UserStatusDisabled {
slog.Debug("账户状态异常", "username", req.Username, "status", user.Status)
return core.NewBizErr("账号无法登录")
}
// 更新用户的登录时间 // 更新用户的登录时间
user.LastLogin = u.P(time.Now()) user.LastLogin = u.P(time.Now())
user.LastLoginIP = ip user.LastLoginIP = ip
user.LastLoginUA = ua user.LastLoginUA = ua
if err := tx.User.Save(user); err != nil {
return err
}
return nil case PwdLoginAsAdmin:
}) admin, err = authAdmin(req.LoginType, req.Username, req.Password)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// 更新管理员登录时间
admin.LastLogin = u.P(time.Now())
admin.LastLoginIP = ip
admin.LastLoginUA = ua
}
// 生成会话 // 生成会话
session := &m.Session{ session := &m.Session{
IP: ip, IP: ip,
UA: ua, UA: ua,
UserID: &user.ID,
ClientID: &auth.Client.ID, ClientID: &auth.Client.ID,
Scopes: u.X(req.Scope), Scopes: u.X(req.Scope),
AccessToken: uuid.NewString(), AccessToken: uuid.NewString(),
AccessTokenExpires: now.Add(time.Duration(env.SessionAccessExpire) * time.Second), AccessTokenExpires: now.Add(time.Duration(env.SessionAccessExpire) * time.Second),
} }
if user != nil {
session.UserID = &user.ID
}
if admin != nil {
session.AdminID = &admin.ID
}
if req.Remember { if req.Remember {
session.RefreshToken = u.P(uuid.NewString()) session.RefreshToken = u.P(uuid.NewString())
session.RefreshTokenExpires = u.P(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second)) session.RefreshTokenExpires = u.P(now.Add(time.Duration(env.SessionRefreshExpire) * time.Second))
} }
err = SaveSession(session) // 保存用户更新和会话
err = q.Q.Transaction(func(tx *q.Query) error {
if err := SaveSession(tx, session); err != nil {
return err
}
if user != nil {
if err := tx.User.Save(user); err != nil {
return err
}
}
if admin != nil {
if err := tx.Admin.Save(admin); err != nil {
return err
}
}
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -353,7 +419,7 @@ func authRefreshToken(_ *fiber.Ctx, _ *AuthCtx, req *TokenReq, now time.Time) (*
} }
// 保存令牌 // 保存令牌
err = SaveSession(session) err = SaveSession(q.Q, session)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -394,14 +460,87 @@ func sendError(c *fiber.Ctx, err error, description ...string) error {
return err return err
} }
func Revoke() error { // Revoke 令牌撤销端点
func Revoke(ctx *fiber.Ctx) error {
_, err := GetAuthCtx(ctx).PermitUser()
if err != nil {
// 用户未登录
return nil return nil
} }
func Introspect() error { // 解析请求参数
req := new(RevokeReq)
if err := ctx.BodyParser(req); err != nil {
return err
}
// 删除会话
err = RemoveSession(ctx.Context(), req.AccessToken, req.RefreshToken)
if err != nil {
return err
}
return nil return nil
} }
type RevokeReq struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
// Introspect 令牌检查端点
func Introspect(ctx *fiber.Ctx) error {
// 验证权限
authCtx, err := GetAuthCtx(ctx).PermitUser()
if err != nil {
return err
}
// 获取用户信息
profile, err := q.User.
Where(q.User.ID.Eq(authCtx.User.ID)).
Omit(q.User.DeletedAt).
Take()
if err != nil {
return err
}
// 检查用户是否设置了密码
hasPassword := false
if profile.Password != nil && *profile.Password != "" {
hasPassword = true
profile.Password = nil // 不返回密码
}
// 掩码敏感信息
if profile.Phone != "" {
profile.Phone = maskPhone(profile.Phone)
}
if profile.IDNo != nil && *profile.IDNo != "" {
profile.IDNo = u.P(maskIdNo(*profile.IDNo))
}
return ctx.JSON(IntrospectResp{*profile, hasPassword})
}
type IntrospectResp struct {
m.User
HasPassword bool `json:"has_password"` // 是否设置了密码
}
func maskPhone(phone string) string {
if len(phone) < 11 {
return phone
}
return phone[:3] + "****" + phone[7:]
}
func maskIdNo(idNo string) string {
if len(idNo) < 18 {
return idNo
}
return idNo[:3] + "*********" + idNo[14:]
}
type CodeContext struct { type CodeContext struct {
UserID int32 `json:"user_id"` UserID int32 `json:"user_id"`
ClientID int32 `json:"client_id"` ClientID int32 `json:"client_id"`

View File

@@ -6,15 +6,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
"platform/web/core"
m "platform/web/models"
q "platform/web/queries"
s "platform/web/services"
"strings" "strings"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"golang.org/x/crypto/bcrypt"
) )
func Authenticate() fiber.Handler { func Authenticate() fiber.Handler {
@@ -123,67 +118,3 @@ func authBasic(_ context.Context, token string) (*AuthCtx, error) {
Scopes: []string{}, Scopes: []string{},
}, nil }, nil
} }
func authClient(clientId, clientSecret string) (*m.Client, error) {
// 获取客户端信息
client, err := q.Client.
Where(
q.Client.ClientID.Eq(clientId),
q.Client.Status.Eq(1)).
Take()
if err != nil {
return nil, err
}
// 检查客户端密钥
if client.Spec == m.ClientSpecWeb || client.Spec == m.ClientSpecAPI {
if bcrypt.CompareHashAndPassword([]byte(client.ClientSecret), []byte(clientSecret)) != nil {
return nil, errors.New("客户端密钥错误")
}
}
// todo 查询客户端关联权限
// 组织授权信息(一次性请求)
return client, nil
}
func authUserBySms(tx *q.Query, username, code string) (*m.User, error) {
// 验证验证码
err := s.Verifier.VerifySms(context.Background(), username, code)
if err != nil {
return nil, core.NewBizErr("短信认证失败:%w", err)
}
// 查找用户
return tx.User.Where(tx.User.Phone.Eq(username)).Take()
}
func authUserByEmail(tx *q.Query, username, code string) (*m.User, error) {
return nil, core.NewServErr("邮箱登录不可用")
}
func authUserByPassword(tx *q.Query, username, password string) (*m.User, error) {
user, err := tx.User.
Where(tx.User.Phone.Eq(username)).
Or(tx.User.Email.Eq(username)).
Or(tx.User.Username.Eq(username)).
Take()
if err != nil {
slog.Debug("查找用户失败", "error", err)
return nil, core.NewBizErr("用户不存在或密码错误")
}
// 验证密码
if user.Password == nil || *user.Password == "" {
slog.Debug("用户未设置密码", "username", username)
return nil, core.NewBizErr("用户不存在或密码错误")
}
if bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) != nil {
slog.Debug("密码验证失败", "username", username)
return nil, core.NewBizErr("用户不存在或密码错误")
}
return user, nil
}

View File

@@ -29,8 +29,8 @@ func FindSessionByRefresh(refreshToken string, now time.Time) (*m.Session, error
).First() ).First()
} }
func SaveSession(session *m.Session) error { func SaveSession(tx *q.Query, session *m.Session) error {
return q.Session.Save(session) return tx.Session.Save(session)
} }
func RemoveSession(ctx context.Context, accessToken string, refreshToken string) error { func RemoveSession(ctx context.Context, accessToken string, refreshToken string) error {

View File

@@ -1,97 +0,0 @@
package handlers
import (
"platform/pkg/u"
auth2 "platform/web/auth"
m "platform/web/models"
q "platform/web/queries"
"github.com/gofiber/fiber/v2"
)
// region /revoke
type RevokeReq struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
func Revoke(c *fiber.Ctx) error {
_, err := auth2.GetAuthCtx(c).PermitUser()
if err != nil {
// 用户未登录
return nil
}
// 解析请求参数
req := new(RevokeReq)
if err := c.BodyParser(req); err != nil {
return err
}
// 删除会话
err = auth2.RemoveSession(c.Context(), req.AccessToken, req.RefreshToken)
if err != nil {
return err
}
return nil
}
// endregion
// region /profile
type IntrospectResp struct {
m.User
HasPassword bool `json:"has_password"` // 是否设置了密码
}
func Introspect(c *fiber.Ctx) error {
// 验证权限
authCtx, err := auth2.GetAuthCtx(c).PermitUser()
if err != nil {
return err
}
// 获取用户信息
profile, err := q.User.
Where(q.User.ID.Eq(authCtx.User.ID)).
Omit(q.User.DeletedAt).
Take()
if err != nil {
return err
}
// 检查用户是否设置了密码
hasPassword := false
if profile.Password != nil && *profile.Password != "" {
hasPassword = true
profile.Password = nil // 不返回密码
}
// 掩码敏感信息
if profile.Phone != "" {
profile.Phone = maskPhone(profile.Phone)
}
if profile.IDNo != nil && *profile.IDNo != "" {
profile.IDNo = u.P(maskIdNo(*profile.IDNo))
}
return c.JSON(IntrospectResp{*profile, hasPassword})
}
func maskPhone(phone string) string {
if len(phone) < 11 {
return phone
}
return phone[:3] + "****" + phone[7:]
}
func maskIdNo(idNo string) string {
if len(idNo) < 18 {
return idNo
}
return idNo[:3] + "*********" + idNo[14:]
}
// endregion

View File

@@ -5,6 +5,7 @@ import (
"github.com/gofiber/contrib/otelfiber/v2" "github.com/gofiber/contrib/otelfiber/v2"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover" "github.com/gofiber/fiber/v2/middleware/recover"
"github.com/gofiber/fiber/v2/middleware/requestid" "github.com/gofiber/fiber/v2/middleware/requestid"
@@ -19,6 +20,14 @@ func ApplyMiddlewares(app *fiber.App) {
EnableStackTrace: true, EnableStackTrace: true,
})) }))
// cors
app.Use(cors.New(cors.Config{
AllowCredentials: true,
AllowOriginsFunc: func(origin string) bool {
return true
},
}))
// logger // logger
app.Use(logger.New(logger.Config{ app.Use(logger.New(logger.Config{
Next: func(c *fiber.Ctx) bool { Next: func(c *fiber.Ctx) bool {

View File

@@ -13,9 +13,11 @@ func ApplyRouters(app *fiber.App) {
// 认证 // 认证
auth := api.Group("/auth") auth := api.Group("/auth")
auth.Get("/authorize", auth2.AuthorizeGet)
auth.Post("/authorize", auth2.AuthorizePost)
auth.Post("/token", auth2.Token) auth.Post("/token", auth2.Token)
auth.Post("/revoke", handlers.Revoke) auth.Post("/revoke", auth2.Revoke)
auth.Post("/introspect", handlers.Introspect) auth.Post("/introspect", auth2.Introspect)
auth.Post("/verify/sms", handlers.SmsCode) auth.Post("/verify/sms", handlers.SmsCode)
// 用户 // 用户