zoukankan      html  css  js  c++  java
  • Shiro 认证流程详解

    1. 认证

      身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

    2. Shiro 中认证的关键对象

    • Subject 主体:访问系统的用户,主体可以是用户、程序等等,进行认证的都称为主体;
    • Principal 身份信息:是主体(Subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)
    • Credential 凭证信息:是只有主体自己知道的安全信息,如密码、证书等。

    3. 认证流程

             

    4. 认证开发

    4.1  模拟数据库中已存储用户信息【创建 shiro.ini 文件】

    [users]
    wangwu=123
    zhangsan=123456
    lisi=789

    4.2 开发认证代码

    public class TestAuthenticator {
    
        public static void main(String[] args) {
            // 1. 创建安全管理器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            // 2. 给安全管理器设置 realm 【Realm 需要获取用户认证的数据源信息】
            securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
            // 3. SecurityUtils 设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
            // 4. 获取关键对象 Subject 主体
            Subject subject = SecurityUtils.getSubject();
            // 5. 创建令牌
            UsernamePasswordToken token = new UsernamePasswordToken("wangwu", "123");
            // 用户认证
            try {
                System.out.println("认证状态: " + subject.isAuthenticated());
                subject.login(token);
                System.out.println("认证状态: " + subject.isAuthenticated());
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("认证失败: 用户名不存在~");
            } catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                System.out.println("认证失败: 密码错误~");
            }
        }
    }
    • DisabledAccountException(帐号被禁用)

    • LockedAccountException(帐号被锁定)

    • ExcessiveAttemptsException(登录失败次数过多)

    • ExpiredCredentialsException(凭证过期)

    4.3 自定义 Realm

      上边的程序使用的是 Shiro 自带的 IniRealm,IniRealm 从 shiro.ini 配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义 Realm。

    1)Shiro 自定义 Realm 

             

    2)根据认证源码使用的是 SimpleAccountRealm 【只保留认证和授权这两部分代码】

    public class SimpleAccountRealm extends AuthorizingRealm {
            //.......省略
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            SimpleAccount account = getUser(upToken.getUsername());
    
            if (account != null) {
    
                if (account.isLocked()) {
                    throw new LockedAccountException("Account [" + account + "] is locked.");
                }
                if (account.isCredentialsExpired()) {
                    String msg = "The credentials for account [" + account + "] are expired";
                    throw new ExpiredCredentialsException(msg);
                }
    
            }
    
            return account;
        }
    
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String username = getUsername(principals);
            USERS_LOCK.readLock().lock();
            try {
                return this.users.get(username);
            } finally {
                USERS_LOCK.readLock().unlock();
            }
        }
    }

    3)自定义 Realm 

     由于 Realm 相当于 Datasource 数据源,SecurityManager 进行安全认证需要通过 Realm 获取用户身份数据,所以可以自定义 Realm 获取用户身份的信息;

    public class CustomerRealm extends AuthorizingRealm {
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            System.out.println("====================");
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String principal = (String) token.getPrincipal();
            System.out.println("principal = " + principal);
            //根据身份信息使用jdbc mybatis查询相关数据库
            if ("xiaochen".equalsIgnoreCase(principal)) {
                // 参数1:返回数据库中的用户名  参数2:返回数据库中的正确密码  参数3:提供当前的 realm 名字,this.getName()
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo("wangwu", "123", this.getName());
                return simpleAuthenticationInfo;
            }
            return null;
        }
    }

    4)使用自定义 Realm 进行认证

    public class TestCustomerRealm {
    
        public static void main(String[] args) {
            // 1.创建安全管理器
            DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
            // 2.给安全管理器设置 realm 【数据库中获取用户名和密码】
            defaultSecurityManager.setRealm(new CustomerRealm());
    
            // 3.SecurityUtils 设置安全管理器
            SecurityUtils.setSecurityManager(defaultSecurityManager);
    
            // 4.获取用户登录的主体
            Subject subject = SecurityUtils.getSubject();
    
            AuthenticationToken token = new UsernamePasswordToken("wangwu", "123");
            try {
                subject.login(token);
            } catch (UnknownAccountException e) {
                System.out.println("用户名不正确");
                e.printStackTrace();
            } catch (IncorrectCredentialsException e) {
                System.out.println("用户密码不正确");
                e.printStackTrace();
            }
        }
    
    }

    5) 使用 MD5 和 Salt 

    实际应用是将盐和散列后的值存在数据库中,自动realm从数据库取出盐和加密后的值由shiro完成密码校验。
      A 自定义 MD5 + salt 的 Realm 
    public class CustomerMD5Realm extends AuthorizingRealm {
    
        /**
         * 一个主题可以有多个身份,参数是集合 principals;但是只有一个主身份;
         * @param principals
         * @return
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            // 获取身份信息
            String principal = (String) principals.getPrimaryPrincipal();
            System.out.println("身份信息 = " + principal);
            
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String principal = (String) token.getPrincipal();
            System.out.println("principal = " + principal);
            //根据身份信息使用jdbc mybatis查询相关数据库
            if ("wangwu".equalsIgnoreCase(principal)) {
                // 参数1:返回数据库中的用户名  参数2:返回数据库中的正确密码  参数3:注册时的随机盐  参数4:提供当前的 realm 名字,this.getName()
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,
                        "e4f9bf3e0c58f045e62c23c533fcf633",
                        ByteSource.Util.bytes("X0*7ps"),
                        this.getName());
                return simpleAuthenticationInfo;
            }
            return null;
        }
    }    
      B 使用 MD5 + Salt 进行认证处理
    public class TestCustomerMD5Authenticator {
    
        public static void main(String[] args) {
            // 1. 创建安全管理器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
    
            // 2. 给安全管理器设置 realm 【Realm 需要获取用户认证的数据源信息】
            CustomerMD5Realm customerMD5Realm = new CustomerMD5Realm();
            // 设置 realm 使用的 hash 凭证匹配器
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            credentialsMatcher.setHashAlgorithmName("md5"); // 设置使用的 Hash 算法
            credentialsMatcher.setHashIterations(1024);     // 需要散列多少次
            customerMD5Realm.setCredentialsMatcher(credentialsMatcher);
            securityManager.setRealm(customerMD5Realm);
    
            // 3. SecurityUtils 设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
    
            // 4. 获取关键对象 Subject 主体
            Subject subject = SecurityUtils.getSubject();
    
            // 5. 创建令牌
            UsernamePasswordToken token = new UsernamePasswordToken("wangwu", "123");
    
            // 用户认证
            try {
                System.out.println("认证状态: " + subject.isAuthenticated());
                subject.login(token);
                System.out.println("认证状态: " + subject.isAuthenticated());
            } catch (UnknownAccountException e) {
                e.printStackTrace();
                System.out.println("认证失败: 用户名不存在~");
            } catch (IncorrectCredentialsException e) {
                e.printStackTrace();
                System.out.println("认证失败: 密码错误~");
            }
        }
    }

      注意:深入了解 Shiro 的认证流程,可以 debug 运行程序,查看源代码进行学习深究!

  • 相关阅读:
    经典面试题目C语言
    论C语言中二级指针和二维数组之间的区别
    判断单链表中是否有环找到环的入口节点
    论decltype和auto的区别
    在ubuntu下安装opencv
    C中有关引用和指针的异同
    (四)关于读文件的结束的判别方法(EOF和feof)以及区别
    (三)论sizeof与strlen之间的区别
    (二)C语言文本流和二进制流的区别
    (一)C的编译,printf,规范化
  • 原文地址:https://www.cnblogs.com/blogtech/p/14822112.html
Copyright © 2011-2022 走看看