电网接口插件项目的token认证原理

电网接口插件项目的token认证原理

一、技术实现原理与流程

1. Token 认证基本原理

Token 认证是一种无状态的身份验证机制,主要流程如下:

  1. 用户登录:用户提供凭证(用户名/密码)进行身份验证
  2. Token 生成:服务端验证成功后,生成包含用户信息的 token
  3. Token 存储:将 token 存储在缓存系统中(内存或 Redis)
  4. Token 返回:将 token 返回给客户端
  5. 后续请求:客户端在后续请求中携带 token
  6. Token 验证:服务端验证 token 的有效性和权限

2. gftoken 组件工作原理

gftoken 是一个基于 GoFrame 框架的 token 认证组件,其核心工作原理:

  1. Token 生成

    • 使用用户 ID 和时间戳等信息构造 token 数据
    • 使用配置的加密密钥对数据进行加密
    • 生成的 token 为 Base64 编码的字符串
  2. Token 存储

    • 根据配置的缓存适配器(内存/Redis)存储 token
    • 存储的键名格式为:{缓存前缀} + {token标识符}
    • 设置 token 的过期时间
  3. Token 验证流程

    • 从请求头中提取 Authorization 字段的 token
    • 解密 token 获取用户信息
    • 在缓存中查找对应的 token 记录
    • 验证 token 是否有效(未过期、未被撤销)

3. 多系统共享 Token 的技术实现

当两个独立系统需要共享 token 时,关键在于:

  1. 共享存储

    • 两个系统必须使用相同的 Redis 实例
    • Redis 配置(地址、端口、数据库编号)必须一致
  2. 一致的缓存键

    • 缓存前缀必须完全一致
    • token 的标识符生成规则必须一致
  3. 相同的加密机制

    • 加密密钥必须完全一致
    • 加密算法必须相同

二、代码示例(使用占位符替代敏感信息)

1. 配置文件示例

