美文网首页SpringFrameworkSpring BootJava学习笔记
谈谈Shiro的原理及在SSM和SpringBoot两种环境下的

谈谈Shiro的原理及在SSM和SpringBoot两种环境下的

作者: Gopher2020 | 来源:发表于2017-12-16 15:53 被阅读139次

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。

相关文章

网友评论

    本文标题:谈谈Shiro的原理及在SSM和SpringBoot两种环境下的

    本文链接:https://www.haomeiwen.com/subject/jvwswxtx.html