谈谈Shiro的原理及在SSM和SpringBoot两种环境下的使用姿势(上篇)


title: 谈谈Shiro的原理及在SSM和SpringBoot两种环境下的使用姿势(上篇)
categories:

  • Spring
    tags:
  • Shiro使用

本篇主要是记录关于Shiro进行认证和授权的大致原理,然后是单独在Shiro中实现认证和授权的方式。最后主要说明在传统SSM的工程中使用Shiro和在SpringBoot的工程中使用Shiro进行整合。关于认证和授权,我这里采用的是规范的RBAC权限模型,数据库的建表语句已经托管github的工程中。

在进行Shiro具体认证和授权的流程介绍之前,首先说一下Shiro中几个比较重要的概念(其中的接口或者类)。

  1. Subject:含义为主体。Subject作为用户端(使用Shiro进行授权的一端)的抽象,在Shiro中是通过一个接口来体现的。在使用Shiro的时候,我们也就是通过调用Subject的认证和授权的方法来实现具体的认证和授权的。

  2. SecurityManager:含义为安全管理器。SecurityManager是整个Shiro的核心所在,它负责对所有的Subject进行安全管理,我们在通过Subject进行授权和认证的时候,Subject其实是通过SecurityManager来实现具体业务逻辑的。SecurityManager在Shiro中是通过一个接口来体现的,而且它继承了Authenticator,Authorizer,SessionManager。如下图:

    Aaron Swartz

    这样SecurityManager的认证会交给Authenticator定义的业务逻辑完成,授权会交给Authorizer定义的业务逻辑完成,会话管理会交给SessionManager来完成。

  3. Authenticator:含义为认证器,主要是完成对用户身份的认证。Authenticator在Shiro是一个接口,在Shiro中提供了一个ModularRealmAuthenticator的实现类用于完成认证。ModularRealmAuthenticator已经可以完成大多数的认证需求,如果我们有新的业务,那么我们通过自定义认证器来完成特殊的业务。

  4. Authorizer:含义为授权器,主要是完成对用户操作的授权。Authorizer在Shiro中是一个接口,相比Authenticator,Shiro提供了更多的授权器的实现类,其中也包括类似的ModularRealmAuthorizer。这些默认的实现类可以完成大多数需求,如果我们有新的业务,那么我们通过自定义授权器来完成特殊的业务。

  5. realm:含义为领域。Realm在Shiro中也是一个接口,SecurityManager进行认证和授权的时候,它所需要的数据信息都是从Realm中获取的。Realm有多种实现类,代表了Realm可以从多种数据源中读取已经配置的数据信息用于认证和授权。Realm在Shiro中是一个相当关键的部分,因为我们的授权器和认证器最终都是通过Realm来实现各自的业务的。

  6. SessionDAO:含义为Session会话.在Shiro中也是作为一个接口来体现的。sessiondao可以实现将session数据持久化。

  7. CacheManager:含义为缓存管理器。用户的认证和授权的信息可以缓存到CacheManager中,从而提升数据的访问性能。

  8. Cryptography:含义为密码管理。shiro提供了Cryptography来作为我们信息的加密和界面的工具。

ok,在说完了Shiro中几个比较关键的概念之后,我们开始看一下在Shiro中是如何进行的认证和授权的。

注:所有的sql都包含在了工程中

代码地址: https://github.com/fuyunwang/ShiroDemo.git
认证:

下面这张图说明了Shiro中进行认证的大致流程。

Aaron Swartz

可以看到,Shiro最终其实通过Realm来完成最终的认证。我们上面也已经提到,Realm其实作为一种数据源的地位存在,其包含多个实现类代表着从不同的数据源中进行数据信息的获取。我这里通过使用其中一个实现类IniRealm来实现最简单的认证流程。(具体代码在v0.1tag下。)

    Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:inirealm-shiro.ini");
    
    SecurityManager securityManager = factory.getInstance();
    
    SecurityUtils.setSecurityManager(securityManager);
    
    Subject subject = SecurityUtils.getSubject();
    
    UsernamePasswordToken token=new UsernamePasswordToken("beautifulsoup", "password");
    
    try{
        subject.login(token);
    }catch(AuthenticationException e){
        e.printStackTrace();
    }

介绍完使用inirealm来完成从ini配置文件中获取数据之后,我们做一个自定义Realm,来完成从数据库这一数据源中获取数据。
自定义Realm一般采用继承自AuthorizingRealm的方式,然后重写其中的认证和授权的方法,核心代码如下,完整代码在github。

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        String username=(String) token.getPrincipal();
        ShiroDemoMapper mapper=getShiroMapper();
        User user = mapper.findByUsername(username);
        if(null!=user){
            SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(username, user.getPassword(),TAG);
            return authenticationInfo;
        }
        return null;
    }

然后进行配置:

    [main]
    #进行自定义realm的配置
    customRealm=com.beautifulsoup.shiro.demo.realm.ShiroDemoRealm
    securityManager.realms=$customRealm

