简单易懂的Shiro实战项目,无需任何配置

Shiro的读音以及Shiro是什么

       

        shiro,一开始碰到它的人,基本都会被它的读音难到,这货到底是读“山弱”,“息肉”,还是读“西弱”呢。然后落叶君依靠职业思维,在百度框里输入shiro,进入了apache shiro官网,看到以下这段话后令我茅塞顿开。原来它的名字来自日语城堡,意思就是安全,坚固,防御性很强的东西。日语中的城堡就是“城(しろ)????”,这个单词的罗马音就是“shi ro”,而罗马音中的r一般读出来像我们拼音中的l“了”,所以要用汉字来说读音的话,那就应该读“西漏”了。官网介绍如下:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

什么是Apache Shiro?

        Apache Shiro(发音“shee-roh”,是日语中的城堡的意思)是一款功能强大并且简单易用的java安全框架,它可以用来进行身份验证权限认证数据加密session管理,并且可以用来保护任何应用,从命令行应用,移动应用,到大型web应用和企业级应用。

        Shiro提供了应用安全API,来实现以下这些功能(我喜欢叫这些东西为应用安全的四大基石)

        身份验证——证明用户的身份,经常被称为用户登录。

        权限认证——访问控制(是否有权限访问此资源)

        数据加密——保护数据不被人偷看

        session管理——每一个用户拥有一个时效性状态。

 

注:session就是会话的意思,这个词就我个人而言,比较恶心。随着见的多了,慢慢理解了它的意思(现在其实还有些迷糊),就是某东西与某东西进行交互的一段历程。每个会话都有其唯一标识sessionID,用来标记这段交互历程是属于某东西与某东西的,既然是一段历程,那么肯定是在一段时间内发生的,这就是时效性,假设state为1,session有效,过了一段时间,state为0,session过期,那么某东西与某东西的session就会被销毁,这段历程也就结束了,如果某东西与某东西还想要交互,那么就需要再创建一个属于它们的session(怎么感觉越说越污啊,画面感强烈,我现在终于知道为什么要加sessionID了,即使再怎么搞多人运动,也不能违反自然定律啊)。

简单易懂的Shiro实战项目,无需任何配置

 

shiro实战练手项目

       

        今天我们从实际应用出发倒过来讲解shiro能帮我们干什么以及怎样更好的使用它。和往常一样,我做了一个shiro的练手项目,在该项目中使用自制哈希表实现了一个冒牌redis(跟随项目启动,所以已经不能叫remote dictionary server 了,不过不影响我们研究shiro),redis操作工具类模仿redisTemplate,目前只能操作String数据类型,不过对于研究shiro够用了。

        数据库采用得hashMap,数据结构采用关系模型,符合第三范式,所以也不用去配置数据库了,可以说又是0配置的练手项目。启动即可使用。数据库表如下所示,可以先略过这堆表,在讲解接口的时候对照着看会比较好。

--------------------------------------------------------------------

        用户表:   

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        用户角色关联表:

简单易懂的Shiro实战项目,无需任何配置简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        角色表:

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        角色权限表:

 

简单易懂的Shiro实战项目,无需任何配置--------------------------------------------------------------------

        权限表:

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

可以先看完整篇文章,对shiro有个大致的了解,然后再下载项目亲自体验一下。项目地址如下:

https://github.com/liantengda/MySecurity.git

 

shiro实战解析(回溯)

 

 

没有权限保护的接口

        我们先来看一个没有被安全框架保护的接口。在该项目启动后,会初始化一组角色列表集合如下图所示:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        现在我们直接调用删除角色方法,对系统内部角色进行删除,请求头中什么也不带,操作结果如下图所示:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        我们再来查看角色列表,发现我们的超级管理员角色没了,这还得了,软件行业许多命名都是约定俗成的,随便一个人写个请求,调用下删除接口,我们系统内部的数据就被删除了,这简直是*了狗了

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

