在传统的RBAC机制中,接口权限是基于令牌中的角色来获取的,与令牌的生命周期或状态无关。事实上,token本身是无状态的。然而,系统开发通常需要一个状态来进行维护,因此在大多数项目中,通常会为无状态的token增加一个有状态的身份标识。
通常,获取数据的access_token的有效时间很短,例如十分钟以内,然后通过动态拉取业务数据来获取;负责刷新的refresh_token则可以设置为2个小时或更长时间。这种方式虽然能在一定程度上缓解问题,但无法彻底解决,因为refresh_token同样存在于前端,仍然可能生成新的令牌。
那么,如何真正限制某个令牌的使用呢?以下我将从架构角度,列举五种方式。
这是最大程度的拦截,即使是当前有效的token,在整个系统层面也将全部失效。
应用场景:如数据安全问题、商品超卖等异常情况。
操作方式:采取暴力拦截的方式,直接修改JWT的私钥,或更改发行人、订阅人等核心配置,使得即便是当前有效的令牌也无法请求加权接口,从而要求用户重新登录并获取符合新规则的token。
如果系统级别的问题确实出现,拦截token可能不是最优解,因为此时密码也可能已被泄露。为了进一步保护安全,可以将用户信息标记为异常,强制用户修改密码,并通过手机号或邮箱等个人专属信息来验证用户身份。因此,在用户系统的架构设计之初,必须确保收集到足够的基础用户信息。
当然,如果不想采取如此激进的措施,也可以在权限拦截器中,通过过滤令牌claim中的创建时间,令所有创建时间早于当前时间的token失效,并直接返回拒绝请求。
"Audience": {
"Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs", //不要太短,16位+
"SecretFile": "C:\\my-file\\blog.core.audience.secret.txt", //安全。内容就是Secret
"Issuer": "Blog.Core", //这个值一定要在自己的项目里修改!!
"Audience": "wr" //这个值一定要在自己的项目里修改!!
},
在系统级别失效的基础上,可以缩小失效范围,仅针对特定范围的token进行失效处理。
应用场景:例如,某段时间内,特定区域(如华北区或华南区)的token需要失效,或者针对特定角色、某个自定义属性的用户进行失效处理。这种场景在区域性限流、局部问题排查或特定业务需求中较为常见。
实现方式:这种设计思路与系统级别的失效类似,都是通过令牌的创建时间或其他特定条件来筛选和拦截token。通过对符合条件的token进行批量失效,可以实现更精细化的控制,确保在出现问题时仅影响特定范围内的用户,不会波及全局。
在实际应用中,可以通过在权限拦截器中,配置区域、角色或自定义属性的过滤条件,来动态拦截特定批次的token。此外,还可以结合定时任务或触发器机制,定期或实时地更新失效策略,以应对快速变化的业务需求。
进一步缩小失效范围,可以针对特定的某个用户,使其名下的所有token失效。
应用场景:这种策略类似于黑名单功能,适用于当用户出现异常日志、修改密码或其他重要信息时,系统需要强制其重新登录,并确保其所有token失效,以防止冒名顶替的风险。这对于账号安全尤为重要,尤其是在用户敏感信息变更的情况下。
实现方式:由于一个用户可能会生成多个token,因此需要在权限拦截器中,根据token中的用户ID,查询用户表中的状态信息(如禁用、修改密码、强制退出等)。一旦发现用户状态异常,则直接对该用户名下的所有token进行拦截,强制失效。
为了提高效率,避免每次请求都进行数据库查询,可以使用缓存机制。常见的做法是将用户的状态信息缓存至Redis中,当用户状态发生变更时,更新缓存。结合Redis的hash缓存结构,可以实现快速的用户状态查询,从而减少数据库的压力,并提升系统的响应速度。
扩展建议:在大型系统中,结合消息队列(如Kafka或RabbitMQ)与缓存机制,可以在用户状态变更时实时广播通知相关服务,确保所有节点都能同步更新用户状态,进一步提升系统的响应效率与一致性。
//应该要先校验用户的信息 再校验菜单权限相关的
// JWT模式下校验当前用户状态
// IDS4也可以校验,可以通过服务或者接口形式
SysUserInfo user = new();
if (!Permissions.IsUseIds4)
{
//校验用户
user = await _userServices.QueryById(_user.ID, true);
if (user == null)
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户不存在或已被删除").MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
if (user.IsDeleted)
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登录!").MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
if (!user.Enable)
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被禁用!禁止登录!").MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
}
继续缩小范围,针对具体某个令牌进行失效操作,这是最小颗粒度的限制措施。
应用场景:这种方式适用于需要针对某个特定token进行处理的场景,如在线用户管理、异常活动检测或强制下线等。例如,当发现某个token存在异常活动时,可以立即失效该token,确保账户安全。
实现方式:通常通过Redis将token进行hash或MD5加密,作为key来记录一个有状态的身份,从而实现对单个token的精确控制。当需要失效某个token时,只需删除或修改对应的Redis记录,即可使该token失效,迫使用户重新登录。
扩展建议:在某些高级应用场景中,还可以结合行为分析和机器学习模型,对token的使用情况进行实时监控。一旦检测到异常行为,可以自动触发失效机制,确保系统安全。这种方式适用于需要高安全性的场景,如金融系统或企业级应用。
一种常见的场景是令牌的互斥,即新生成的token会使旧的失效。
应用场景:这种方式通常应用于同一账号只能单设备登录的场景,或在在线答题、考试等需要强制单设备登录的场景中,每次新登录都会将旧的token挤下线,确保同一时间内只有一个token有效。
实现方式:用户登录时,在用户表中记录当前时间A,并生成新的token,该token中的claim会包含生成时间B。每次验证token时,系统会比较数据库中的时间A与token中的时间B。如果A > B,则表明该token已经失效,系统会拒绝操作,并提示用户重新登录。
扩展建议:为了进一步提升系统的安全性和用户体验,可以在实现互斥失效的基础上,增加通知机制。当用户在另一台设备上登录时,可以通过短信、邮件或APP推送通知用户,提醒其当前账号在另一设备上登录,是否需要确认。如果用户确认是本人操作,则无需修改密码;否则,可以立即采取措施如锁定账号或强制修改密码。
这种互斥原则失效的方式不仅提高了系统的安全性,也能够有效防止账号共享或盗用的情况,适用于需要严格控制用户登录设备的场景。