太久没写了,今天补一篇,本篇无实际代码,主要是设计思路。

关于JWT:在我所开发的系统中用户Token都是有意义的,都会携带部分数据,不过多用于userId

个人的权限系统使用历程:

基于Security(不加表、角色放在Token中)

  • 注解验证(乱七八糟)
    使用Spring Security框架,将用户角色ROLE_USER写在account表中,UserDetailsService的实现方法中封装UserDetails返回,然后在接口上添加注解进行权限校验
    好处是简单,但是权限写在逻辑里面了。改权限就要发版本,小系统可以这样干
  • 请求地址前缀验证(分模块)
    顾名思义,按照角色划分接口地址,类似于微服务模块拆分,但是这个纯粹就是为了权限验证拆。而且说实话也不好升级。

RBAC模型

用户表、角色表、功能表、接口表、关联表
在这里插入图片描述
凑活看。不过后面的很多思路都是基于这个表结构的。

  • RBAC数据库
    实现AccessDecisionManager接口

    FilterInvocation filterInvocation = (FilterInvocation) object;
    Set<String> uris = smsAccountRoleMapper.uris((String) authentication.getPrincipal());
    if (uris.contains(filterInvocation.getRequestUrl())) {
        return;
    } else {
        log.info("当前请求路径:[{}]用户权限为:[{}]", filterInvocation.getRequestUrl(), uris);
        throw new AccessDeniedException("权限不足");
    }
    

    到这里就已经实现接口权限动态配置了,但是同时到此也全都是没营养的废话,下面开始更经典的。

  • 使用Redis+JWT
    Redis用于存储JWT、JWT用来存储用户标识
    实现AuthorizationServerTokenServicescreateAccessTokenrefreshAccessToken方法,不需要实现TokenStore,直接写在MyTokenService里就行、就是一堆Token生成存储逻辑、从JWT的实现类里面拷贝一部分,再拷贝一部分Redis的就组合成了。

    技巧:以下所有存储Redis有效期设置尽量比真实的有效期短1小时,防止Redis验证通过而Token解析失败

    前缀含义
    UNAME_TO_ACCESS用户标识>>>accessToken、主要用于用户封号冻结时删除Token及单设备登录限制
    ACCESS_TO_USERaccessToken >>>userId、鉴于Token为JWT生成,所以此处仅用于验证Token真实性
    ACCESS_TO_REFRESHaccessToken >>>refreshToken 、账号冻结时删除当前账号所有有关的Token信息
    REFRESH_TO_ACCESSrefreshToken >>> accessToken、刷新令牌仅可以使用一次、且用完之后需要删除旧的accessToken
    REFRESHrefreshToken信息、用于刷新令牌

    验证逻辑
    1、开放性接口直接过
    2、需要登录的由Redis负责校验是否为真实有效的Token
    3、需要进行权限校验的依靠数据库RBAC关系进行查询校验。

  • 微服务
    其实微服务和前面的一样,认证中心发放Token,网关验证鉴权、所有的逻辑基本一致

  • 前端按钮验证
    在这里要安利一个开源项目:若依,给了我很多启发。
    上文表结构接口表中有一个字段为前端验证key,具体使用方式为后端在用户登录之后返给前端一个Set集合,前端在组件中封装一个标签,类似v-if,参数为这个按钮的验证key,如果Set集合中有就显示,没有就不显示按钮。
    下图是若依的验证逻辑,考虑的非常周到,一个按钮可能对应多个接口:
    在这里插入图片描述
    建议:尽量和后端接口名保持一致,因为后端接口你不可能提供两个相同的(尽量使用PostMapping

  • 前端页面验证
    页面相较于按钮复杂之处在于后端的一个接口会在多个页面调用且一个页面会调用后端多个接口,所以后端提供路由配置就显得不是那么方便,所以我的思路是:前端知道当前页面会调用哪些接口,而且也知道哪些接口有权限验证,同时考虑到前端某些数据可能今天在这里展示,明天就换位置了,所以将页面级的路由交于前端自行校验,具体逻辑为:
    1、当前页面存在开放性接口,如查询,那么直接展示
    2、当前页面所有接口都需要权限,那么验证后端返回的Set集合中是否和当前页面的接口验证key存在交集,如果有展示,如果没有隐藏

DBAC模型

顾名思义:同一个接口,不同的人访问看到的数据是不一样的,这个不一样不是指看到的自己的数据不一样,而是同样一个页面,你只能看自己的,老师能看班级的,校长能看全校的。

数据权限一定要根据系统实际使用进行设计,虽然有某些通用的设计方案,但是如果直接拿过来使用而不加以设计那么一定会面临性能问题。

  • 统一权限
    某人对系统的操作等级是固定的,只在角色变更时变化,那么可以使用若依的那套实现方式,MyBatis拦截器的方式,然后判断用户权限等级拼接SQL后缀、不过若依那一套简单,SQL基本固定,因为他的部门角色是基础实现。
  • 拆分权限
    一个用户对于系统中的不同接口的访问权限是不同的,不单单是部门的问题(例如一个部门的部长犯了错误,公司让他回家反省,那么他还是部长,但是可能他再登录系统就只能看自己的数据了)
    思路:将前端的验证思路搬到后端,在MyBatis接口上添加注解,在拦截器中判断当前用户对于当前SQL的权限等级是什么,查询出不同的SQL后缀拼接到查询语句中。
    在这里插入图片描述
    知道用户、mapper接口、那么就能查看对应的SQL后缀是什么,进行动态拼接就可以了。

欢迎留言,有时间也搞个开源项目大家玩玩。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