shiro保护下的接口

        接下来我们给删除角色的接口加上一个注解,如下所示,我们看到有个注解@RequiresRoles("超级管理员"),@ApiOperation为Swagger的注解,对该接口进行了说明,只有超级管理员才有删除角色的本领。所以此处不必太在意@Api开头的注解

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        接下来我们重启项目,像刚才那样进行一波删除操作。重启项目之后,数据库又会初始化回原来的样子。操作结果如下:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        其中4401为自定义的全局异常处理状态码,实际对应401状态码,表示当前用户无权限操作。异常信息message大概在说,当前主体(subject)是匿名的,它没有任何验证身份的主属性,而权限认证操作首先需要核对一下身份。一个主体实例,通过apache shiro 的 subject.login()方法成功登陆以后会自动获得这些用来验证身份的主要属性,或者通过开启remember me功能来记住以前登录的属性。当前异常也可能是因为之前登陆的用户,执行了登出操作,导致它再次变为匿名状态。因为以上这些原因,权限认证未通过。

        所以按照以上异常信息的指引,我们首先需要执行一次subject.login,来验证下自己的身份。我们接下来去看代码里面哪里用到了这个login方法。从如下代码可以看出,要想subject.login成功,这个token是必须的,目前这个案例中的token取自Http 请求头里面的 Authorization参数(请求接口后进入JwtFilter中,是因为配置了拦截器,拦截器在本篇中暂不分析,项目中已经配好了,用就可以)。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        然后我们再来看看这个token到底干了什么。以下为自定义的认证token类,我们可以看到,自定义的token类实现了apache shiro的验证身份接口直接将token作为验证身份的主要属性,即principal

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        此处就有个疑问了,也是我了解shiro以来一直存在的一个疑问。目前来看,这个token确实是我们验证当前用户的一个主要属性。但是怎么就验证成功的呢,为啥从header里面取出来的这个Authorization,就被系统识别呢,他们怎么知道这个Authorization属性就是当前这个用户的呢。那肯定是系统提前存了一份呗。存在哪里了呢,把他给存到了redis中了。

        当初到这个地方,我也迷惑了,不是shiro帮助我们验证吗,怎么是根据你存的东西去判断,而且你存就存呗,和shiro有啥关系,shiro又不知道redis是啥东西,又不知道你存的东西在哪里。

        此处就可以引出来shiro的又一个重要角色,Realm。shiro其实本身自带一个存储当前主体主要属性的数据库,被称为LDAP数据库,而Realm类似于我们的ORM框架,可以与LDAP数据库进行交互,来存取数据。我们来看以下示例,

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        从上图我们可以看到这样一个过程,提前存储主体主要属性,再到创建一个匿名主体subject,再到subject.login与我们登录的用户名和密码绑定,成为一个带有身份的subject,再到进行身份验证和权限认证。总结为如下:

        往某个地方提前存一份东西,需要的时候再把存好的东西拿出来看看,我今天带来的东西和我以前存储的东西是否一样,一样就是良民,可以进入城堡,不一样就暴打一顿扔出去。

 

        以上是shiro自带的realm,shiro提供了一个类AuthorizingRealm,可以让我们自定义自己的realm,接下来我们再翻回实战项目接着讲,之所以我可以把数据存在redis中同时还能使用shiro,就是因为这个realm是我自己定义的。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        继承AuthorizingRealm,需要实现两个方法,如下图所示,其中一个方法用来身份验证,一个方法用来权限认证。验证逻辑也都是自定义的,基本脱离了shiro。只是最后需要给shiro返回两个对象,SimpleAuthenticationInfo和SimepleAuthorizationInfo(扣押良民证不妥改为“重新核验”良民证)。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        接下来,举个例子对以上的解释进行一个总结操作,我规定,超级管理员和管理员都可以查询角色列表。这下就不能用刚才的@RequiresRoles注解了,如下图所示,我本来想的是这样的,因为注解提示可以输入角色数组,所以我以为数组元素之间是或者的关系,有一个就行。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        实验结果却是这样的,我们先执行登录接口,将我们的主属性token提前存储到redis中。登录接口代码如下:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

 

登录结果如下(此时登录接口还没有返回用户详细信息的功能):

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        然后我们带上这个token主属性,放入请求头中,在身份验证的时候,取出提前存储在redis中的token,与请求头中的token进行比对,身份验证成功后,调用我们用户业务逻辑,查询当前用户的角色和拥有的权限,封装到SimpleAuthorizationInfo中,如下图所示,超级管理员角色和权限已经封装完毕,准备提交给shiro进行管理。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

 

        接着去请求查询角色列表接口,如下图所示,提示我们,subject不具有管理员这种角色,这下清楚了,原来这么写的意思是,当前用户必须同时为超级管理员和管理员,才可以访问此接口。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        所以此时我们再看看之前创建的permissionRole关联列表,如下图所示

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        因此我们将接口注解改为下图中的注解@RequiresPermissions,就是一开始看到权限管理的时候很懵逼的那种写法,字符串:字符串

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        然后我们重启项目,初始化数据库,进行登录操作,为了更加直观,我对返回数据进行了修改,返回当前用户更加全面的信息。如下图所示,我们登录一个管理员账号

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        然后请求查询角色列表,请求成功。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        此时我们再查看一下管理员经过认证后,封装了哪些权限。如下图所示:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

 

 

趣味案例

        看完以上讲解,我们大概就知道shiro如何使用了,接下来来个案例,多方位使用一下刚学完的shiro。在铁齿铜牙纪晓岚电视剧中,只有纪府的丫鬟才敢吆喝老爷,训斥老爷,尤其是杜小月在场的时候,府中人员的地位就会发生令人匪夷所思的变化:

                    小月>杏儿>鹦鹉>纪晓岚

        接下来有个需求,创建一个杏儿训斥纪晓岚的接口,杏儿需要满足一定条件才可以训斥纪晓岚,那就是同时拥有纪府丫鬟杜小月丫鬟两种角色,我们先来看一下我们数据库中的杏儿拥有什么角色。那么我们需要先查看一下用户列表得到杏儿的id,这些操作需要我们拥有user:list权限(swagger中的注解notes写错了,此处使用的是细粒度权限permission,不是通过角色判断)。如下图所示:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        我们需要先登录管理员用户,获得验证身份主要属性token。如下:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        由下图,我们可以得到杏儿的id为8。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        然后查询杏儿拥有的角色,需要拥有role:list权限,刚好我们在初始化数据库的时候给管理员指定了这个权限。可以看到杏儿目前拥有纪府丫鬟这个角色。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        然后我们登录杏儿,请求训斥纪晓岚的接口,结果如下,可见要想训斥纪晓岚光是纪府丫鬟还不够,还必须有杜小月丫鬟这个角色才可以。

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        实际接口要求权限如下所示:

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

        那么我们需要给杏儿加个角色,这需要超级管理员角色才可以做到,所以我们登录超级管理员账号,获取超级管理员token,然后请求添加角色接口,如下图所示

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

然后对杏儿这个主体进行角色关联,赋予其杜小月丫鬟这个角色,如下图所示

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

然后我们使用杏儿的token,再次请求训斥纪晓岚接口,如下图所示,接口请求成功

--------------------------------------------------------------------

简单易懂的Shiro实战项目,无需任何配置

--------------------------------------------------------------------

浪花深处玉沉钩,圆缺几时休

素娥应信别离愁,天上载悠悠