[优化] 更换 JWT 认证机制

This commit is contained in:
ryan
2026-06-04 11:30:44 +08:00
parent 3dbc7b3045
commit 161e6c4e86
20 changed files with 629 additions and 76 deletions
+2 -2
View File
@@ -3,7 +3,7 @@
本文件是 OpenFlare 的 AI 接手入口,不承载详细设计、规范和计划。接手项目时,请根据以下分层文档指引进行阅读与开发:
### 面向 AI 的开发指导规范 (AI Guidelines) 必须阅读 ⚠️
### 面向 AI 的开发指导规范 (AI Guidelines) 必须阅读
为了理解 OpenFlare 的设计理念、产品边界、核心机制以及代码编写的工程约束,**AI 在接手项目时必须首先且完整阅读以下文档**:
@@ -66,5 +66,5 @@
* 产品启动、部署、升级、联调方式变化:更新 `docs/guide/quick-start.md`、`docs/deployment/deployment.md` 和 `README.md`
* 用户操作路径、常见场景变化:更新 `docs/guide/usage.md`
* 本地开发、测试、构建方式变化:更新 `docs/design/development.md`
* 常见故障、排查路径变化:更新 `docs/guide/troubleshooting.md`
* 环境变量、命令行参数、运行时配置、Agent 配置变化:更新 `docs/reference/configuration.md`
* **任何代码、配置或文档变更完成后:必须在 [`docs/changelog/index.md`](./docs/changelog/index.md) 的 `[Unreleased]` 区块补充对应条目(新增 / 变更 / 修复),格式遵循文件内已有模板。**
+411
View File
@@ -0,0 +1,411 @@
---
sidebar: false
---
# 更新日志
本文件记录 OpenFlare 每个版本的重要变更。
格式基于 [Keep a Changelog](http://keepachangelog.com/),版本号遵循 [语义化版本](http://semver.org/)。
## 重大变更
> [!IMPORTANT]
> 2.3.2 开始使用 JWT_SECRET 环境变量替代 SESSION_SECRET 进行管理端 API 的 JWT 签名密钥管理。SESSION_SECRET 将会在之后的版本中逐步废弃,请务必尽快迁移到 JWT_SECRET。
## [Unreleased]
### 说明
### 新增
### 变更
---
## [v2.3.2] - 2026-06-04
### 说明
> [!IMPORTANT]
> 2.3.2 开始使用 JWT_SECRET 环境变量替代 SESSION_SECRET 进行管理端 API 的 JWT 签名密钥管理。SESSION_SECRET 将会在之后的版本中逐步废弃,请务必尽快迁移到 JWT_SECRET。
### 新增
- 新增 `JWT_SECRET` 环境变量,专用于管理端 API JWT 签名密钥;生产环境必须显式配置
- 新增 VitePress 更新日志页面(`docs/changelog/index.md`),记录所有版本变更历史
### 变更
- 管理端 API 鉴权框架迁移至 `gin-jwt`
- 认证方式变更为 Headers 认证.
- `JWT_SECRET` 优先于 `SESSION_SECRET` 用于 JWT 签名;未配置时回退到 `SESSION_SECRET`,向下兼容
- 屏蔽手动升级入口(`/api/update/manual-upload`、`/api/update/manual-upgrade`),前端隐藏对应 UI 组件
---
## [v2.3.1] - 2026-06-03
### 变更
- 屏蔽手动升级入口,前端隐藏对应 UI 组件
- POW 与 WAF 规则合并, 统一逻辑处理
---
## [v2.3.0] - 2026-06-03
### 新增
- WAF IP 组支持订阅模式,可从远程文本或 JSON 源定时同步
- 新增 Pages 静态站点托管,支持 SPA fallback 路由配置
- Agent 实现 WebSocket 实时推送,Server 发布配置后立即通知在线 Agent
### 变更
- Agent 数据面与 OpenResty 合并为集成镜像部署方式
- 访问日志与观测数据支持数据库分片,按 ID 分片替代原有逻辑
---
## [v2.2.8] - 2026-06-03
### 修复
- 修复多域名部署场景下跨域认证绕过安全漏洞
---
## [v2.2.6] - 2026-06-02
### 新增
- 新增 Uptime Kuma 集成,支持自动同步监控任务
- WAF 新增 PoW(工作量证明)防护能力,可配置有效期
### 变更
- 内网穿透支持 TunnelRelay 中继节点(frps),新增 OpenFlared 客户端(frpc)
---
## [v2.2.5] - 2026-06-02
### 新增
- 新增 WAF 自动 IP 组,支持基于 Expr 规则定时聚合请求日志更新名单
- WAF IP 组黑白名单支持直接引用 IP 组对象
### 变更
- WAF 规则组与网站解耦,支持全局规则组和自定义规则组独立管理
---
## [v2.2.4] - 2026-06-02
### 新增
- WAF 规则组新增拦截返回配置 Tab
### 修复
- 修复 WAF 配置发布后部分规则不生效的问题
---
## [v2.2.3] - 2026-06-02
### 新增
- 新增 WAF 安全防护模块,支持 IP 黑白名单和地域拦截规则
---
## [v2.2.2] - 2026-06-01
### 变更
- 观测数据支持按时间窗口自动清理,新增数据库自动清理调度器
---
## [v2.2.1] - 2026-06-01
### 修复
- 修复仪表板概览数据压缩与规范化问题
---
## [v2.2.0] - 2026-06-01
### 新增
- 新增 TLS 证书转换为 ACME 托管证书的接口(`/convert-acme`)
- 新增 ACME 账号与 DNS 账号管理页面
- 支持 Let's Encrypt 自动申请与续期
---
## [v2.1.1] - 2026-06-01
### 变更
- Agent 架构调整,采用集成镜像方式内置 OpenResty
---
## [v2.0.3] - 2026-05-31
### 修复
- 修复版本号生成逻辑,确保使用当日最大序列号
---
## [v2.0.1] - 2026-05-30
### 修复
- 修复 GitHub 登录逻辑异常
---
## [v2.0.0] - 2026-05-30
### 新增
- 全面重构发布模型,引入配置版本不可变快照机制
- 支持配置版本回滚(重新激活旧版本)
- 新增 `source_config_json` 与 `support_files` 供 Agent 获取完整配置包
- 新增节点专属 Agent Token 与 Discovery Token 双轨鉴权
### 变更
- 数据库迁移框架切换至 goose,统一管理版本升级步骤
- Agent API 与管理端 API 鉴权完全分离
---
## [v1.9.3] - 2026-05-30
### 修复
- 修复节点 IP 自动探测逻辑,优先使用公网地址
---
## [v1.9.2] - 2026-05-29
### 变更
- Agent 心跳超时后自动退回 HTTP 轮询模式
---
## [v1.9.1] - 2026-05-29
### 修复
- 修复 Agent WebSocket 升级失败时的重连逻辑
---
## [v1.9.0] - 2026-05-29
### 新增
- Agent 支持 WebSocket 长连接,Server 发布后实时推送配置变更
---
## [v1.8.0] - 2026-05-26
### 新增
- 支持自定义 DNS 解析器(`OpenRestyResolvers`)
- 新增历史配置快照清理功能
### 变更
- CORS 配置支持动态源与凭证
- 上游统一渲染为命名 `upstream` 并启用 keepalive
---
## [v1.7.0] - 2026-05-25
### 新增
- 新增 ACME 和 DNS 账号管理功能,支持证书申请与续期
### 变更
- 移除新用户注册功能
- 更新 Go 版本要求至 1.25+
---
## [v1.6.1] - 2026-05-13
### 修复
- 修复个人设置页无法查看第三方认证源及解绑功能
---
## [v1.6.0] - 2026-05-13
### 新增
- 支持 OIDC 单点登录(SSO)
---
## [v1.5.0] - 2026-04-25
### 新增
- 集成 PoW(Anubis)防护,支持有效期配置
---
## [v1.4.0] - 2026-04-01
### 新增
- 支持域名级别独立绑定 TLS 证书,每个域名可单独选择证书
- 新增批量更新配置项接口
- 新增 Agent 卸载脚本
### 变更
- 禁用新用户自助注册
- 默认服务器块新增 HTTPS 握手拒绝支持
---
## [v1.3.2] - 2026-03-30
### 新增
- 网站配置支持多域名绑定与共享设置
- 新增抽屉式规则创建组件
---
## [v1.3.1] - 2026-03-20
### 新增
- 新增源站管理功能,支持源站创建、更新与删除
### 变更
- 重构代理路由页面,优化输入组件与样式
---
## [v1.3.0] - 2026-03-19
### 新增
- 新增数据库观测数据手动和自动清理策略
- 节点访问日志支持数据库分片,按 ID 分片
### 变更
- 数据库版本管理与迁移逻辑重构
---
## [v1.2.0] - 2026-03-19
### 新增
- 支持多上游地址负载均衡
- 新增缓存策略配置(路径前缀、精确路径)
- 节点健康事件清理功能
### 变更
- 上游渲染改为命名 upstream 并启用 keepalive
- 更新 HTTPS 配置,启用 reuseport 与 epoll 事件模型
---
## [v1.1.2] - 2026-03-18
### 变更
- HTTPS 启用 HTTP/2 支持
---
## [v1.1.1] - 2026-03-18
### 新增
- 新增获取配置版本详情 API
### 变更
- 仪表板概览数据结构优化,添加压缩与规范化
---
## [v1.1.0] - 2026-03-18
### 新增
- 新增应用日志分页查询与清理功能
- 新增访问日志 IP 汇总与趋势查询
- 新增 OpenResty DNS 解析器指令支持
- Docker 部署支持在运行中容器内执行 reload
### 修复
- 修复应用结果警告逻辑
- Lua 和证书文件管理重构,优化文件同步与清理机制
---
## [v1.0.2] - 2026-03-17
### 新增
- 支持 PostgreSQL 数据库,添加数据库迁移逻辑
- 新增 Docker Compose 配置,支持 PostgreSQL 联动部署
### 变更
- 多个管理端 API 请求方法从 PUT/DELETE 统一改为 POST
---
## [v1.0.1] - 2026-03-16
### 新增
- 新增 `origin_host` 字段,支持覆盖回源请求的 Host 头
### 修复
- 修复代理配置中 SSL 服务器名称和主机头覆盖逻辑
---
## [v1.0.0] - 2026-03-15
OpenFlare 首个正式版本发布。
### 新增
- 管理端 UI、管理 API、Agent API 基础功能
- 反向代理配置管理与 OpenResty 配置渲染
- 配置版本发布与 Agent 同步
- TLS 证书导入与管理
- 节点注册、心跳与状态观测
- SQLite 数据库支持
+4 -2
View File
@@ -10,7 +10,8 @@ export default defineAdditionalConfig({
sidebar: {
'/guide/': { base: '/guide/', items: sidebarGuide() },
'/reference/': { base: '/reference/', items: sidebarReference() },
'/design/': { base: '/design/', items: sidebarDesign() }
'/design/': { base: '/design/', items: sidebarDesign() },
'/changelog/': false
},
editLink: {
@@ -57,7 +58,8 @@ function nav(): DefaultTheme.NavItem[] {
return [
{ text: '指南', link: '/guide/', activeMatch: '/guide/' },
{ text: '参考', link: '/reference/', activeMatch: '/reference/' },
{ text: '设计', link: '/design/', activeMatch: '/design/' }
{ text: '设计', link: '/design/', activeMatch: '/design/' },
{ text: '更新日志', link: '/changelog/', activeMatch: '/changelog/' }
]
}
+3 -3
View File
@@ -2,7 +2,7 @@
你会学到:OpenFlare 的推荐部署方式、Server 与 Agent 的运行要求、源码启动方式、联调步骤、升级与卸载入口。
生产环境建议使用 PostgreSQL 作为 Server 数据库,并为 Server 显式配置 `SESSION_SECRET`。Agent 部署方式推荐为 Docker 部署(即直接使用内置 OpenResty 的 Agent 镜像);亦支持通过安装脚本或手动本地运行。
生产环境建议使用 PostgreSQL 作为 Server 数据库,并为 Server 显式配置 `JWT_SECRET`。Agent 部署方式推荐为 Docker 部署(即直接使用内置 OpenResty 的 Agent 镜像);亦支持通过安装脚本或手动本地运行。
## 部署拓扑
@@ -107,7 +107,7 @@ services:
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
JWT_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
@@ -144,7 +144,7 @@ pnpm build
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export JWT_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
# 可选:设置后优先使用 PostgreSQL。
+6 -6
View File
@@ -13,7 +13,7 @@ OpenFlare Server 是 Gin + GORM 单体控制面,负责管理端 UI、管理 AP
| pnpm | 推荐通过 `corepack enable` 使用项目声明的 pnpm |
| 数据库 | SQLite 文件目录可写,或可访问的 PostgreSQL 实例 |
生产环境建议显式配置 `SESSION_SECRET`,并优先使用 PostgreSQL。
生产环境必须显式配置 `JWT_SECRET`,并优先使用 PostgreSQL。
## 构建管理端前端
@@ -38,7 +38,7 @@ pnpm test
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export JWT_SECRET='replace-with-a-long-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
@@ -54,7 +54,7 @@ http://localhost:3000
```bash
cd openflare_server
export SESSION_SECRET='replace-with-a-long-random-string'
export JWT_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='info'
go run .
@@ -81,7 +81,7 @@ docker run -d \
--name openflare-server \
-p 3000:3000 \
-v $(pwd)/openflare-data:/data \
-e SESSION_SECRET='replace-with-a-long-random-string' \
-e JWT_SECRET='replace-with-a-long-random-string' \
-e SQLITE_PATH='/data/openflare.db' \
-e GIN_MODE='release' \
-e LOG_LEVEL='info' \
@@ -91,7 +91,7 @@ docker run -d \
启动参数说明:
* **`-p 3000:3000`**:映射宿主机 `3000` 端口到容器内 `3000` 端口。
* **`-v $(pwd)/openflare-data:/data`**:挂载本地目录到容器的 `/data`,确保数据库文件 `openflare.db` 在重启或重建容器时不丢失。
* **`SESSION_SECRET`**:建议配置的临时 Session 签名密钥,主要用于 OAuth 状态等非管理端 API 鉴权流程;管理端 API 登录凭证通过 `OPENFLARE_TOKEN` 请求头传递。
* **`JWT_SECRET`**:管理端 API 登录令牌的 JWT 签名密钥,生产环境必须配置,避免重启后已登录令牌全部失效。
---
@@ -127,7 +127,7 @@ services:
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-random-string
JWT_SECRET: replace-with-random-string
SQLITE_PATH: /data/openflare.db
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
+2 -2
View File
@@ -39,7 +39,7 @@ SQLite 模式:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export JWT_SECRET='dev-jwt-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
@@ -49,7 +49,7 @@ PostgreSQL 模式:
```bash
cd openflare_server
export SESSION_SECRET='dev-session-secret'
export JWT_SECRET='dev-jwt-secret'
export DSN='postgres://openflare:secret@127.0.0.1:5432/openflare?sslmode=disable'
export LOG_LEVEL='debug'
go run .
+1 -1
View File
@@ -54,7 +54,7 @@ services:
ports:
- "3000:3000"
environment:
SESSION_SECRET: replace-with-a-long-random-string
JWT_SECRET: replace-with-a-long-random-string
DSN: postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable
GIN_MODE: release
LOG_LEVEL: info
+1 -1
View File
@@ -164,7 +164,7 @@ WAF 规则组、网站绑定或 PoW 配置修改后,需要重新发布并激
## 推荐实践
* 生产环境显式配置 `SESSION_SECRET`,并优先使用 PostgreSQL。
* 生产环境必须显式配置 `JWT_SECRET`,并优先使用 PostgreSQL。
* 修改网站配置后先看预览或 diff,再发布。
* 每次发布后检查节点详情与应用记录。
* 多节点部署时保持 Agent 到 Server 的网络路径稳定。
+1 -1
View File
@@ -8,7 +8,7 @@
```bash
cd openflare_server
export SESSION_SECRET='replace-with-random-string'
export JWT_SECRET='replace-with-random-string'
export SQLITE_PATH='./openflare.db'
export LOG_LEVEL='info'
go run .
+4 -4
View File
@@ -64,7 +64,7 @@ go run . --port 3000 --log-dir ./logs
| `PORT` | Server 监听端口 | `3000` |
| `GIN_MODE` | Gin 运行模式 | 非 `debug` 时按 release |
| `LOG_LEVEL` | 日志等级 | `info` |
| `SESSION_SECRET` | 临时 Session 签名密钥,主要用于 OAuth 状态等非管理端 API 鉴权流程 | 启动时随机生成 |
| `JWT_SECRET` | 管理端 API 登录令牌的 JWT 签名密钥,生产环境必须显式配置 | 启动时随机生成 |
| `SQLITE_PATH` | SQLite 数据库文件路径 | `openflare.db` |
| `DSN` | PostgreSQL DSN,设置后优先于 SQLite | 空 |
| `SQL_DSN` | 兼容旧命名的 PostgreSQL DSN,优先级低于 `DSN` | 空 |
@@ -76,7 +76,7 @@ go run . --port 3000 --log-dir ./logs
* `DSN` 与 `SQL_DSN` 同时存在时优先使用 `DSN`。
* `DSN` 或 `SQL_DSN` 与 `SQLITE_PATH` 同时存在时优先使用 PostgreSQL。
* 当目标 PostgreSQL 数据库为空且本地 `SQLITE_PATH` 文件存在时,Server 启动阶段会自动迁移 SQLite 数据,并在日志中输出按表迁移进度。
* `SESSION_SECRET` 生产环境建议显式配置,避免 OAuth 授权状态等临时会话在重启后失效;管理端 API 登录凭证不再通过 Cookie Session 传递,而是使用 `OPENFLARE_TOKEN` 请求头。
* `JWT_SECRET` 用于管理端 API 登录令牌的签名与验证,生产环境必须显式配置,避免重启后所有已登录令牌失效。
* `REDIS_CONN_STRING` 未配置时,相关能力回退为进程内实现。
## 运行时 Option
@@ -295,7 +295,7 @@ OpenResty 性能参数与缓存参数继续统一保存在 `Option` 表。当前
### 生产 Server + PostgreSQL
```bash
export SESSION_SECRET='replace-with-a-long-random-string'
export JWT_SECRET='replace-with-a-long-random-string'
export DSN='postgres://openflare:replace-with-strong-password@postgres:5432/openflare?sslmode=disable'
export GIN_MODE='release'
export LOG_LEVEL='info'
@@ -304,7 +304,7 @@ export LOG_LEVEL='info'
### 本地 Server + SQLite
```bash
export SESSION_SECRET='dev-session-secret'
export JWT_SECRET='dev-jwt-secret'
export SQLITE_PATH='./openflare-dev.db'
export LOG_LEVEL='debug'
go run .
+1
View File
@@ -17,6 +17,7 @@ var HomePageLink = ""
// Any options with "Secret", "Token" in its key won't be return by GetOptions
var SessionSecret = uuid.New().String()
var JWTSecret = "" // if empty, falls back to SessionSecret; set via JWT_SECRET env var
var SQLitePath = "openflare.db"
var SQLDSN = ""
+3
View File
@@ -48,6 +48,9 @@ func ParseFlags() {
if os.Getenv("SESSION_SECRET") != "" {
SessionSecret = os.Getenv("SESSION_SECRET")
}
if os.Getenv("JWT_SECRET") != "" {
JWTSecret = os.Getenv("JWT_SECRET")
}
if os.Getenv("SQLITE_PATH") != "" {
SQLitePath = os.Getenv("SQLITE_PATH")
}
+13 -30
View File
@@ -1,16 +1,14 @@
package controller
import (
"errors"
"openflare/common"
"openflare/middleware"
"openflare/model"
"openflare/utils/security"
"openflare/utils/validation"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type LoginRequest struct {
@@ -47,17 +45,22 @@ func Login(c *gin.Context) {
// setup token and then return user info
func setLoginToken(user *model.User) (*model.User, error) {
token, err := ensureUserOpenFlareToken(user)
// Generate a signed JWT using gin-jwt middleware
tokenString, _, err := middleware.JWTMiddleware.TokenGenerator(user)
if err != nil {
return nil, err
}
// Persist JWT in DB so we can invalidate it on logout
if err := model.DB.Model(user).Update("token", tokenString).Error; err != nil {
return nil, err
}
cleanUser := &model.User{
Id: user.Id,
Username: user.Username,
DisplayName: user.DisplayName,
Role: user.Role,
Status: user.Status,
Token: token,
Token: tokenString,
}
return cleanUser, nil
}
@@ -85,24 +88,6 @@ func Logout(c *gin.Context) {
respondSuccessMessage(c, "")
}
func ensureUserOpenFlareToken(user *model.User) (string, error) {
if user.Token != "" {
return user.Token, nil
}
for i := 0; i < 3; i++ {
token := strings.Replace(uuid.New().String(), "-", "", -1)
if model.DB.Where("token = ?", token).First(&model.User{}).RowsAffected != 0 {
continue
}
if err := model.DB.Model(user).Update("token", token).Error; err != nil {
return "", err
}
user.Token = token
return token, nil
}
return "", errors.New("生成登录凭证失败,请重试")
}
func currentUserFromOpenFlareToken(c *gin.Context) *model.User {
token := c.GetHeader("OpenFlare-Token")
if token == "" {
@@ -163,19 +148,17 @@ func GenerateToken(c *gin.Context) {
respondFailure(c, err.Error())
return
}
user.Token = uuid.New().String()
user.Token = strings.Replace(user.Token, "-", "", -1)
if model.DB.Where("token = ?", user.Token).First(user).RowsAffected != 0 {
respondFailure(c, "请重试,系统生成的 UUID 竟然重复了!")
// Generate a fresh JWT for the user
tokenString, _, err := middleware.JWTMiddleware.TokenGenerator(user)
if err != nil {
respondFailure(c, "生成 Token 失败: "+err.Error())
return
}
user.Token = tokenString
if err := user.Update(false); err != nil {
respondFailure(c, err.Error())
return
}
respondSuccess(c, user.Token)
}
+13 -8
View File
@@ -9,10 +9,10 @@ require (
github.com/gin-contrib/cors v1.6.0
github.com/gin-contrib/sessions v0.0.5
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.9.1
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.11.0
github.com/go-acme/lego/v4 v4.35.2
github.com/go-playground/validator/v10 v10.23.0
github.com/go-playground/validator/v10 v10.25.0
github.com/go-redis/redis/v8 v8.11.5
github.com/google/uuid v1.6.0
github.com/oschwald/maxminddb-golang v1.13.1
@@ -32,16 +32,19 @@ require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/appleboy/gin-jwt/v2 v2.10.3 // indirect
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff // indirect
github.com/bytedance/sonic v1.11.2 // indirect
github.com/bytedance/sonic v1.12.9 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -50,7 +53,8 @@ require (
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
@@ -63,7 +67,7 @@ require (
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/longbridgeapp/sqlparser v0.3.1 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
@@ -73,13 +77,14 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/arch v0.14.0 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
+30
View File
@@ -6,6 +6,8 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/appleboy/gin-jwt/v2 v2.10.3 h1:KNcPC+XPRNpuoBh+j+rgs5bQxN+SwG/0tHbIqpRoBGc=
github.com/appleboy/gin-jwt/v2 v2.10.3/go.mod h1:LDUaQ8mF2W6LyXIbd5wqlV2SFebuyYs4RDwqMNgpsp8=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
@@ -14,6 +16,11 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ=
github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
@@ -25,6 +32,9 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpV
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -52,11 +62,15 @@ github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfm
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
@@ -87,6 +101,8 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
@@ -95,6 +111,10 @@ github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
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/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
@@ -134,6 +154,8 @@ github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
@@ -180,6 +202,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5
github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
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=
@@ -196,6 +220,7 @@ github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLy
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -204,6 +229,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
@@ -218,12 +244,16 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
+1
View File
@@ -73,6 +73,7 @@ func main() {
// Initialize options
model.InitOptionMap()
middleware.InitJWTMiddleware()
geoip.InitGeoIP(common.GeoIPProvider)
backgroundCtx, cancelBackgroundTasks := context.WithCancel(context.Background())
defer cancelBackgroundTasks()
+48 -11
View File
@@ -1,17 +1,19 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
"openflare/common"
"openflare/model"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
)
const OpenFlareTokenHeader = "OpenFlare-Token"
func authHelper(c *gin.Context, minRole int) {
token := c.GetHeader(OpenFlareTokenHeader)
if token == "" {
tokenStr := c.GetHeader(OpenFlareTokenHeader)
if tokenStr == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,未登录或 token 无效",
@@ -20,16 +22,49 @@ func authHelper(c *gin.Context, minRole int) {
return
}
user := model.ValidateUserToken(token)
if user == nil || user.Username == "" {
token, err := JWTMiddleware.ParseTokenString(tokenStr)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,token 无效",
"message": "无权进行此操作,token 无效: " + err.Error(),
})
c.Abort()
return
}
if user.Status == common.UserStatusDisabled {
claims := jwt.ExtractClaimsFromToken(token)
id, ok := claims["id"].(float64)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,token 格式错误",
})
c.Abort()
return
}
dbUser := &model.User{}
dbErr := model.DB.Select([]string{"id", "username", "display_name", "role", "status", "token"}).
First(dbUser, "id = ?", int(id)).Error
if dbErr != nil || dbUser.Username == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,用户不存在",
})
c.Abort()
return
}
if dbUser.Token != tokenStr {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,token 已失效或已登出",
})
c.Abort()
return
}
if dbUser.Status == common.UserStatusDisabled {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "用户已被封禁",
@@ -37,7 +72,8 @@ func authHelper(c *gin.Context, minRole int) {
c.Abort()
return
}
if user.Role < minRole {
if int(dbUser.Role) < minRole {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无权进行此操作,权限不足",
@@ -45,9 +81,10 @@ func authHelper(c *gin.Context, minRole int) {
c.Abort()
return
}
c.Set("username", user.Username)
c.Set("role", user.Role)
c.Set("id", user.Id)
c.Set("username", dbUser.Username)
c.Set("role", dbUser.Role)
c.Set("id", dbUser.Id)
c.Set("authByToken", true)
c.Next()
}
+73
View File
@@ -0,0 +1,73 @@
package middleware
import (
"log"
"openflare/common"
"openflare/model"
"time"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
)
var JWTMiddleware *jwt.GinJWTMiddleware
// jwtSigningKey returns JWT_SECRET when set, falling back to SESSION_SECRET
// for backward compatibility with deployments that only configure SESSION_SECRET.
func jwtSigningKey() []byte {
if common.JWTSecret != "" {
return []byte(common.JWTSecret)
}
return []byte(common.SessionSecret)
}
func InitJWTMiddleware() {
var err error
JWTMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
Realm: "openflare",
Key: jwtSigningKey(),
Timeout: 24 * time.Hour,
MaxRefresh: 24 * time.Hour,
IdentityKey: "identity",
PayloadFunc: func(data interface{}) jwt.MapClaims {
if v, ok := data.(*model.User); ok {
return jwt.MapClaims{
"id": v.Id,
"username": v.Username,
"role": v.Role,
}
}
return jwt.MapClaims{}
},
IdentityHandler: func(c *gin.Context) interface{} {
claims := jwt.ExtractClaims(c)
id, ok := claims["id"].(float64)
if !ok {
return nil
}
username, _ := claims["username"].(string)
role, _ := claims["role"].(float64)
return &model.User{
Id: int(id),
Username: username,
Role: int(role),
}
},
Authorizator: func(data interface{}, c *gin.Context) bool {
return data != nil
},
Unauthorized: func(c *gin.Context, code int, message string) {
c.JSON(code, gin.H{
"success": false,
"message": "无权进行此操作,未登录或 token 无效: " + message,
})
},
TokenLookup: "header: OpenFlare-Token",
TokenHeadName: "", // Empty for raw token value directly
SendCookie: false,
})
if err != nil {
log.Fatalf("JWT Init Error: %s", err.Error())
}
}
+3 -2
View File
@@ -4,7 +4,6 @@ import (
"errors"
"openflare/common"
"openflare/utils/security"
"strings"
)
// User if you add sensitive fields, don't forget to clean them in setupLogin function.
@@ -150,11 +149,13 @@ func (user *User) FillUserByUsername() error {
return nil
}
// ValidateUserToken looks up a user by their stored JWT token string.
// JWT signature verification is handled by middleware/auth.go; this
// function is used by Logout to find and clear the token from DB.
func ValidateUserToken(token string) (user *User) {
if token == "" {
return nil
}
token = strings.Replace(token, "Bearer ", "", 1)
user = &User{}
if DB.Where("token = ?", token).First(user).RowsAffected == 1 {
return user
+9 -3
View File
@@ -17,6 +17,7 @@ import (
"net/http"
"net/http/httptest"
"openflare/common"
"openflare/middleware"
"openflare/model"
"openflare/router"
"openflare/service"
@@ -485,6 +486,7 @@ func setupTestDB(t *testing.T) {
if err := model.InitDB(); err != nil {
t.Fatalf("failed to init db: %v", err)
}
middleware.InitJWTMiddleware()
t.Cleanup(func() {
if err := model.CloseDB(); err != nil {
t.Fatalf("failed to close db: %v", err)
@@ -498,11 +500,15 @@ func prepareRootToken(t *testing.T) string {
if err := user.FillUserByUsername(); err != nil {
t.Fatalf("failed to load root user: %v", err)
}
user.Token = "phase1-test-token"
if err := model.DB.Model(user).Update("token", user.Token).Error; err != nil {
// Generate a proper JWT so auth middleware can validate it
tokenString, _, err := middleware.JWTMiddleware.TokenGenerator(user)
if err != nil {
t.Fatalf("failed to generate JWT for root user: %v", err)
}
if err := model.DB.Model(user).Update("token", tokenString).Error; err != nil {
t.Fatalf("failed to set root token: %v", err)
}
return user.Token
return tokenString
}
func performJSONRequest(t *testing.T, engine http.Handler, token string, method string, path string, body any) apiResponse {