系统 A 的配置文件 (config.yaml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 缓存配置
cache:
prefix: "example_prefix:" # 缓存前缀,两系统必须一致
adapter: "redis" # 缓存驱动方式:memory|redis|file

# Redis 配置
redis:
default:
mode: "single"
address: "redis://example_host:6379" # Redis 地址
db: 0 # 数据库编号
user: "example_user" # 用户名
pass: "example_password" # 密码

# Token 配置
gfToken:
timeOut: 10800 # token超时时间(秒)
maxRefresh: 5400 # token自动刷新时间(秒)
multiLogin: true # 是否允许一个账号多人同时登录
encryptKey: "your_32_byte_encrypt_key_example" # 加密密钥 (32位)
excludePaths: # 排除不做登录验证的路由
- "/api/v1/login"
- "/api/v1/captcha"

系统 B 的配置文件 (config.yaml):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 缓存配置 - 与系统 A 保持一致
cache:
prefix: "example_prefix:" # 必须与系统 A 一致
adapter: "redis"

# Redis 配置 - 与系统 A 保持一致
redis:
default:
mode: "single"
address: "redis://example_host:6379" # 必须与系统 A 一致
db: 0 # 必须与系统 A 一致
user: "example_user"
pass: "example_password"

# Token 配置 - 与系统 A 保持一致
gfToken:
encryptKey: "your_32_byte_encrypt_key_example" # 必须与系统 A 一致

2. 系统 A 中的 Token 生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package system

import (
"context"
"example/pkg/gftoken"
"github.com/gogf/gf/v2/frame/g"
)

// 生成 Token 的选项
type TokenOptions struct {
Timeout int
MaxRefresh int
MultiLogin bool
ExcludePaths []string
}

// 初始化 Token 组件
func InitGfToken(ctx context.Context) *gftoken.GfToken {
// 从配置文件读取选项
options := TokenOptions{
Timeout: g.Cfg().MustGet(ctx, "gfToken.timeOut", 10800).Int(),
MaxRefresh: g.Cfg().MustGet(ctx, "gfToken.maxRefresh", 5400).Int(),
MultiLogin: g.Cfg().MustGet(ctx, "gfToken.multiLogin", false).Bool(),
ExcludePaths: g.Cfg().MustGet(ctx, "gfToken.excludePaths").Strings(),
}

// 从配置获取缓存前缀
prefix := g.Cfg().MustGet(ctx, "cache.prefix", "example_prefix:").String()

// 从配置获取缓存模式
cacheAdapter := g.Cfg().MustGet(ctx, "cache.adapter", "memory").String()

// 根据缓存模式选择存储方式
var cacheOption gftoken.OptionFunc
if cacheAdapter == "redis" {
cacheOption = gftoken.WithGRedis()
} else {
cacheOption = gftoken.WithGCache()
}

// 从配置获取加密密钥
encryptKey := g.Cfg().MustGet(ctx, "gfToken.encryptKey", "your_32_byte_encrypt_key_example").String()

// 初始化 gfToken
gfToken := gftoken.NewGfToken(
gftoken.WithEncryptKey([]byte(encryptKey)),
gftoken.WithCacheKey(prefix),
gftoken.WithTimeout(options.Timeout),
gftoken.WithMaxRefresh(options.MaxRefresh),
gftoken.WithMultiLogin(options.MultiLogin),
gftoken.WithExcludePaths(options.ExcludePaths),
cacheOption,
)

return gfToken
}

// 用户登录并生成 Token
func Login(ctx context.Context, username, password string) (string, error) {
// 验证用户名密码(示例代码,实际应查询数据库)
if username != "admin" || password != "password123" {
return "", errors.New("用户名或密码错误")
}

// 获取 gfToken 实例
gfToken := InitGfToken(ctx)

// 生成 token(用户ID为1)
token, err := gfToken.GenerateToken(ctx, "1", "")
if err != nil {
return "", err
}

return token, nil
}

3. 系统 B 中的 Token 验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package router

import (
"context"
"fmt"
"example/pkg/gftoken"
"strings"

"github.com/gogf/gf/v2/database/gredis"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)

// 初始化路由和中间件
func Init(server *ghttp.Server) {
ctx := context.Background()

// API 路由组
apiGroup := server.Group("api/v1")

// 添加响应处理中间件
apiGroup.Middleware(ghttp.MiddlewareHandlerResponse)

// 添加调试中间件
apiGroup.Middleware(func(r *ghttp.Request) {
fmt.Printf("=== Token验证开始 ===\n")
fmt.Printf("请求路径: %s\n", r.URL.Path)
fmt.Printf("请求方法: %s\n", r.Method)

authHeader := r.Header.Get("Authorization")
fmt.Printf("Authorization头: %s\n", authHeader)

if authHeader != "" && strings.HasPrefix(authHeader, "Bearer ") {
token := strings.TrimPrefix(authHeader, "Bearer ")
fmt.Printf("提取的token: %s\n", token)
}

r.Middleware.Next()

fmt.Printf("=== Token验证结束 ===\n")
})

// 初始化 Redis 客户端
var redisConfig gredis.Config
if err := g.Cfg().MustGet(ctx, "redis.default").Scan(&redisConfig); err != nil {
fmt.Printf("读取Redis配置失败: %v\n", err)
}

redisClient, err := gredis.New(&redisConfig)
if err != nil {
fmt.Printf("创建Redis客户端失败: %v\n", err)
}

// 获取加密密钥
encryptKey := g.Cfg().MustGet(ctx, "gfToken.encryptKey", "your_32_byte_encrypt_key_example").String()

// 获取缓存前缀
prefix := g.Cfg().MustGet(ctx, "cache.prefix", "example_prefix:").String()

// 初始化 gftoken
gfToken := gftoken.NewGfToken(
gftoken.WithEncryptKey([]byte(encryptKey)),
gftoken.WithCacheKey(prefix),
gftoken.WithGRedis(redisClient),
)

// 调试 Redis 中的 token
if redisClient != nil {
searchPattern := prefix + "*"
keys, err := redisClient.Do(ctx, "KEYS", searchPattern)
if err != nil {
fmt.Printf("Redis查询 %s keys失败: %v\n", searchPattern, err)
} else {
result := keys.Strings()
fmt.Printf("Redis中 %s 相关keys数量: %d\n", searchPattern, len(result))
for i, key := range result {
if i < 10 { // 只显示前10个
fmt.Printf("Redis key: %s\n", key)
}
}
}
}

// 注册 token 中间件
gfToken.Middleware(apiGroup)

// 绑定控制器
apiGroup.Bind(controllers...)
}

三、两个系统间 Token 共享的关键配置点

要实现两个系统间的 token 共享,必须确保以下配置点完全一致:

  1. Redis 连接配置

    1
    2
    3
    4
    redis:
    default:
    address: "redis://example_host:6379" # 地址必须一致
    db: 0 # 数据库编号必须一致
  2. 缓存前缀

    1
    2
    cache:
    prefix: "example_prefix:" # 前缀必须完全一致
  3. 加密密钥

    1
    2
    gfToken:
    encryptKey: "your_32_byte_encrypt_key_example" # 密钥必须完全一致
  4. Token 格式:两个系统必须使用相同的 token 格式和解析方式

四、密钥更新的安全最佳实践

  1. 定期轮换密钥

    • 建议每 90 天更换一次加密密钥
    • 更新流程应自动化,减少人为错误
  2. 密钥更新步骤

    1
    2
    3
    4
    5
    6
    1. 生成新密钥
    2. 更新系统 A 的配置
    3. 重启系统 A
    4. 使所有现有 token 失效(可选)
    5. 更新系统 B 的配置
    6. 重启系统 B
  3. 密钥管理

    • 使用密钥管理系统存储密钥,而非硬编码
    • 配置文件中的密钥应加密或使用环境变量
  4. 无缝过渡策略

    • 实现双密钥支持,允许新旧密钥共存一段时间
    • 代码示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 支持新旧两种密钥的验证
    func verifyToken(token string) bool {
    // 先尝试用新密钥验证
    if verifyWithKey(token, newEncryptKey) {
    return true
    }
    // 如果失败,尝试用旧密钥验证
    return verifyWithKey(token, oldEncryptKey)
    }
  5. 监控与审计

    • 记录所有密钥更改操作
    • 监控密钥使用情况和异常访问

通过以上实现原理和代码示例,可以成功实现两个系统间的 token 共享认证,确保用户只需在一个系统登录,即可访问另一个系统的受保护资源。