package auth import ( "context" "encoding/base64" "errors" "fmt" "log/slog" "platform/web/core" m "platform/web/models" q "platform/web/queries" s "platform/web/services" "strings" "time" "github.com/gofiber/fiber/v2" "golang.org/x/crypto/bcrypt" ) func Authenticate() fiber.Handler { return func(ctx *fiber.Ctx) error { header := ctx.Get(fiber.HeaderAuthorization) authCtx, err := authHeader(ctx.Context(), header) if err != nil { return err } if authCtx == nil { authCtx = &AuthCtx{} } SetAuthCtx(ctx, authCtx) return ctx.Next() } } func authHeader(ctx context.Context, header string) (*AuthCtx, error) { if header == "" { return nil, nil } var split = strings.Split(header, " ") if len(split) != 2 { slog.Debug("Authorization 头格式不正确") return nil, ErrAuthenticateUnauthorize } var token = strings.TrimSpace(split[1]) if token == "" { slog.Debug("提供的令牌为空") return nil, ErrAuthenticateUnauthorize } var authCtx *AuthCtx var err error switch split[0] { case "Bearer": authCtx, err = authBearer(ctx, token) if err != nil { slog.Debug("Bearer 认证失败", "err", err) return nil, ErrAuthenticateUnauthorize } case "Basic": authCtx, err = authBasic(ctx, token) if err != nil { slog.Debug("Basic 认证失败", "err", err) return nil, ErrAuthenticateUnauthorize } default: slog.Debug("无效的认证方式", "method", split[0]) return nil, ErrAuthenticateUnauthorize } return authCtx, err } func authBearer(_ context.Context, token string) (*AuthCtx, error) { session, err := FindSession(token, time.Now()) if err != nil { slog.Debug("Bearer 认证失败", "err", err) return nil, ErrAuthenticateUnauthorize } scopes := []string{} if session.Scopes != nil { scopes = strings.Split(*session.Scopes, " ") } return &AuthCtx{ User: session.User, Admin: session.Admin, Client: session.Client, Scopes: scopes, Session: session, }, nil } func authBasic(_ context.Context, token string) (*AuthCtx, error) { // 解析 Basic 认证信息 var base, err = base64.RawURLEncoding.DecodeString(token) if err != nil { base, err = base64.URLEncoding.DecodeString(token) if err != nil { return nil, errors.New("令牌格式错误,无法解析令牌") } } var split = strings.Split(string(base), ":") if len(split) != 2 { return nil, errors.New("令牌格式错误,必须是 : 格式") } client, err := authClient(split[0], split[1]) if err != nil { return nil, fmt.Errorf("客户端认证失败:%w", err) } return &AuthCtx{ Client: client, Scopes: []string{}, }, 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 }