本文目录导读:

- 核心策略:基于过期时间和最后使用时间
- 方案一:基于数据库存储(最常见,例如 OAuth2.0/ 自建系统)
- 方案二:清理 Redis 缓存中的令牌(常见于微服务架构)
- 方案三:JWT 令牌(无状态,需依赖黑名单或撤销机制)
- 方案四:文件系统令牌(API Key 文件)
- 最佳实践建议
- 总结脚本框架
清理未使用的访问令牌(Access Token)是提升系统安全性和减少资源浪费的重要操作,具体实现方式取决于你的令牌存储方式(数据库、缓存、文件系统)以及令牌的发放机制(JWT、OAuth2.0、自签发)。
以下是针对不同场景的自动化脚本清理方案和核心逻辑:
核心策略:基于过期时间和最后使用时间
清理脚本的核心逻辑通常是双重过滤:
- 已过期:令牌的
exp(过期时间)属性已过当前时间。 - 长期未使用:令牌未过期,但
last_used_at(最后使用时间)远早于当前时间(例如超过30天未使用)。
基于数据库存储(最常见,OAuth2.0/ 自建系统)
假设你有一个 access_tokens 表,结构类似:
CREATE TABLE access_tokens (
id INT PRIMARY KEY,
token_hash VARCHAR(64) UNIQUE, -- 存储哈希,而非明文
user_id INT,
expires_at DATETIME, -- 过期时间
last_used_at DATETIME, -- 最后使用时间
is_revoked BOOLEAN DEFAULT FALSE
);
使用 SQL + 定时任务(Crontab / 计划任务)
#!/bin/bash
# 每天凌晨3点执行清理
# 定义清理策略:
# - 删除所有已过期的令牌
# - 删除超过30天未使用且已过期7天以上的令牌(双保险)
# - 更新 is_revoked = TRUE 而不是直接删除(软删除,保留审计)
mysql -u root -pYourPassword -e "
UPDATE access_tokens
SET is_revoked = TRUE
WHERE
(expires_at < NOW())
OR (last_used_at < DATE_SUB(NOW(), INTERVAL 30 DAY));
"
# 可选:物理删除已标记为 revoked 超过 90 天的记录
# mysql -u root -pYourPassword -e "DELETE FROM access_tokens WHERE is_revoked = TRUE AND updated_at < DATE_SUB(NOW(), INTERVAL 90 DAY);"
echo "$(date): Token cleanup completed"
定时执行:
# crontab -e 0 3 * * * /path/to/cleanup_tokens.sh
Python 脚本(更灵活,支持复杂日志和通知)
import datetime
import mysql.connector
import logging
logging.basicConfig(filename='token_cleanup.log', level=logging.INFO)
def cleanup_old_tokens():
conn = mysql.connector.connect(user='root', password='pass', host='127.0.0.1', database='your_db')
cursor = conn.cursor()
now = datetime.datetime.now()
thirty_days_ago = now - datetime.timedelta(days=30)
# 1. 已过期
query_expired = """
UPDATE access_tokens SET is_revoked = TRUE
WHERE expires_at < %s AND is_revoked = FALSE
"""
cursor.execute(query_expired, (now,))
expired_count = cursor.rowcount
# 2. 过期且超过30天未活跃(即使未过期也清理)
query_inactive = """
UPDATE access_tokens SET is_revoked = TRUE
WHERE (last_used_at < %s OR last_used_at IS NULL)
AND is_revoked = FALSE
"""
cursor.execute(query_inactive, (thirty_days_ago,))
inactive_count = cursor.rowcount
conn.commit()
cursor.close()
conn.close()
logging.info(f"Cleaned {expired_count} expired tokens and {inactive_count} inactive tokens.")
print(f"Cleaned {expired_count} expired, {inactive_count} inactive.")
if __name__ == "__main__":
cleanup_old_tokens()
优势:可以加入邮件/钉钉通知,记录每条被清理的令牌对应的 user_id 以便审计。
清理 Redis 缓存中的令牌(常见于微服务架构)
如果你的令牌存储在 Redis 中(例如作为 Session 或 JWT 黑名单),通常依赖于 TTL(存活时间),但 Redis 本身不记录“最后使用时间”,因此需要额外策略。
依赖 TTL 自动过期(推荐)
JWT 令牌本身包含过期时间,即使 Redis 未清理,只要解析令牌时校验 exp 即可。
# 无需脚本,Redis 会自动删除过期的 key redis-cli config set key-limit 100000
清除不活跃的 Session(针对手动管理的 Redis Session)
如果你在 Redis 中存储了 user:session:{token} 并手动更新 last_access,可以扫描并清理:
import redis
import time
r = redis.Redis(host='localhost', port=6379, db=0)
def cleanup_inactive_sessions(inactive_seconds=2592000): # 30天
now = time.time()
cursor = '0'
deleted_count = 0
while cursor != 0:
cursor, keys = r.scan(cursor=cursor, match='user:session:*', count=1000)
for key in keys:
session_data = r.hgetall(key)
if session_data:
last_access = float(session_data.get(b'last_access', 0))
if now - last_access > inactive_seconds:
r.delete(key)
deleted_count += 1
print(f"Deleted {deleted_count} inactive sessions.")
注意:对于大型集群,扫描所有 key 可能影响性能,可以考虑使用 Redis 6+的 Redis TimeSeries 或 额外维护一个活跃令牌的 Set。
JWT 令牌(无状态,需依赖黑名单或撤销机制)
JWT 通常无法被服务端直接“删除”,因为它们是自包含的,清理的是撤销列表(Revocation List)或黑名单缓存。
- 黑名单通常存储在 Redis 中,使用 令牌的 jti (JWT ID) 作为 key。
- 清理脚本:删除黑名单中已过期的记录(因为 JWT 本身会过期,黑名单不再需要)。
# 清理 JWT 黑名单中已过期的条目
# 假设你的黑名单 key 是 "blacklist:jti:{jti}", 并且设置了与 JWT 相同的 TTL
# 不需要清理,Redis 会自动删除。
真正的“清理”是指: 定期删除数据库中的撤销记录(如果记录在数据库)。
DELETE FROM revoked_tokens WHERE expired_at < NOW();
文件系统令牌(API Key 文件)
如果你的令牌以 API Key 文件形式存在于服务器(不推荐,但存在老旧系统)。
#!/bin/bash # 清理 /var/tokens/ 目录下超过 90 天未修改的令牌文件 find /var/tokens/ -type f -mtime +90 -delete # 同时清理 0 字节的空文件 find /var/tokens/ -type f -empty -delete echo "$(date): Cleaned old token files."
最佳实践建议
- 不可逆存储:数据库或日志中永远不要存储明文令牌,只存 SHA256 哈希。
- 软删除优先:先标记
is_revoked,保留 30-90 天再物理删除,以便审计和问题追溯。 - 分批清理:对于大型表(百万级),使用
LIMIT 1000循环删除,避免锁表。-- 分批删除示例 DELETE FROM access_tokens WHERE is_revoked = TRUE AND id > 0 ORDER BY id LIMIT 1000;
- 监控与告警:记录每次清理的数量,如果某次清理数量异常多(例如突然清理了 10 万个),触发告警。
- 考虑短生命周期令牌:如果业务允许,将令牌过期时间设为 15 分钟(Access Token),配合 Refresh Token(7 天),这样可以大幅减少清理压力。
总结脚本框架
| 存储方式 | 清理命令/脚本 | 频率 |
|---|---|---|
| MySQL/PostgreSQL | UPDATE ... WHERE expires_at < NOW() OR last_used_at < ... + Crontab |
每天一次 |
| Redis | 依赖 TTL | 自动 |
| JWT 黑名单 | 依赖 TTL + 定期清理已过期的 revoked_tokens 表 |
每天一次 |
| 文件系统 | find + -mtime |
每周一次 |
你可以根据你的实际存储方案,选择上述最匹配的脚本模板并修改表名、字段和清理阈值即可。