放在前面的话
本项目为合肥工业大学的部分系统提供权限管理支持,不敢保证商业的高并发,高可用,但至少在线上生产环境下保证了系统的安全。如果你对我的框架有好的建议可以联系我做改进,我非常的欢迎。
git地址
登陆和认证
下面聊一聊登陆和鉴权的一些概念和思考,以及他们在多用户环境下的一些思考。我会边聊这些问题的思考,边给出设计的理念和使用的方法,让你像看故事一样,学会注解的使用。
登陆
登陆是认证的前提,基于这个理念,Guardian的所有认证注解,包括角色认证和权限认证都是基于登陆前提的,换句话说:只要你在上面标注了认证相关的注解,不管是不是和这个用户类型有关的,我们都会要求用户登陆。
其实这个逻辑很简单,假如我有一个接口是为A类用户准备的,但我需要A有x权限。如果不限制必须登陆,那么我完全可以退出登陆,然后访问这个接口,这样的权限认证肯定是没有丝毫逻辑的。
在多用户类型的环境中,我们还需要考虑用户类型。我们假设有的接口可以让所有人都访问,比如获取应用的名称,这类接口和用户类型无关。然后,我们还有一个接口,是查看个人信息,只要是用户都可以查看个人信息。这个时候我们就要求所有人都登陆,使用注解吗?不,除非你保证不同类型用户返回的数据是一样的。那你说,我可以利用Guardian上下文判断呀。其实,这种的情况,分开写两个方法更好。
这就是多用户环境下的悖论吗?我们可以分开写接口。是的,但也不完全是这样。因此,我们的@RequiredLogin注解在你不加任何限制的情况下,默认就是,不管你是什么类型的用户,你都需要登陆。
那什么叫不完全是呢?好,我给你举一个例子,我们有一个文件上传的接口,这个接口只容许部分类型的用户上传文件。那你可能会想,这难道不是认证部分吗?我的回答是,可以是,也可以不是。嗯?什么意思?假如你这两种类型的用户,有一种用户完全不能上传文件,另一个用户都可以上传文件。那你就用@RequiredLogin,否则,你某种类型的用户,只有具有一部分权限的用户才能上传文件,那就使用认证的注释。而且,认证前比登陆的逻辑,你完全可以兼容前面的那种情况!
登陆验证
@RequiredLogin既可以标注在类上,又可以标注在方法上。依据就近原则,方法标注优先。
你可以这样记,在类上标注了,相当于给每个方法都标注了,如果同时标注了,方法覆盖类上的注解。
-
单种用户
-
需要登陆
- @RequireLogin
-
不需要登陆
- 不标注
-
-
多种用户(A,B,C....Z)
-
需要登陆
-
全部需要登陆
- @RequireLogin
-
只容许A,B使用
- @RequireLogin(onlyFor={"A","B"})
-
不容许B,C使用
- @RequireLogin(forbidden={"B","C"})
-
-
不需要登陆
- 不标注
-
认证
这下看了,登陆是完全没有问题了,那认证呢?在多用户的环境的下,你继续要考虑角色,又需要考虑权限,还得兼顾不同用类型的不同角色,最最关键的是,有的时候角色和权限是混合在一起校验的,那这么复杂的逻辑,你能用注解表示清楚吗?那判断逻辑不得套个7,8层吗?
别急,我给你慢慢分析。你仔细想想,我们对接口的访问是不是可以看成对资源的一种操作?
你说的没有错,这不就是restful风格的接口吗?对,我们先来设想一个业务场景:学生保修宿舍的问题,维修部门指定谁去报销,修好了之后,让学生确认好评。现在你看,学生和维修部门对于我们来说,都是用户,而且他们要共同操作一个“资源”:维修。而且最关键的是,这两种用户之间的数据表结构肯定是不一样的,所以,我们必须要使用多用户了。而且为了业务更能提现框架的强大和智能,我们加一种用户类型,维修工人,维修工人负责接任务,修东西。
现在,我们有多种用户类型,多种角色和多种权限了。我们来思考几个问题。
首先是权限,我们来想,如果光有权限的话,是不是也能完成所有的认证逻辑。报修:上报,报修:指定人员,报修:维修,报修:好评,报修:删除......,这么想想好像确实也够了。而且,这些权限还是统一的,就是说,学生的维修:删除和部门报修:删除是一个权限,可能你会说,学生只能删除自己的,但是部门可以删除。。。打住打住,这个明显是业务逻辑的部分了,一个权限框架,怎么去知道怎么判断是否为用户自己的资源。
哦哦,原来权限是统一的呀,就像restful一样。那只弄权限不就行了嘛。当然可以了,但是这样系统的需要储存的权限也太多了,每个学生明明权限都一样,却要在数据里面储存一大堆。对呀,那怎么办,要是有一个东西能把这些权限结合起来就好了。等一下,那不就是角色吗?我们给学生指定学生的角色,学生就自动有这些所有的权限。
所以,我们在这里确定了两个基本逻辑,算上登陆那个,已经有三个了。权限对所有用户类型来说都是一样的。角色是权限的集合。等一下,等一下,就比如user:delete删除这个权限你怎么写呢?假如我有系统用户和学生用户。你自己其实已经说出来了,system-user:delete,student-user:delete,甚至你写三级分类都是可以的,逻辑上是一样的。再等一下,假如我想要有*-user:delete的权限呢,就比如我是超级管理员。小问题,我给你加上正则匹配。那假如我想写user:add和user:update,要两个一起写吗,不,你可以写成user:add,update,而且你这是用户的权限吧,谁会这么写接口呀。哦哦,有道理嘿,那。。。别那了,我直接把这个扩展的逻辑给你,先执行你的逻辑,你要是通过了,我就不验证了,你要是没通过,可以指定是否执行我的判断逻辑。那好啊,这太棒了!
所以我们注解认证的基本逻辑就来了,先验证权限,如果权限通过了,那就不验证了角色了。啊?为什么?那你想想,为什么我要指定权限?为什么?不是有角色就够了吗,自己知道这些角色能什么,指定就可以了。所以说,权限肯定是角色的一种特殊,比如我有一个角色是学生,但是我是这个系统的开发者唉,让我看看用户登陆的情况不过分吧,那你怎么办?难道给我管理员的角色吗?万一我那天把全校的学生都删了呢?那。。给你一个角色专门查看登陆情况吧,等一下,这不就是权限吗?哦哦,原来权限是在角色的基础上富裕用户的一些特殊行为呀,这下我理解了。等一下,还没结束呢,我们可以是唯一一个天然支持多用户类型的框架,那多用户类型呢?你可别忘了,权限这个核心概念就告诉你,权限和用户类型是无关的!但是角色和用户类型是有关的,学生的管理员和教师的管理员,那权限差了远了。
我懂了,是不是我们的注解是这样设计的,一个权限注解指定了权限,然后里面套多角色注解,如果权限通过,就不管角色了,如果权限没有通过,那我就一个一个验证用户。对不对!我只能说,逻辑上对,但是你这样写起来太复杂了,我直接把这个两个注解分开了,一个是单个的@RequiredPermission然后是@RequiredRole和@RequiredRoles你标注一个,如果你针对默认用户,那就标注前一个,否则,你就标注后一个,后一个就是把前一个嵌套在一起了。而且这样做还有一个好处,就是方便扩展。比如你定义了一个接口需要管理员角色,但是你怎么也没想到,有个人想要查看登陆用户这个权限,难道我把这个注解套到里面吗?不。我直接加一个@RequiredPermission就行。然后,我发现这小子利用这个权限干坏事,我不能把这个权限给任何人了,我直接把那删掉。
哦,那这样太完美了。等一下,我还想到一个问题,权限禁封,假如我这个角色的某个用户被禁封某种权限呢,那也可以,我们直接加到权限里。等一下,怎么加?很简单,加个符号!user:add,我们把这个加到数据库里面,然后拿出来匹配,甚至我们的注解都不用改,我们加个判断逻辑就可以了。
秒啊,那,你@RequiredPermission打算弄成怎么样呢,弄一个还是弄多个。当然是弄多个了,又不是什么难事,说不定有的接口需要能操作两种权限的人呢,比如那种操作多个数据表的接口。
怎么样,激动人心吗?
权限验证
-
已登陆->获取userType
-
查看是否标注了@RequiredPermission注解
-
标注了
-
查看自己的权限是否能通过这些验证
- 通过了->访问接口
- 没有通过->继续往下看角色注解
- 被禁止了->禁止访问
-
-
没有标注
- 看角色注解
-
-
标注了任意与角色相关的注解
-
按照:方法优先于类,复合优先于单注解的顺序,寻找和userType匹配的注解
-
找到了
-
依据逻辑判断类型开始匹配
- or:符合其中一个就可以访问
- and:全部符合才可以访问
- not:禁止只含有指定角色的访问(如果我们的角色比not指定的多,我就可以通过
- 接口,自定义实现逻辑
-
-
未找到
- 禁止访问接口
-
-
-
未标注任何与角色相关的注解
- 可以访问接口
-
-
未登录
-
标注任意与权限相关的注解
- 禁止访问接口
-
未标注任何于权限相关的注解
- 可以访问接口
-