这样我们自定义的realm就会生效了,我们可以实现从数据库中获取数据,然后校验我们主体subject的信息,从而实现判断是否认证成功的功能。
这里我们认证的时候,在数据库中采用明文存取的密码,这当然是不合理的,所以通常情况下,我们会采用加盐(salt)的方式,使用散列算法如MD5对我们原有的密码进行加密然后存入数据库中。(改进之后的代码在v0.2标签下)

    首先修改配置文件,定义散列算法和散列次数等:
    [main]
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    credentialsMatcher.hashAlgorithmName=md5
    credentialsMatcher.hashIterations=3
    #进行自定义realm的配置
    customRealm=com.beautifulsoup.shiro.demo.realm.ShiroDemoRealm
    customRealm.credentialsMatcher=$credentialsMatcher
    securityManager.realms=$customRealm
    然后修改realm
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        String username=(String) token.getPrincipal();
        ShiroDemoMapper mapper=getShiroMapper();
        User user = mapper.findByUsername(username);
        if(null!=user){
            SimpleAuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(username, user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()),TAG);
            return authenticationInfo;
        }
        return null;
    }

好,说完认证我们接下来说授权:

授权:

同样,下面这张图说明了在Shiro中进行授权的大致流程。

Aaron Swartz

可以看到,SecurityManager最终交给Realm进行授权,实际上Realm是会返回一个ModularRealmAuthorizer类,该类得到所有的系统配置的权限然后调用PermissionResolver进行了权限的匹配。

接上所讲,我们还是使用ini的配置文件来配置shiro实现授权,主要是配置文件更加方便我们的管理。

这里我们的权限信息定义在配置文件中,毕竟我们的权限信息大多数是固定的,而且对于权限不多的情况下,这种方式更简单。对于授权的操作主要包括针对角色的授权和针对资源的授权两种方式,由于基于角色的权限控制不如基于资源的权限控制更加灵活,所以我们采用基于资源的权限控制为例来介绍。

配置文件进行配置的方式如下(代码在v0.3标签):

        [users]
        #用户beautifulsoup具有role1和role3的角色
        beautifulsoup=password,role1,role3
        [roles]
        #权限role1具有对01用户订单的创建权限和对02订单资源的修改权限和对所有订单的查询操作。
        role1=item:create:01,item:update:02,item:query
        role2=item:*:01,item:update:02
        role3=item:create:02,item:delete:02

基本权限的验证:

        @Test
        public void testIniAuthorization(){
            Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:permission-shiro.ini");
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
            Subject subject = SecurityUtils.getSubject();
            //首先认证,认证通过之后才能授权
            UsernamePasswordToken token=new UsernamePasswordToken("beautifulsoup", "password");
            try{
                subject.login(token);
            }catch(AuthenticationException e){
                e.printStackTrace();
            }
            System.out.println("用户的认证状态:"+subject.isAuthenticated());
            boolean isPermitted=subject.isPermittedAll("item:create:01","item:query");
            subject.checkPermissions("item:create:01","item:query");
            System.out.println(isPermitted);
        }

接下来使用自定义realm来实现用户的授权。
在认证中已经提到继承自AuthorizingRealm,其提供了两个方法,我们现在用第二个方法来实现授权的逻辑。(代码在v0.4标签)

        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
            //得到认证成功之后凭证的身份信息
            String username=(String) principals.getPrimaryPrincipal();
            //查询数据库得到所有的权限列表
            List<String> permissionList=new ArrayList<String>();
            UserCustomMapper mapper=getUserCustomMapper();
            UserCustom userCustom = mapper.findUserCustomByUsername(username);
            Set<RoleCustom> roles=userCustom.getRoleSet();
            for(RoleCustom role:roles){
                Set<Permission> permissionSet = role.getPermissionSet();
                for (Permission permission:permissionSet) {
                    permissionList.add(permission.getPname());
                }
            }
            SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
            authorizationInfo.addStringPermissions(permissionList);
            return authorizationInfo;
        }       
        同样我们也需要配置:
        [main]
        credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
        credentialsMatcher.hashAlgorithmName=md5
        credentialsMatcher.hashIterations=3
        #进行自定义realm的配置
        customRealm=com.beautifulsoup.shiro.demo.realm.ShiroDemoRealm
        customRealm.credentialsMatcher=$credentialsMatcher
        securityManager.realms=$customRealm

OK,到现在为止上篇已经对Shiro所有认证和授权的基础知识做过了介绍,下篇开始对SSM和SpringBoot中的Shiro的使用进行整合。
代码地址: https://github.com/fuyunwang/ShiroDemo.git

如果对您有过帮助,感谢您的一个star。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容

  • 文章转载自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus阅读 3,333评论 0 3
  • 前言 Spring boot 是什么,网上的很多介绍,这里博客就不多介绍了。如果不明白Spring boot是什么...
    xuezhijian阅读 17,857评论 13 39
  • 一 shiro 是什么 shiro 是一个功能强大和易于使用的Java安全框架,为开发人员提供一个直观而全面的解决...
    司鑫阅读 58,078评论 17 98
  • 构建一个互联网应用,权限校验管理是很重要的安全措施,这其中主要包含: 认证 - 用户身份识别,即登录 授权 - 访...
    zhuke阅读 3,441评论 0 30
  • 一:基础概念 什么是权限管理 权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经...
    QGUOFENG阅读 540评论 0 0