zoukankan      html  css  js  c++  java
  • Shiro安全框架

    Shiro简介

           Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和
    会话管理等功能。
            对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。其不仅可
    以用在JavaSE环境,也可以用在JavaEE环境。

    二、Shiro架构图

    1.从外部来看Shiro,即从应用程序角度来观察如何使用Shiro完成工作。如
    下图:

    2.从Shiro内部看Shiro的架构,如下图所示:

     

    Subject(org.apache.shiro.subject.Subject)当前与软 件进行交互的实体(用户,第三方服务,cron job,等 等)的安全特定“视图”


    SecurityManager:SecurityManager 是 Shiro 架构的 心脏。它基本上是一个“保护伞”对象,协调其管理的组
    件 以 确 保 它 们 能 够 一 起 顺 利 的 工 作 类 似 于SpringMVC中的入口 servlet

    Realms:域 Realms 在 Shiro 和你的应用程序的安全数据之间担当 “桥梁”或“连接器”。当它实际上与安全相关的数据如用
    来执行身份验证(登录)及授权(访问控制)的用户帐户交互时, Shiro从一个或多个为应用程序配置的Real中寻找许多这样的东西

    Shiro 的环境搭建


    使用 shiro 实现登陆的操作


    第一步 导包
    第二步:书写 shiro.ini 文件

    [users] 
    zs=123 
    sxt=root

    第三步:书写测试代码

    import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFact ory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.apache.shiro.mgt.SecurityManager;
    public class TestA {
    public static void main(String[] args) {
    //[1]解析shiro.ini文件
    Factory<SecurityManager> factory =new IniSecurityManagerFactory("classpath:shiro.ini ");
    //[2]通过SecurityManager工厂获得 SecurityManager实例
    SecurityManager securityManager = factory.getInstance();
    //[3]把SecurityManager对象设置到运行环境中
    SecurityUtils.setSecurityManager(securityManag er);
    //[4]通过SecurityUtils获得主体subject Subject subject = SecurityUtils.getSubject();
    //[5]书写自己输入的账号和密码---相当于用户自 己输入的账号和密码 //我们拿着自己书写用户名密码去和shiro.ini 文 件中的账号密码比较 UsernamePasswordToken token =new UsernamePasswordToken("sxt","root");
    //[6]进行身份的验证 subject.login(token);
    //[7]通过方法判断是否登录成功
    if(subject.isAuthenticated()){ System.out.println("登录成功");
    }else { System.out.println("登录失败");
    }
    }
    }
    View Code

    Shiro 验证时异常分析


    DisabledAccountException

    账户失效异常


    ConcurrentAccessException

    竞争次数过多


    ExcessiveAttemptsException

    尝试次数过多


    UnknownAccountException

    用户名不正确


    IncorrectCredentialsException

    凭证(密码)不正确


    ExpiredCredentialsException
    凭证过期

    Shiro--认证流程

    三、Shiro涉及常见名词

    四、Shiro配置文件详解

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

    1、[main]

    main主要配置shiro的一些对象,例如securityauthenticator,authcStrategy 等等,例如:

    2、[users]

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

    3、[roles]

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

    4、[urls]


    这部分配置主要在web应用中,格式为:url=拦截器[参数],拦截器[参
    数]……,例如

     

     

     

     

     

     

     

     

     

     

     

    五、认证实现

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


    5.1.principals(用户名)

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


    5.2.credentials(密码)

    凭证,即只有主体知道的安全值,如密码/数字证书等。


    最常见的principals和credentials组合就是用户名/密码了。

    5.3 实现步骤


    5.3.1 导入jar包
    5.3.2 从源码的示例项目quickstart中拷贝shiro.ini放到src下,并配置
    5.3.3 编写代码

    package com.bjsxt.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());
        }
    }
    View Code

    shiro.ini

    [users]
    
    victor=123456
    View Code

    六、JDBCRealm

    Shiro默认使用自带的IniRealm,IniRealm从ini配置文件中读取用户的信息。


    大部分情况下需要从系统的数据库中读取用户信息,所以需要使用JDBCRealm或自定义Realm。

    需求:使用JDBCRealm提供数据源,从而实现认证


    实现步骤:
    6.1建users表(表名、字段对应上)
    6.2添加jar包(数据库驱动、数据库连接池、beanutils等)
    6.3编写shiro.ini
    6.4编写测试代码

    AuthenticationTest.java

    package com.bjsxt.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());
        }
    }
    View Code

    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:///test
    dataSource.user = root
    dataSource.password = victor
    
    jdbcRealm.dataSource = $dataSource
    
    #将Realm注入给SecurityManager
    securityManager.realm = $jdbcRealm
    View Code

    认证策略


    规定了如果有多个数据源的时候应该如何操作


    AtLeastOneSuccessfulStrategy

    如果一个(或更多)Realm 验证成功,则整体的尝试被认为是成功的。
    如果没有一个验证成功,
    则整体尝试失败 类似于 java 中的 &


    FirstSuccessfulStrategy
    只有第一个成功地验证的 Realm 返回的信息将被使用。所有进一步的
    Realm 将被忽略。如果没有一个验证成功,则整体尝试失败。
    类似于 java 中的 &&


    AllSucessfulStrategy

    为了整体的尝试成功,所有配置的 Realm 必须验证成功。如果没有一个验
    证成功,则整体尝试失败

    package com.bjsxt.shiro1;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.config.IniSecurityManagerFactory;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.realm.Realm;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.util.Factory;
    
    public class TestA {
    
        public static void main(String[] args) {
    
            /*Realm*/
    
            //[1]解析shiro.ini文件
            Factory<SecurityManager>  factory =new IniSecurityManagerFactory("classpath:shiro-jdbc.ini");
    
            //[2]通过SecurityManager工厂获得SecurityManager实例
            SecurityManager securityManager = factory.getInstance();
    
            //[3]把SecurityManager对象设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
    
            //[4]通过SecurityUtils获得主体subject
            Subject subject = SecurityUtils.getSubject();
    
            //[5]书写自己输入的账号和密码---相当于用户自己输入的账号和密码
            //我们拿着自己书写用户名密码去和shiro.ini 文件中的账号密码比较
            UsernamePasswordToken  token =new UsernamePasswordToken("root","123");
    
            try {
                //[6]进行身份的验证
                subject.login(token);
    
                //[7]通过方法判断是否登录成功
    
                if(subject.isAuthenticated()){
                    System.out.println("登录成功");
    
                }
            } catch (IncorrectCredentialsException e) {
    
                System.out.println("登录失败");
    
    
            }catch (UnknownAccountException e){
    
                System.out.println("用户名不正确");
            }
        }
    }
    View Code
    [main]
    #获得数据源A
    dataSou=com.mchange.v2.c3p0.ComboPooledDataSource
    dataSou.driverClass=com.mysql.jdbc.Driver
    dataSou.jdbcUrl=jdbc:mysql://127.0.0.1:3306/shiro
    dataSou.user=root
    dataSou.password=root
    
    #配置了jdbcRealmA
    jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
    jdbcRealm.dataSource=$dataSou
    
    
    #获得数据源B
    dataSou1=com.mchange.v2.c3p0.ComboPooledDataSource
    dataSou1.driverClass=com.mysql.jdbc.Driver
    dataSou1.jdbcUrl=jdbc:mysql://127.0.0.1:3306/shiro1
    dataSou1.user=root
    dataSou1.password=root
    
    #配置了jdbcRealmB
    jdbcRealm1=org.apache.shiro.realm.jdbc.JdbcRealm
    jdbcRealm1.dataSource=$dataSou1
    
    
    #配置验证器
    authenticationStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
    
    
    
    #设置securityManager中realm
    securityManager.realms=$jdbcRealm,$jdbcRealm1
    securityManager.authenticator.authenticationStrategy=$authenticationStrategy
    View Code

    七、如何自定义Realm

    [1]为什么使用自定义 Realm


    我们使用 JDBCRealm 的时候发现,shiro 的底层自己封装了数据库
    表的名称和字段的名称,这样就造成了使用起来非常不方便

    [2]解决方案

    自定义 Realm
    我们如果自己定义 realm 的话,可以实现这个接口

    自定义Realm,可以注入给securityManager更加灵活的安全数据源(例如,JDBCRealm中表和字段都限定了)

    通过实现Realm接口,或根据需求继承他的相应子类即可。


    需求:使用自定义Realm提供数据源,从而实现认证


    实现步骤:


    6.1添加jar包
    6.2编写自定义Realm
    6.3编写shiro.ini
    6.4编写测试类

    package com.bjsxt.shiro2;
    
    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 java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    
    public class UserRealm  extends AuthorizingRealm {
    
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
            //System.out.println(authenticationToken.getPrincipal());
    
            try {
                Class.forName("com.mysql.jdbc.Driver");
    
                Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro", "root", "root");
    
    
                PreparedStatement prepareStatement = conn.prepareStatement("select  pwd  from  admin  where  uname =? ");
    
                prepareStatement.setObject(1,authenticationToken.getPrincipal());
    
                ResultSet rs = prepareStatement.executeQuery();
    
                while (rs.next()){
    
                    SimpleAuthenticationInfo  info=new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),rs.getString("pwd"),"userRealm");
    
                    return   info;
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
            return null;
        }
    
    
    
         //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
    
    }
    View Code
    package com.bjsxt.shiro2;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    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.apache.shiro.util.Factory;
    
    public class TestB {
    
        public static void main(String[] args) {
    
            /*Realm*/
    
            //[1]解析shiro.ini文件
            Factory<SecurityManager>  factory =new IniSecurityManagerFactory("classpath:shiro-jdbc2.ini");
    
            //[2]通过SecurityManager工厂获得SecurityManager实例
            SecurityManager securityManager = factory.getInstance();
    
            //[3]把SecurityManager对象设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
    
            //[4]通过SecurityUtils获得主体subject
            Subject subject = SecurityUtils.getSubject();
    
            //[5]书写自己输入的账号和密码---相当于用户自己输入的账号和密码
            //我们拿着自己书写用户名密码去和shiro.ini 文件中的账号密码比较
            UsernamePasswordToken  token =new UsernamePasswordToken("root","123");
    
            try {
                //[6]进行身份的验证
                subject.login(token);
    
                //[7]通过方法判断是否登录成功
    
                if(subject.isAuthenticated()){
                    System.out.println("登录成功");
    
                }
            } catch (IncorrectCredentialsException e) {
    
                System.out.println("登录失败");
    
    
            }catch (UnknownAccountException e){
    
                System.out.println("用户名不正确");
            }
        }
    }
    View Code
    [main]
    #设置securityManager中realm
    userRealm=com.bjsxt.shiro2.UserRealm
    securityManager.realms=$userRealm
    View Code


    【代码示例】

     realms

    package com.bjsxt.realms;
    
    import java.net.ConnectException;
    import java.security.interfaces.RSAKey;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    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;
    
    import com.mysql.jdbc.Driver;
    
    public class CustomRealm extends AuthenticatingRealm {
    
        private String principal;
        private String credentials;
        private ResultSet rs;
        private Statement state;
        private Connection conn;
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //使用JDBC,从数据库获取数据
            try {
                //1.注册驱动
                Driver driver = new Driver();
                DriverManager.registerDriver(driver);
                //2.获取连接对象
                String url ="jdbc:mysql:///test";
                String user = "root";
                String password = "victor";
                conn = DriverManager.getConnection(url , user , password );
                state = conn.createStatement();
                //4.执行sql语句
                String sql = "select userName,passwd from starLogin";
                rs = state.executeQuery(sql );
                //5.处理结果集
                while (rs.next()) {
                    principal = rs.getString("userName");
                    credentials = rs.getString("passwd");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                if(rs != null){
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if(state != null){
                    try {
                        state.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if(conn != null){
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials, "customRealm");
            return simpleAuthenticationInfo;
        }
    
        
    }
    View Code

    AuthenticationTest

    package com.bjsxt.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","123");
            //6.进行身份验证
            subject.login(token);
            //7.判断是否认证通过
            System.out.println(subject.isAuthenticated());
        }
    }
    View Code

    Shrio.ini

    [main]
    #配置Realm
    customRealm = com.bjsxt.realms.CustomRealm
    
    #将Realm注入给SecurityManager
    securityManager.realm = $customRealm
    View Code

    八、密码加密实现方案


    8.1几种常见加密算法比较


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

    8.1.2非对称算法(加密密钥和解密密钥不同)

    8.1.3 对称与非对称算法比较

    8.1.4 散列算法比较‘

    8.2 MD5加密、加盐与迭代


    加盐:

    使用MD5存在一个问题,相同的password生产的Hash值是相同的,如
    果两个用户设置了相同的密码,那么数据库当就会存储相同的值,这样是极
    不安全的。

    加Salt可以一定程度上解决这一问题。所谓加Salt方法,就是加点
    “佐料”。其基本想法是这样的:当用户首次提供密码时(通常是注册时),
    由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,来确定密码是否正确。

    加盐原理:

    给原文加入随机数生成新的MD5值。
    迭代:加密的次数
    【代码示例】

     md5Test

    package com.bjsxt.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("123456");
            System.out.println(md5);
            //加盐
            md5 = new Md5Hash("123456", "bjsxt");
            System.out.println(md5);
            //迭代
            md5 = new Md5Hash("123456", "bjsxt", 2);
            System.out.println(md5);
        }
    }
    View Code

    TestB.java

    package com.bjsxt.shiro3;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    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.apache.shiro.util.Factory;
    
    public class TestB {
    
        public static void main(String[] args) {
    
            /*Realm*/
    
            //[1]解析shiro.ini文件
            Factory<SecurityManager>  factory =new IniSecurityManagerFactory("classpath:shiro-jdbc3.ini");
    
            //[2]通过SecurityManager工厂获得SecurityManager实例
            SecurityManager securityManager = factory.getInstance();
    
            //[3]把SecurityManager对象设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
    
            //[4]通过SecurityUtils获得主体subject
            Subject subject = SecurityUtils.getSubject();
    
            //[5]书写自己输入的账号和密码---相当于用户自己输入的账号和密码
            //我们拿着自己书写用户名密码去和shiro.ini 文件中的账号密码比较
            UsernamePasswordToken  token =new UsernamePasswordToken("root","111");
    
            try {
                //[6]进行身份的验证
                subject.login(token);
    
                //[7]通过方法判断是否登录成功
    
                if(subject.isAuthenticated()){
                    System.out.println("登录成功");
    
                }
            } catch (IncorrectCredentialsException e) {
    
                System.out.println("登录失败");
    
    
            }catch (UnknownAccountException e){
    
                System.out.println("用户名不正确");
            }
        }
    }
    View Code

    TestDemo.java

    package com.bjsxt.shiro3;
    
    import org.apache.shiro.crypto.hash.Md5Hash;
    
    public class TestDemo   {
    
    
        public static void main(String[] args) {
    
    
            //使用MD5加密
            Md5Hash  md5=new Md5Hash("1111");
    
            System.out.println("1111=="+md5);
    
            //加盐
            md5=new Md5Hash("1111","sxt");
    
            System.out.println("1111=="+md5);
    
            //迭代次数
            md5=new Md5Hash("123","sxt",2);
    
            System.out.println("1111=="+md5);
    
    
    
    
        }
    
    }
    View Code

    shiro-jdbc.ini

    [main]
    #设置securityManager中realm
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    credentialsMatcher.hashAlgorithmName=md5
    credentialsMatcher.hashIterations=2
    
    userRealm=com.bjsxt.shiro3.UserRealm
    userRealm.credentialsMatcher=$credentialsMatcher
    securityManager.realms=$userRealm
    View Code

    8.3 凭证匹配器


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

    Shiro.ini

    [main]
    
    #配置凭证匹配器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    
    #设置凭证匹配器的相关属性
    credentialsMatcher.hashAlgorithmName=MD5
    credentialsMatcher.hashIterations=2
    
    #配置Realm
    customRealm=com.bjsxt.realms.CustomRealm
    
    #配置Realm的凭证匹配器属性
    customRealm.credentialsMatcher=$credentialsMatcher
    
    #将Realm注入给SecurityManager
    securityManager.realm=$customRealm
    View Code

    costomRealm

    package com.bjsxt.realms;
    
    import java.net.ConnectException;
    import java.security.interfaces.RSAKey;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    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;
    import org.apache.shiro.util.ByteSource;
    
    import com.mysql.jdbc.Driver;
    
    public class CustomRealm extends AuthenticatingRealm {
    
        private String principal;
        private String credentials;
        private ResultSet rs;
        private Statement state;
        private Connection conn;
        private String salt;
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            //使用JDBC,从数据库获取数据
            try {
                //1.注册驱动
                Driver driver = new Driver();
                DriverManager.registerDriver(driver);
                //2.获取连接对象
                String url ="jdbc:mysql:///test";
                String user = "root";
                String password = "victor";
                conn = DriverManager.getConnection(url , user , password );
                state = conn.createStatement();
                //4.执行sql语句
                String sql = "select userName,passwd,passwd_salt from starLogin";
                rs = state.executeQuery(sql );
                //5.处理结果集
                while (rs.next()) {
                    principal = rs.getString("userName");
                    credentials = rs.getString("passwd");
                    salt = rs.getString("passwd_salt");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }finally{
                if(rs != null){
                    try {
                        rs.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if(state != null){
                    try {
                        state.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if(conn != null){
                    try {
                        conn.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }
            
            ByteSource newSalt = ByteSource.Util.bytes(salt);
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal, credentials,newSalt , "customRealm");
            return simpleAuthenticationInfo;
        }
    
        
    }
    View Code

    AutenticationTest

    package com.bjsxt.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());
        }
    }
    View Code

    9.授权

    授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限
    (Permission)、角色(Role)。
     
    主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权
    后才允许访问相应的资源。
     
    资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些
    数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
     
    权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户
    有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用
    户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控
    制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允
    许。
     
    Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,
    即实例级别的)
     
    角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有
    一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等
    都是角色,不同的角色拥有一组不同的权限




    详细见代码

    package com.bjsxt.shiro1;
    
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    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.apache.shiro.util.Factory;
    
    import java.util.Arrays;
    
    public class TestA {
    
        public static void main(String[] args) {
            //[1]解析shiro.ini文件
            Factory<SecurityManager>  factory =new IniSecurityManagerFactory("classpath:shiro.ini");
    
            //[2]通过SecurityManager工厂获得SecurityManager实例
            SecurityManager securityManager = factory.getInstance();
    
            //[3]把SecurityManager对象设置到运行环境中
            SecurityUtils.setSecurityManager(securityManager);
    
            //[4]通过SecurityUtils获得主体subject
            Subject subject = SecurityUtils.getSubject();
    
            //[5]书写自己输入的账号和密码---相当于用户自己输入的账号和密码
            //我们拿着自己书写用户名密码去和shiro.ini 文件中的账号密码比较
            UsernamePasswordToken  token =new UsernamePasswordToken("zs","123");
    
            try {
                //[6]进行身份的验证
                subject.login(token);
    
            } catch (IncorrectCredentialsException e) {
                System.out.println("登录失败");
            }
    
    
            //授权的查询
    
            //基于角色的授权
            boolean flag = subject.hasRole("role1");
            //System.out.println(flag);
    
            //判断是否具有多个角色
            boolean[] booleans = subject.hasRoles(Arrays.asList("role1", "role3"));
    
            /*for(Boolean b:booleans){
    
                System.out.println(b);
            }*/
    
            //可以使用checkRole判断指定用户是否具有对应角色
            //如果指定用户下没有对应的角色就会抛出异常UnauthorizedException
           /* subject.checkRole("role3");
    
            subject.checkRoles("role1","role2");*/
    
    
           //基于资源的授权
            boolean flag2 = subject.isPermitted("iii");
    
            //System.out.println(flag2);
    
            //判读是否具有多个资源
            boolean permittedAll = subject.isPermittedAll("add", "oo", "ii");
    
            //通过checkPermission 进行判断指定用户下是否有指定的资源
            //如果没有就会抛出UnauthorizedException
            subject.checkPermission("uu");
    
            subject.checkPermissions("ii","ooo","add");
    
    
        }
    }
    View Code

    Role.java

    package com.bjsxt.shiro1;
    
    import org.apache.shiro.authz.annotation.RequiresRoles;
    
    public class Role {
    
    
    
    }
    View Code

    9.Shiro 中的授权检查的 3 种方式

    Shiro 支持三种方式的授权:
     
    编程式:通过写if/else 授权代码块完成
     
    注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常
     
    JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成

    默认拦截器

     
     Shiro 内置了很多默认的拦截器,比如身份验证、授权等
    相关的。默认拦截器可以参考
    org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举
    拦截器:
    身份验证相关的
    授权相关的

     

    其他

     

    Permissions
     
    • 规则:资源标识符:操作:对象实例 ID 即对哪个资源的哪个
    实例可以进行什么操作. 其默认支持通配符权限字符串,: 表
    示资源/操作/实例的分割;, 表示操作的分割,* 表示任意资
    源/操作/实例。
    • 多层次管理:
    – 例如:user:query、user:edit
    – 冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分
    是权限被操作的领域(打印机),第二部分是被执行的操作。
    – 多个值:每个部件能够保护多个值。因此,除了授予用户 user:query
    和 user:edit 权限外,也可以简单地授予他们一个:user:query, edit
    – 还可以用 * 号代替所有的值,如:user:* , 也可以写:*:query,表示
    某个用户在所有的领域都有 query 的权限
    Shiro 的 Permissions
     
    • 实例级访问控制
    – 这种情况通常会使用三个部件:域、操作、被付诸实
    施的实例。如:user:edit:manager
    – 也可以使用通配符来定义,如:user:edit:*、user:*:*、
    user:*:manager
    – 部分省略通配符:缺少的部件意味着用户可以访问所
    有与之匹配的值,比如:user:edit 等价于 user:edit :*、
    user 等价于 user:*:*
    – 注意:通配符只能从字符串的结尾处省略部件,也就
    是说 user:edit 并不等价于 user:*:edit

     

    授权流程

     
    流程如下:
    1、首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给
    SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
     2、Authorizer是真正的授权者,如果调用如
    isPermitted(“user:view”),其首先会通过
    PermissionResolver 把字符串转换成相应的 Permission 实例;
    3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角
    色/权限用于匹配传入的角色/权限;
    4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果
    有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,
    如果匹配如 isPermitted*/hasRole* 会返回true,否则返回false表示
    授权失败
    ModularRealmAuthorizer
     
    • ModularRealmAuthorizer 进行多 Realm 匹配流程:
    – 1、首先检查相应的 Realm 是否实现了实现了Authorizer;
    – 2、如果实现了 Authorizer,那么接着调用其相应的
    isPermitted*/hasRole* 接口进行匹配;
    – 3、如果有一个Realm匹配那么将返回 true,否则返回 false。
     

    Shiro 标签

    Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如
    根据登录用户显示相应的页面按钮。
     guest 标签:用户没有身份验证时显示相应信息,即游客
    访问信息: 
    user 标签:用户已经经过认证/记住我登录后显示相应的信
    息。

     authenticated 标签:用户已经身份验证通过,即

    Subject.login登录成功,不是记住我登录的
     
     
    • notAuthenticated 标签:用户未进行身份验证,即没有调
    用Subject.login进行登录,包括记住我自动登录的也属于
    未进行身份验证。

    pincipal 标签:显示用户身份信息,默认调用
    Subject.getPrincipal() 获取,即 Primary Principal。
     
     
    hasRole 标签:如果当前 Subject 有角色将显示 body 体内
    容:
    hasAnyRoles 标签:如果当前Subject有任意一个
    角色(或的关系)将显示body体内容。
     
    lacksRole:如果当前 Subject 没有角色将显
    示 body 体内容
     
    hasPermission:如果当前 Subject 有权限
    将显示 body 体内容
    lacksPermission:如果当前Subject没有权
    限将显示body体内容。

    权限注解

    @RequiresAuthentication:表示当前Subject已经通过login
    进行了身份验证;即 Subject. isAuthenticated() 返回 true
     
    @RequiresUser:表示当前 Subject 已经身份验证或者通过记
    住我登录的。
     
    @RequiresGuest:表示当前Subject没有身份验证或通过记住
    我登录过,即是游客身份。
     
     @RequiresRoles(value={“admin”, “user”}, logical=
    Logical.AND):表示当前 Subject 需要角色 admin 和user
     
     @RequiresPermissions (value={“user:a”, “user:b”},
    logical= Logical.OR):表示当前 Subject 需要权限 user:a 或
    user:b。
     

    自定义拦截器

    通过自定义拦截器可以扩展功能,例如:动态url-角色/权
    限访问控制的实现、根据 Subject 身份信息获取用户信息
    绑定到 Request(即设置通用数据)、验证码验证、在线
    用户信息的保存等
     
     
    概述
     
      Shiro 提供了完整的企业级会话管理功能,不依赖于底层容
    器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境
    都可以使用,提供了会话管理、会话事件监听、会话存储/
    持久化、容器无关的集群、失效/过期支持、对Web 的透明
    支持、SSO 单点登录的支持等特性。
     
    会话相关的 API
     
     Subject.getSession():即可获取会话;其等价于
    Subject.getSession(true),即如果当前没有创建 Session 对象会创建
    一个;Subject.getSession(false),如果当前没有创建 Session 则返回null
     
    • session.getId():获取当前会话的唯一标识
     
    • session.getHost():获取当前Subject的主机地址
     
    • session.getTimeout() & session.setTimeout(毫秒):获取/设置当
    前Session的过期时间
     
    • session.getStartTimestamp() & session.getLastAccessTime():
    获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定
    期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每
    次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间。
     
    session.touch() & session.stop():更新会话最后访问时
    间及销毁会话;当Subject.logout()时会自动调用 stop 方法
    来销毁会话。如果在web中,调用 HttpSession. invalidate()
    也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会
     
    • session.setAttribute(key, val) &
    session.getAttribute(key) &
    session.removeAttribute(key):设置/获取/删除会话属
    性;在整个会话范围内都可以对这些属性进行操作
     

     

     

     

     

    自定义 Realm 实现授权


    我们仅仅通过配置文件指定授权是非常的不灵活的,在实际的应用中
    我们是将用户的信息和合权限信息保存到数据库中,我们是从数据库
    中获得用户的信息 ,使用 JDBCRealm 进行授权 。使用 JDBCRealm 操
    作的时候也不是很灵活。所以我们一般使用自定义 Realm 实现授权。

    package com.bjsxt.shiro;
    
    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 java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.util.ArrayList;
    import java.util.List;
    
    public class UserRealm  extends AuthorizingRealm {
    
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
            //System.out.println(authenticationToken.getPrincipal());
    
            try {
                Class.forName("com.mysql.jdbc.Driver");
    
                Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro", "root", "root");
    
    
                PreparedStatement prepareStatement = conn.prepareStatement("select  pwd  from  admin  where  uname =? ");
    
                prepareStatement.setObject(1,authenticationToken.getPrincipal());
    
                ResultSet rs = prepareStatement.executeQuery();
    
                while (rs.next()){
    
                    SimpleAuthenticationInfo  info=new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),rs.getString("pwd"),"userRealm");
    
                    return   info;
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
            return null;
        }
    
    
    
         //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
            String username = principalCollection.getPrimaryPrincipal().toString();
    
            //获得username  然后去数据库查询这个用户对应的角色,在根据角色查询出指定角色下对应的菜单,
            //返回给指定角色下的所有菜单--List集合
            System.out.println("username="+username);
    
            //模拟数据库查的菜单
            List<String>  list =new ArrayList<>();
            list.add("updateUser");
            list.add("addUser");
            list.add("deleteUser");
    
            SimpleAuthorizationInfo  simpleAuthorizationInfo=new SimpleAuthorizationInfo();
    
            for(String   l:list){
                simpleAuthorizationInfo.addStringPermission(l);
            }
    
            return simpleAuthorizationInfo;
        }
    
    
    }
    View Code

    相关博客

    https://blog.csdn.net/pengjwhx/article/details/84867112

  • 相关阅读:
    linux安装mysql8
    linux安装nginx
    linux tp5隐藏index.php
    E45: 'readonly' option is set (add ! to override)
    linux安装git方法
    php-5.6 添加php_zip.dll拓展
    双向链表
    每日一题 为了工作 2020 0315 第十三题
    每日一题 为了工作 2020 03014 第十二题
    每日一题 为了工作 2020 03013 第十一题
  • 原文地址:https://www.cnblogs.com/wq-9/p/10945035.html
Copyright © 2011-2022 走看看