• shiro三连斩之第一斩


    通过JavaSE,创建不同的 realm ,由简单到复杂一步步的深入的理解shiro完成认证与授权内在联系

    推荐从下向上一步步的测试,每一个方法都有详细的注释,说明  从哪里来-->到哪里去,理清其中的前因后果

    简单点的一下用户, 角色与权限之间的关系

    一个用户可以拥有某种角色或者身份,身份或者角色代表一种或多种权限

    一个用户可以拥有一种或多种权限,

    我理解为 角色或身份 做为用户的一个属性 来封装权限。用户最好不要直接拥有权限,而是作为某种角色拥有角色封装的权限

    package test;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.mgt.DefaultSecurityManager;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.realm.SimpleAccountRealm;
    import org.apache.shiro.realm.jdbc.JdbcRealm;
    import org.apache.shiro.realm.text.IniRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.ByteSource;
    import org.junit.Before;
    import org.junit.Test;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public class TestShiro {
    
    
        /**
         * 自定义一个域与业务层交互获取数据,真实数据的来源于业务层方法的调用,在域内定义 认证或者授权的方法
         */
        class CustomRealm extends AuthorizingRealm{
    
            /**
             * @param principals  与认证方法中返回的info或者本域有关
             * @return
             */
            @Override
            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
                //从指定的域中获取subject提交过来的token,在认证方法中 参数1与本域进行关联,在这里取出,强转原参数1的类型
                //与这个本域关联的主题可能不止一个,.fromRealm()返回的集合,进行迭代后,获取。
                User user=(User)principals.fromRealm(this.getClass().getName()).iterator().next();
                String roleName = roleService.findRoleNameByUserName(user.getUsername);
                Set<String> perms = new HashSet<>();//用于存储 权限
                perms.add("权限1");//权限也是从数据库中查询出来的,这里直接手写
                perms.add("权限2");//权限是一种规范,目的用于判断的时候进行区别
                perms.add("权限3");//规范:user:list    user:delete  词能达意,对用户的查看权限,对用户的删除权限。
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//把角色和权限封装为内部属性的pojo类
                info.addRole(roleName);
                info.setStringPermissions(perms);
                return info;
            }
            /**
             * @param token  controller层,根据当前用户 生成的 token 用来在这里与真实数据进行认证
             * @return
             * @throws AuthenticationException
             */
            @Override
            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
                String login_userName = (String) token.getPrincipal();//返回controller层 subject.login()参数token中的username属性
                char[] password = (char[]) token.getCredentials();//这个token就是前面传的token ,在token内部用 string存uusername char[]存password
                String login_pwd = new String(password);//把 char数组类型转为字符串。
    
                //通过前台传送的token的username,调用自定义的业务成接口在数据库中查找,以username为用户名的用户信息
                User db_user=userService.fingUserByName(login_userName);
    
                //把数据库中查出来的信息,放入到info中以后,shiro会把数据库中的信息,与登录时生成的AuthenticationToken中的token进行比较,并把参数1与域进行关联。这个参数1可以在授权方法中获取
                //参数1:数据库中查询的真实数据,也属于域中的数据(一般为认证时需要用的数据) 参数2:密码,用于验证登录时token中的凭据 参数3:域的名称
                SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(db_user, db_user.getPassword(), this.getClass().getName());
                info.setCredentialsSalt(ByteSource.Util.bytes("盐值"));//每个用户的盐值都不一样,一般为数据库中查出来的,  info根据额外的数据对 token中的密码进行处理后再进行匹配
                return info;
            }
        }
        //自定义一个域,自定义认证的方式,授权的方式,
        private CustomRealm customRealm = new CustomRealm();
        /*该方法中,注册的自定义域中的方法需要与业务层进行配合,在数据库中进行查找,这里测试时失败的重点,从下往上,对不同的域一个个分析,理解shiro认证,授权的原理 核心*/
        @Test
        public void testCustomRealm() {
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            securityManager.setRealm(customRealm);
            SecurityUtils.setSecurityManager(securityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("喜欢与改变", "admin");
            HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();//密码比较器,在CustomRealm重写的认证方法中的SimpleAuthenticationInfo这个对// 会对token中的密码根据密码比较器运算后数据库中的密码比较。数据库中的密码,也是这个比较器处理过
            matcher.setHashAlgorithmName("md5");//密码进行散列的方式
            matcher.setHashIterations(2);//散列次数,还有一个盐值,在CustomRealm中指定
            try {
                subject.login(token);
                System.out.println("成功");
            } catch (UnknownAccountException e) {
                System.out.println("用户名错误");
            } catch (AuthenticationException e) {
                System.out.println("认证失败");
            }
        }
    
    
    
    
    
        //把数据库作为数据域,需要给Realm一个连接的数据库的数据源
        private JdbcRealm jdbcRealm = new JdbcRealm();
        private DruidDataSource dataSource = new DruidDataSource();//连接数据库的数据源
        @Before
        public void druid() {//配置数据源数据并注入数据源
            dataSource.setUrl("jdbc:mysqljdbc://localhost/3306/数据库");
            dataSource.setUsername("root");
            dataSource.setUsername("");
            dataSource.setDriverClassName("com.mysql.jdbc.Driver");
            jdbcRealm.setDataSource(dataSource);
        }
        /**
         * 认证 数据库中的数据作为域。真实的开发中,一般把真实的用户数据放在数据库,通过当前用户的 username 到数据库中查询 对应的 password
         * 把登录的密码与查询的密码进行比较(是否存在密码散列,对登录密码进一步操作后再比较)。登录成功,把用户信息,放在shiro的Session。
         */
        @Test
        public  void testJdbcRealm() {
            String sql = "select password from t_user where username= ?";
            jdbcRealm.setAuthenticationQuery(sql);//jdbc有默认的查询表格与对应的字段,可以手动设置,符合数据库结构的SQL语句
    
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            securityManager.setRealm(jdbcRealm);
            SecurityUtils.setSecurityManager(securityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("喜欢与改变","admin");
            try {
                subject.login(token);//shiro根据token与指定的SQL语句。和查出来的数据进行操作
                System.out.println("认证成功");
    
            } catch (UnknownAccountException e) {
                System.out.println("用户名错误");
            } catch (AuthenticationException e) {
                System.out.println("认证失败");
    
            }
        }
    
    
    
    
        //把ini配置文件作为一个域,ini配置文件相当于数据库存储真实数据,IniRealm根据ini文件的中的信息,对当前用户进行认证与授权
        private IniRealm iniRealm = new IniRealm("classpath:authenticator.ini");
        /**
         * 认证。把ini配置文件作为域
         */
        @Test
        public  void  testIniRealm() {
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            securityManager.setRealm(iniRealm);
            SecurityUtils.setSecurityManager(securityManager);
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token = new UsernamePasswordToken("喜欢与改变", "admin");
            try {
                subject.login(token);//认证在IniRealm中(认证方法的中的接受参数的对象中)进行
                System.out.println("认证成功");
                System.out.println("是否有角色xxx"+subject.hasRole("superadmin"));
                System.out.println("是否有角色yyy"+subject.hasRole("normal"));
                subject.checkPermission("巨无霸");
                System.out.println("当前用户拥有  巨无霸  权限");
            } catch (UnknownAccountException e) {
                System.out.println("用户名错误");
            } catch (AuthenticationException e) {
                System.out.println("认证错误");
            }
        }
    
    
    
        //在controller层的写法一般是固定的,创建一个shiro核心控制器,给控制器注入域对象,并把控制器放到工具类中便于使用,从工具类中的获取一个subject对象
        //根据前台传来的岩心信息,生成一个token(令牌),放到subject.login()中,subject会保存这个令牌,进入核心控制器,核心控制器根据自己管理的(例,注入进来的域)对象,执行login()相关的操作
        //认证成功,可以继续执行下一步操作,我们一般对subject进行操作,查看这个用户的角色,拥有的权限等。这些信息,login()是核心控制器,已经完成了认证与授权
    
        //一个简单域(shiro自带的),用户传来数据 ,跟域中获取的数进行验证。(域相当于数据源)
        private SimpleAccountRealm realm = new SimpleAccountRealm();
        /**
         * 给域添加用户数据
         */
        @Before
        public void before() {
            realm.addAccount("喜欢与改变","admin");
        }
        /**
         * Authenticator 认证器
         * 认证  简单的域
         */
        @Test
        public void  textAuthenticator() {
            //1、创建shiro环境:shiro是核心控制器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            //2、给核心控制器 注入域对象---->在认证过程中,存储安全数据(真实的用户信息,就是从这里获取数据和登录的进行验证)
            securityManager.setRealm(realm);
            SecurityUtils.setSecurityManager(securityManager);//把核心管理器放到工具类中
            //3、主体认证,会将认证委托给SecurityManager
            Subject subject = SecurityUtils.getSubject();
    
            //根据当前用户的信息生成一个token(令牌对象),用于让shiro进行验证
            UsernamePasswordToken token = new UsernamePasswordToken("喜欢与改变", "dmin");
            //执行认证
            try {
                subject.login(token);//shiro拿着token在realm中进行认证(realm可以自定义,再注册进来使用),认证失败抛出异常,根据抛出的异常可知道错误的范围
            } catch (UnknownAccountException e) {//用户名不匹配时抛出异常(还有别的异常类型,用是再查)
                System.out.println("当前信息不存在");
            } catch (AuthenticationException e) {//认证失败时抛出异常
                //e.printStackTrace();
                System.out.println("认证失败");
            }
        }
    }

     ini配置文件说明

    [users]
    zs=123,admin
    lisi=123,normal
    [roles]
    admin=user:insert,user:delete,user:list
    normal=user:list
    //下面是注释。
        users    账户=密码,角色(多个角色用逗号隔开)
        roles    角色=角色拥有的权限(多个权限用逗号隔开)
        角色拥有的权限,user:insert是一种规范,词能达意,用户的添加权限
  • 相关阅读:
    C#的委托事件总结
    iOS的录屏功能
    Unity通过NTP获取网络时间
    Unity的弱联网Json数据传输
    Unity场景和代码合并以及UnityYAMLMerge的使用
    Unity学习--捕鱼达人笔记
    Yomob广告在cocos2dx安卓平台的Demo
    谷歌广告Admob在cocos2dx上通过回调实现底部Banner
    JZ2440 裸机驱动 第13章 LCD控制器(2)
    JZ2440 裸机驱动 第13章 LCD控制器(1)
  • 原文地址:https://www.cnblogs.com/xiaoeyu/p/10459186.html
Copyright © 2020-2023  润新知