美文网首页
Shiro学习day-71:Shiro安全框架

Shiro学习day-71:Shiro安全框架

作者: 开源oo柒 | 来源:发表于2019-10-30 22:33 被阅读0次

    一、Shiro框架介绍

    1.什么是Shiro?

    Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和 会话管理等功能。

    • Shiro应用场景:

    对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。其不仅可 以用在JavaSE环境,也可以用在JavaEE环境。

    2.Shiro的架构图:

    • 从外部看shiro:


      shiro.png
    • 从Shiro内部看Shiro的框架:


      Shiro.png
    • Shiro中常见的英文名词:

    Subject:主体;
    Security:安全;
    Realm:领域,范围;
    Authenticator:认证器;
    Authentication:认证;
    Authorizer:授权器;
    Authorization:授权;
    Cryptography:密码;
    Credential:证书、凭证;
    Matcher:匹配器;
    Principal:身份。

    二、Shiro环境搭建

    1.Shiro中的配置文件:

    shiro.ini文件放在classpath下,shiro会自动查找。其中格式是key/value 键值对配置。INI配置文件一般适用于用户少且不需要在运行时动态创建的 情景下使用。 ini文件中主要配置有四大类:main,users,roles,urls。

    shiro.ini

    (1)[mian]:

    main主要配置shiro的一些对象,例如securityManager ,Realm, authenticator,authcStrategy 等等。

    image.png

    (2)[users]:

    [users]允许你配置一组静态的用户,包含用户名,密码,角色,一个用户 可以有多个角色,可以配置多个角色。

    image.png

    (3)[roles]:

    [roles]将角色和权限关联起来,格式为:角色名=权限字符串1,权限字符 串2…..。

    image.png

    (4)、[roles]

    [roles]将角色和权限关联起来,格式为:角色名=权限字符串1,权限字符 串2…..。

    image.png

    2.Shiro环境搭建实现简单的认证:

    认证:验证用户是否合法 在 shiro 中,用户需要提供principals (身份)和credentials(凭证) 给shiro,从而实现对用户身份的验证。

    • principals:

    身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。 例如:用户名/邮箱/手机号等。

    • .credentials

    凭证,即只有主体知道的安全值,如密码/数字证书等。 最常见的principals和credentials组合就是用户名/密码。

    • 实现简单的认证:
      (1)导入jar包:


      jar

    (2)shiro.ini:

    [users]
    root = 1234
    

    (3)测试:

    package com.zlw.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    
    //实现简单认证
    public class AuthenticationTest {
    
        @Test
        public void testAuthentication() {
            //1.构建SecurityManager工厂
            IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
            //2.通过securityManagerFactory工厂获取SecurityManager实例
            SecurityManager securityManager = securityManagerFactory.getInstance();
            //3.将securityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            //4.获取subject实例
            Subject subject = SecurityUtils.getSubject();
            //5.创建用户名和密码验证令牌Token
            UsernamePasswordToken token = new UsernamePasswordToken("root","1234");
            //6.进行身份验证
            subject.login(token);
            //7.判断是否验证通过
            System.out.println("验证是否通过:"+subject.isAuthenticated());
        }
    }
    
    结果

    3.Shiro内置的JDBCRealm:

    Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息。 大部分情况下需要从系统的数据库中读取用户信息,所以需要使用JDBCRealm或自定义Realm;:使用JDBCRealm提供数据源,从而实现认证 。
    在JDBCRealm中限定了表中的表名和字段。

    (1)创建users表,(字段为username,password):


    users

    (2)导入jar包:


    jar
    (3)配置shiro.ini:
    [main]
    #配置Realm
    jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
    
    #配置数据源
    dataSource = com.mchange.v2.c3p0.ComboPooledDataSource
    dataSource.driverClass = com.mysql.jdbc.Driver
    dataSource.jdbcUrl = jdbc:mysql:///shiro
    dataSource.user = root
    dataSource.password = root
    
    jdbcRealm.dataSource = $dataSource
    
    #将Realm注入给SecurityManager
    securityManager.realm = $jdbcRealm
    

    (4)测试:

    package com.zlw.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    
    public class AuthenticationTest {
        
        @Test
        public void testAuthentication() {
            //1.构建SecurityManager工厂
            IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
            //2.通过securityManagerFactory工厂获取SecurityManager实例
            SecurityManager securityManager = securityManagerFactory.getInstance();
            //3.将SecurityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            //4.获取欧subject实例
            Subject subject = SecurityUtils.getSubject();
            //5.创建用户名密码验证令牌Token
            UsernamePasswordToken token = new UsernamePasswordToken("victor", "123456");
            //6.进行身份验证
            subject.login(token);
            //7.判断是否认证通过
            System.out.println(subject.isAuthenticated());
        }
    }
    
    结果

    4.自定义Realm提供数据源:

    自定义Realm,可以注入给securityManager更加灵活的安全数据源通过实现Realm接口,或根据需求继承他的相应子类即可。使用自定义Realm提供数据源,从而实现认证 。

    (1)创建数据库表(自定义表名,字段名):

    CREATE TABLE `user` (
      `userid` int(5) NOT NULL AUTO_INCREMENT,
      `username` varchar(30) DEFAULT NULL,
      `pwd` varchar(30) DEFAULT NULL,
      PRIMARY KEY (`userid`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    
    

    (2)导入jar包:


    jar

    (3)自定义Realm继承AuthenticatingRealm类:

    package com.zlw.realm;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    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.realm.AuthenticatingRealm;
    
    public class CustomRealm extends AuthenticatingRealm{
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            PreparedStatement pstm = null;
            Connection conn = null;
            ResultSet rs = null;
            String username = token.getPrincipal().toString();
    
            String principal = null;
            String credentials = null;
             SimpleAuthenticationInfo simpleAuthenticationInfo = null;
            
            try {
                //加载驱动
                Class.forName("com.mysql.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/shiro", "root", "root");
               //创建对象
                pstm = conn.prepareStatement("select *from user where username=?");
                pstm.setString(1,username);
                //执行sql
                rs = pstm.executeQuery();
                while(rs.next()) {
                    principal = rs.getString("username");
                    credentials = rs.getString("pwd");
                }
                
                simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials,"customRealm");
                
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }finally {
                try {
                    rs.close();
                    pstm.close();
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            return simpleAuthenticationInfo;
        }
    }
    

    (4)测试:

    package com.zlw.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    
    public class Test01 {
    
        @Test
        public void testAuthentication() {
            // 1.构建SecurityManager工厂
            IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory();
            // 2.通过securityManagerFactory工厂获取SecurityManager实例
            SecurityManager securityManager = securityManagerFactory.getInstance();
            // 3.将SecurityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            // 4.获取欧subject实例
            Subject subject = SecurityUtils.getSubject();
            // 5.创建用户名密码验证令牌Token
            UsernamePasswordToken token = new UsernamePasswordToken("admin", "1234");
            // 6.进行身份验证
            subject.login(token);
            // 7.判断是否认证通过
            System.out.println(subject.isAuthenticated());
        }
    }
    
    结果

    三、MD5加密

    1.常见 的加密算法:

    • 对称加密算法(加密与解密密钥相同):


      示例
    • 非对称加密算法(加密密钥和解密密钥不同):


      示例
    • 对称与非对称算法比较:


      示例
    • 散列算法比较:


      示例

    2.使用DM5加密:

    • 加盐:

    使用MD5存在一个问题,相同的password生产的Hash值是相同的,如 果两个用户设置了相同的密码,那么数据库当就会存储相同的值,这样是极 不安全的。
    加Salt可以一定程度上解决这一问题。所谓加Salt方法,就是加点 “佐料”。其基本想法是这样的:当用户首次提供密码时(通常是注册时), 由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时, 系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,来 确定密码是否正确。
    原理 :给原文加入随机数生成新的MD5值。

    • 迭代:

    对加密的动作进行重复反馈过程的活动;加密的次数。

    • 代码示例:
    package com.zlw.test;
    
    import org.apache.shiro.crypto.hash.Md5Hash;
    import org.junit.Test;
    
    //MD5加密、加盐以及迭代
    public class MD5Test {
    
        @Test
        public void testMD5() {
            //md5加密
            Md5Hash md5 = new Md5Hash("1234556");
            System.out.println(md5);
        }
        
        @Test
        public void testMD5Hash() {
            //加盐
            Md5Hash md5 = new Md5Hash("123456","aaa");
            System.out.println(md5);
        }
        
        @Test
        public void testMD5Hash2() {
            //迭代
            Md5Hash md5 = new Md5Hash("123456","sxt",3);
            System.out.println(md5);
        }
    }
    
    MD5加密
    加盐
    迭代

    3.凭证匹配器和Shiro的授权:

    • 凭证匹配器:

    在Realm接口的实现类AuthenticatingRealm中有credentialsMatcher属性。 意为凭证匹配器。常用来设置加密算法及迭代次数等。

    • Shiro授权:

    授权:又称作为访问控制,是对资源的访问管理的过程。即对于 认证通过的用户,授予他可以访问某些资源的权限。
    Shiro 支持三种方式的授权:代码触发、注解触发、标签触发 。

    授权示例

    4.使用MD5加密和Shiro授权实现菜单授权:

    (1)创建数据库:

    • users表:


      示例
    • roles角色表(角色和user是一对多关系):


      示例
    • Menus菜单表(菜单和角色是多对多关系):


      示例
    • 角色和菜单的中间表:


      示例

      (2)导入jar包:


      jar
      (3)自定义Realm继承AuthorizingRealm类;重写doGetAuthenticationInfo(AuthenticationToken token):认证的方法;doGetAuthorizationInfo(PrincipalCollection collection):授权方法;
    package com.zlw.realm;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.HashSet;
    import java.util.Set;
    
    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.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    
    /**
     * AuthenticatingRealm:认证使用的Realm,只包含认证的方法,认证时调用doGetAuthenticationInfo方法
     * AuthorizingRealm:授权使用的Realm,继承了AuthenticatingRealm, 包含认证和授权的方法,
     *       认证时调用doGetAuthenticationInfo方法,授权时调用doGetAuthorizationInfo方法
     * 
     * 自定义Realm的实现步骤: 我们通过自定义Realm从指定的数据源中获取数据 
     * 1.自定义Realm,继承AuthorizingRealm类
     * 2.重写认证和授权的方法 doGetAuthenticationInfo(AuthenticationToken token):认证的方法
     * doGetAuthorizationInfo(PrincipalCollection arg0):授权方法
     * 3.在配置文件(shiro.ini)中配置自定义的realm customRealm = com.sxt.realm.CustomRealm
     * securityManager.realm = $customRealm
     * 
     * @author zhang
     *
     */
    public class CustomRealm extends AuthorizingRealm {
    
        // 授权方法
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
            Connection conn = null;
            PreparedStatement pstm = null;
            ResultSet rs = null;
            String username = token.getPrincipal().toString();//从令牌中获取身份信息
            String principal = null;
            String credentials = null;
            String password_salt = null;
            SimpleAuthenticationInfo simpleAuthenticationInfo = null;
    
            try {
                // 加载驱动
                Class.forName("com.mysql.jdbc.Driver");
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/rbac", "root", "root");
                pstm = conn.prepareStatement("select *from users where username=?");
                pstm.setString(1, username);
    
                rs = pstm.executeQuery();
                while (rs.next()) {
                    principal = rs.getString("username");
                    credentials = rs.getString("userpwd");
                    password_salt = rs.getString("salt");//获取盐值
                }
                simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials,ByteSource.Util.bytes(password_salt), "customRealm");
    
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    rs.close();
                    pstm.close();
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            return simpleAuthenticationInfo;
        }
    
        // 响应方法
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection collection) throws AuthenticationException {
            Connection conn = null;
            PreparedStatement pstm = null;
            ResultSet rs = null;
            String userName = collection.getPrimaryPrincipal().toString();//获取主体身份信息
            Set<String> roleNameSet = new HashSet<String>();//创建Set集合拥有封装所有的菜单名称
            Set<String> menuNameSet = new HashSet<String>();//创建Set集合拥有封装所有的菜单名称
            
            try {
                //加载驱动
                Class.forName("com.mysql.jdbc.Driver");
                //获取链接
                conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/rbac", "root", "root");
                String sql = "select r.roleName,m.menuName "
                        + "from users u, roles r, roles_menus rm,menus m "
                        + "where u.role_id=r.roleid "
                        + "and r.roleid=rm.roles_id "
                        + "and rm.menus_id=m.menuid "
                        + "and u.username=?";
                pstm = conn.prepareStatement(sql);
                pstm.setString(1, userName);
                rs = pstm.executeQuery();
                while (rs.next()) {
                    String roleName = rs.getString("roleName");
                    String menuName = rs.getString("menuName");
                    roleNameSet.add(roleName);
                    menuNameSet.add(menuName);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    rs.close();
                    pstm.close();
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
                
            }
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.addRoles(roleNameSet);
            simpleAuthorizationInfo.addStringPermissions(menuNameSet);
            return simpleAuthorizationInfo;
        }
    }
    

    (4)配置Shiro.ini文件:

    [main]
    #配置凭证配置器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    #设置凭证配置器的算法名称
    credentialsMatcher.hashAlgorithmName=MD5 
    #设置凭证匹配器的的迭代次数
    credentialsMatcher.hashIterations=3
    
    #配置Realm
    customRealm =com.zlw.realm.CustomRealm
    #设置Realm的凭证匹配器
    customRealm.credentialsMatcher=$credentialsMatcher
    #将Realm注入给SecurityManager
    securityManager.realm = $customRealm
    

    (5)测试:

    package com.zlw.test;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.subject.Subject;
    import org.junit.Test;
    
    public class MD5Test {
        
        @Test
        public void TestMD5() {
            //1.构建SecurityManager工厂
            IniSecurityManagerFactory securityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
            //2.通过securityManagerFactory工厂获取SecurityManager实例
            SecurityManager securityManager = securityManagerFactory.getInstance();
            //3.将securityManager设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
            //4.获取subject实例
            Subject subject = SecurityUtils.getSubject();
            //5.创建用户名和密码验证令牌Token
            UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123456");
            //6.进行身份验证
            subject.login(token);
            //7.判断是否验证通过
            System.out.println("验证是否通过"+subject.isAuthenticated());
            //判断该用户是否拥有指定的角色
            System.out.println("判断是否拥有指定角色:"+subject.hasRole("管理员"));
            //8.判断该用户是否拥有指定的权限
            System.out.println("是否拥有指定权限:"+subject.isPermitted("客户管理"));
                
        }   
    }
    
    zhangsan
    admin

    5.可能会遇到的异常信息:

    (1)UnknownAccountException:No account found for user
    用户主体找不到,用户名错误。
    (2)IncorrectCredentialsException:did not match the expected credentials
    用户认证没有找到相匹配的凭证;用户密码错误。

    相关文章

      网友评论

          本文标题:Shiro学习day-71:Shiro安全框架

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