• shiro入门


    一、简介

    Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。这不就是我们想要的嘛,而且 Shiro 的 API 也是非常简单;其基本功能点如下图所示:

    Authentication :身份认证/登录,验证用户是不是拥有相应的身份;

    Authorization: :授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;Session Manager: :会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;

    Cryptography :加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

    Web Support :Web 支持,可以非常容易的集成到 Web 环境;

    Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

    Concurrency :shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

    Testing :提供测试支持;

    Run As :允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

    Remember Me: :记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

    记住一点,Shiro  不会去维护用户、维护权限;这 些需要我们 自己去 设计/ 提供 ; 然后通过相应的 接口注入给 给 Shiro 。

    首先,我们从外部来看 Shiro 吧,即从应用程序角度的来观察如何使用 Shiro 完成工作。如下图:

    可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;

    其每个 API 的含义:

    Subject: :主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;

    SecurityManager :安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;

    Realm: :域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

    也就是说对于我们而言,最简单的一个 Shiro 应用:

    1、 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;

    2、 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

    从以上也可以看出, ,Shiro 不 不 提供 维护用户/ 权限,过 而是通过 Realm  让开 发人 员 自己 注入。

    接下来我们来从 Shiro 内部来看下 Shiro 的架构,如下图所示:

    Subject :主体,可以看到主体可以是任何可以与应用交互的“用户”;

    SecurityManager : : 相 当 于 SpringMVC 中 的 DispatcherServlet 或 者 Struts2 中 的FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。

    Authenticator :认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了。

    Authrizer: :授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

    Realm :可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;

    SessionManager :如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);

    SessionDAO: :DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO中可以使用 Cache 进行缓存,以提高性能;

    CacheManager: :缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。

    Cryptography :密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。

    二、入门程序

    1、首先准备一些用户身份/凭据(shiro.ini)

    [users]
    zhang=123
    wang=123
    @Test
        public void testHelloworld() {
            //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
            Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    
    
            //2、得到SecurityManager实例 并绑定给SecurityUtils
            SecurityManager securityManager = factory.getInstance();
            SecurityUtils.setSecurityManager(securityManager);
            //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
            Subject subject = SecurityUtils.getSubject();
         //这里的账号和用户名 将来有用户输入 UsernamePasswordToken token
    = new UsernamePasswordToken("zhang", "123"); try { //4、登录,即身份验证 subject.login(token); } catch (AuthenticationException e) { //5、身份验证失败 } //断言用户已经登录 Assert.assertEquals(true, subject.isAuthenticated()); //6、退出 subject.logout(); }

    自定义realm

    public class CustomRealm extends AuthorizingRealm {
    
        // 设置realm的名称
        @Override
        public void setName(String name) {
            super.setName("customRealm");
        }
    
        // 用于认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
    
            // token是用户输入的
            // 第一步从token中取出身份信息
            String userCode = (String) token.getPrincipal();
    
            // 第二步:根据用户输入的userCode从数据库查询
            // ....
        
    
            // 如果查询不到返回null
            //数据库中用户账号是zhangsansan
            /*if(!userCode.equals("zhangsansan")){//
                return null;
            }*/
            
            
            // 模拟从数据库查询到密码
            String password = "111112";
    
            // 如果查询到返回认证信息AuthenticationInfo
    
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                    userCode, password, this.getName());
    
            return simpleAuthenticationInfo;
        }
    
        // 用于授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
            // TODO Auto-generated method stub
            return null;
        }
    
    }

    需要在shiro-realm.ini配置realm注入到securityManager

    [main]
    customRealm=cn.itcast.shiro.realm.CustomRealm
    securityManager.realms=$customRealm

    测试:

    // 自定义realm
        @Test
        public void testCustomRealm() {
    
            // 创建securityManager工厂,通过ini配置文件创建securityManager工厂
            Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                    "classpath:shiro-realm.ini");
    
            // 创建SecurityManager
            SecurityManager securityManager = factory.getInstance();
    
            // 将securityManager设置当前的运行环境中
            SecurityUtils.setSecurityManager(securityManager);
    
            // 从SecurityUtils里边创建一个subject
            Subject subject = SecurityUtils.getSubject();
    
            // 在认证提交前准备token(令牌)
            // 这里的账号和密码 将来是由用户输入进去
            UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
                    "111111");
    
            try {
                // 执行认证提交
                subject.login(token);
            } catch (AuthenticationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            // 是否认证通过
            boolean isAuthenticated = subject.isAuthenticated();
    
            System.out.println("是否认证通过:" + isAuthenticated);
    
        }

    散列算法

    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.apache.shiro.crypto.hash.SimpleHash;
    public class MD5Test {
        
        public static void main(String[] args) {
            
            //原始 密码 
            String source = "111111";
            //
            String salt = "qwerty";
            //散列次数
            int hashIterations = 2;
            //上边散列1次:f3694f162729b7d0254c6e40260bf15c
            //上边散列2次:36f2dfa24d0a9fa97276abbe13e596fc
            
            
            //构造方法中:
            //第一个参数:明文,原始密码 
            //第二个参数:盐,通过使用随机数
            //第三个参数:散列的次数,比如散列两次,相当 于md5(md5(''))
            Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
            
            String password_md5 =  md5Hash.toString();
            System.out.println(password_md5);
            //第一个参数:散列算法 
            SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
            System.out.println(simpleHash.toString());
        }
    
    }

    自定义realm支持散列算法

    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    public class CustomRealmMd5 extends AuthorizingRealm {
    
        // 设置realm的名称
        @Override
        public void setName(String name) {
            super.setName("customRealmMd5");
        }
    
        // 用于认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
    
            // token是用户输入的
            // 第一步从token中取出身份信息
            String userCode = (String) token.getPrincipal();
    
            // 第二步:根据用户输入的userCode从数据库查询
            // ....
    
            // 如果查询不到返回null
            // 数据库中用户账号是zhangsansan
            /*
             * if(!userCode.equals("zhangsansan")){// return null; }
             */
    
            // 模拟从数据库查询到密码,散列值
            String password = "f3694f162729b7d0254c6e40260bf15c";
            // 从数据库获取salt
            String salt = "qwerty";
            //上边散列值和盐对应的明文:111111
    
            // 如果查询到返回认证信息AuthenticationInfo
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                    userCode, password, ByteSource.Util.bytes(salt), this.getName());
    
            return simpleAuthenticationInfo;
        }
    
        // 用于授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
            // TODO Auto-generated method stub
            return null;
        }
    
    }

    在realm中配置凭证匹配器  shiro-realm-md5.ini

    [main]
    #定义凭证匹配器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    #散列算法
    credentialsMatcher.hashAlgorithmName=md5
    #散列次数
    credentialsMatcher.hashIterations=1
    
    #如果查询到返回认证信息AuthenticationInfomation
    customRealm=cn.itcast.shiro.realm.CustomRealmMd5
    customRealm.credentialsMatcher=$credentialsMatcher
    securityManager.realms=$customRealm

    测试:

    // 自定义realm实现散列值匹配
        @Test
        public void testCustomRealmMd5() {
    
            // 创建securityManager工厂,通过ini配置文件创建securityManager工厂
            Factory<SecurityManager> factory = new IniSecurityManagerFactory(
                    "classpath:shiro-realm-md5.ini");
    
            // 创建SecurityManager
            SecurityManager securityManager = factory.getInstance();
    
            // 将securityManager设置当前的运行环境中
            SecurityUtils.setSecurityManager(securityManager);
    
            // 从SecurityUtils里边创建一个subject
            Subject subject = SecurityUtils.getSubject();
    
            // 在认证提交前准备token(令牌)
            // 这里的账号和密码 将来是由用户输入进去
            UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
                    "111111");
    
            try {
                // 执行认证提交
                subject.login(token);
            } catch (AuthenticationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
    
            // 是否认证通过
            boolean isAuthenticated = subject.isAuthenticated();
    
            System.out.println("是否认证通过:" + isAuthenticated);
    
        }
  • 相关阅读:
    417 Pacific Atlantic Water Flow 太平洋大西洋水流
    416 Partition Equal Subset Sum 分割相同子集和
    415 Add Strings 字符串相加
    414 Third Maximum Number 第三大的数
    413 Arithmetic Slices 等差数列划分
    412 Fizz Buzz
    410 Split Array Largest Sum 分割数组的最大值
    409 Longest Palindrome 最长回文串
    day22 collection 模块 (顺便对比queue也学习了一下队列)
    day21 计算器作业
  • 原文地址:https://www.cnblogs.com/zailushang1996/p/8625026.html
Copyright © 2020-2023  润新知