在APP后端开发中,登录鉴权是不可或缺的基础模块。本文分享一套轻量化登录鉴权方案,涵盖登录、鉴权、凭证刷新、退出登录及强制下线等核心功能。
整体设计思路
本方案采用双Token(AccessToken + RefreshToken)机制,生成高熵随机字符串并存储于MySQL数据库中:
AccessToken:用于业务接口鉴权,有效期较短,用于访问各类业务接口。
RefreshToken:用于刷新凭证,有效期较长。当AccessToken过期时,用于换取全新凭证,避免用户频繁重新输入账号密码。
客户端请求时需在请求头携带Token,后端通过查询数据库校验凭证的有效性与过期状态。在退出登录、修改密码或被管理员强制下线时,直接删除数据库记录即可使登录态立即失效。
请求头规范如下:
Authorization: Bearer {token字符串}
数据库表设计
新建 user_token 表以保存所有客户端登录凭证,并合理设置索引以保障查询效率:
CREATE TABLE `user_token` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` int NOT NULL COMMENT '关联用户ID',
`access_token` varchar(128) NOT NULL COMMENT '访问令牌',
`refresh_token` varchar(128) NOT NULL COMMENT '刷新令牌',
`access_expire_at` int NOT NULL COMMENT 'access过期时间戳(秒)',
`refresh_expire_at` int NOT NULL COMMENT 'refresh过期时间戳(秒)',
`create_time` int NOT NULL COMMENT '记录创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_access_token` (`access_token`),
UNIQUE KEY `idx_refresh_token` (`refresh_token`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='APP登录token表';
索引说明:
access_token与refresh_token设置唯一索引,用于通过Token快速检索记录。user_id设置普通索引,便于按用户批量删除记录,实现全设备强制踢下线。时间字段统一采用时间戳格式,便于后端代码处理。
注意:MySQL不会自动过期删除数据,需配置定时任务清理过期脏数据。
核心代码实现
生成Token工具函数
使用安全随机函数生成64位Token,防猜测性强,满足APP安全需求。
/**
* 生成64位随机token字符串
*/
function gen_token(): string
{
return bin2hex(random_bytes(32));
}
登录逻辑实现
账号密码校验通过后,生成一对Token并写入数据库。支持多设备同时登录与单点登录(新设备挤掉旧设备)两种模式。
// 校验账号密码逻辑省略,得到登录用户ID
$userId = 1001;
// 单点登录开启:登录前清除该用户所有旧token记录
Db::name('user_token')->where('user_id', $userId)->delete();
$accessToken = gen_token();
$refreshToken = gen_token();
$now = time();
// access有效期2小时;refresh有效期7天
$accessExpire = $now + 7200;
$refreshExpire = $now + 7 * 86400;
Db::name('user_token')->insert([
'user_id' => $userId,
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'access_expire_at' => $accessExpire,
'refresh_expire_at' => $refreshExpire,
'create_time' => $now
]);
// 返回给APP客户端
return json([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'access_expires_in' => 7200
]);
鉴权中间件
需登录的接口统一通过中间件拦截,校验Token有效性与过期时间。支持开启滑动续期,即用户持续操作时自动延长AccessToken有效期,避免中途掉线。
header('Authorization', '');
if (!str_starts_with($authHeader, 'Bearer ')) {
return json(['code' => 401, 'msg' => '请先登录'], 401);
}
$accessToken = substr($authHeader, 7);
$now = time();
// 根据token查询,同时校验是否过期
$tokenRow = Db::name('user_token')
->where('access_token', $accessToken)
->where('access_expire_at', '>', $now)
->find();
if (!$tokenRow) {
return json(['code' => 401, 'msg' => '登录凭证失效,请重新登录'], 401);
}
// 滑动续期,每次访问重置过期时间
Db::name('user_token')
->where('access_token', $accessToken)
->update(['access_expire_at' => time() + 7200]);
// 将用户ID注入request对象,业务接口直接读取使用
$request->userId = $tokenRow['user_id'];
刷新Token接口
当AccessToken过期时,APP调用刷新接口传入RefreshToken换取新凭证。此环节采用令牌轮换机制,删除旧记录并生成全新的一对Token,提升安全性。接口地址示例:POST /api/token/refresh
$refreshToken = $request->post('refresh_token');
$now = time();
$row = Db::name('user_token')
->where('refresh_token', $refreshToken)
->where('refresh_expire_at', '>', $now)
->find();
if (!$row) {
return json(['code' => 401, 'msg' => '刷新凭证失效,请重新登录'], 401);
}
// 删除旧的登录记录
Db::name('user_token')->where('id', $row['id'])->delete();
// 生成全新token
$newAccess = gen_token();
$newRefresh = gen_token();
Db::name('user_token')->insert([
'user_id' => $row['user_id'],
'access_token' => $newAccess,
'refresh_token' => $newRefresh,
'access_expire_at' => time() + 7200,
'refresh_expire_at' => time() + 7 * 86400,
'create_time' => time()
]);
return json([
'access_token' => $newAccess,
'refresh_token' => $newRefresh,
'access_expires_in' => 7200
]);
退出与强制下线
直接删除数据表对应记录,登录态即刻失效。
// 用户主动退出,销毁当前设备token
Db::name('user_token')->where('access_token', $accessToken)->delete();
// 管理员操作:让该用户所有设备全部下线
Db::name('user_token')->where('user_id', $userId)->delete();
定时清理过期数据
由于MySQL无Key过期自动回收机制,需配置Crontab定时任务(如每小时执行一次),定期清理已失效的Token记录,防止数据表持续膨胀。
$now = time();
Db::name('user_token')
->where('access_expire_at', '<', $now)
->where('refresh_expire_at', '<', $now)
->delete();
注意事项与优化建议
当前方案每次鉴权均执行SQL查询,适用于中小流量APP。若QPS极高,建议增加Redis等缓存层进行优化。
线上环境务必启用HTTPS,防止Token被抓包泄露。
登录接口需做好IP限流策略,防止账号遭受暴力破解。
#token

