zoukankan      html  css  js  c++  java
  • Shiro权限验证

    一、Shiro介绍

    1、可从本教程中明白以下几个知识点:

    • 认识Shiro的整体架构和各组件的概念;
    • Shiro认证、授权过程;
    • Shiro自定义Realm.

    2、Shiro简介

    Shiro是Apache强大灵活的开源的安全框架,有认证、授权、企业会话管理、安全加密、缓存管理等功能。

    Shiro和Spring Security相比较;Shiro更加简单方便、并且可脱离Spring,Spring Security比较笨重复杂,不可脱离Spring。

    3、Shiro架构图

    我主要解释下几个对象:

    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 能得到合法
    的用户及其权限进行判断。

    建议看不懂的可以先直接看完下面的4个案例,再回头看看就很明白了。^_^

    详细图如上面所示:在这里就不介绍具体每个组件了,我会在以下4个实例代码中详细说明;

    4、项目中用到的依赖:

      

    添加完依赖,以下所有实例都可以直接运行。

    二、第一个实例(初识Shiro认证/授权)

    1、Shiro认证/授权过程:代码有详解

    认证原理:创建SecurityManager---->主体提交认证请求----->提交到SecurityManager认证---->SecurityManager认证是由Authenticator进行认证---->Authenticator认证需要通过Realm验证用户数据。

    授权原理:创建SecurityManager---->主体授权---->提交到SecurityManager授权---->SecurityManager授权是有Authorizer进行授权---->Realm获取角色权限数据

    public class AuthenticationTest {
        SimpleAccountRealm simpleAccountRealm=new SimpleAccountRealm();
        @Before
        public void getAccount()
        {
            //方便测试  新增用户和角色权限
            simpleAccountRealm.addAccount("quentin","123456","admin");
        }
        @Test
        public void AuthenticationTest()
        {
            //1、创建SecurtyManager
            DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
            defaultSecurityManager.setRealm(simpleAccountRealm);
    
            //2、主体认证/授权
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject=SecurityUtils.getSubject();//获得当前正在执行程序的用户
    
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
            //3、login()主体认证请求,里面封装好的不用管,它是由SecurityManager认证->Authenticate认证->Realm验证组成,这样就可以实现认证。
            subject.login(usernamePasswordToken);
            System.out.println("isAuthen:"+subject.isAuthenticated());
            //3、checkRole()主体授权请求,里面封装好的不用管,它是由SecurityManager授权->Authenticate授权->Realm获取角色权限组成,这样就可以实现授权功能。
            subject.checkRole("admin");
        }
    }

    运行结果:

      

    当验证不通过会抛出异常,没有权限也会抛出异常!!

     所以,总结一下整个流程,以认证为例:就是在我们调用Subject的login()方法之后,可以看到我传入的是用户的token(里面的实际原理不用管,其实就是第一第二步它经历过一系列的步骤,它调用了Realm中的doGetAuthenticationInfo(token)方法)。

    三、第二个实例(IniRealm实例讲解)

     介绍:通过加载.ini文件生成realm对象来验证

    1、在resourses中建立user.ini文件,内容如下:

    [users]
    quentin=123456,admin
    [roles]
    admin=user:delete,user:update

    设置用户名"quentin",密码是"123456",“admin”角色,"admin"拥有“user:delete、user:update”权限。

    2、实例文件代码如下:

    public class IniRealmTest {
    
        @Test
        public void IniRealmTest()
        {
            IniRealm iniRealm=new IniRealm("classpath:user.ini");  //配置user.ini文件(账号和权限等信息)
    
            //1、创建SecurtyManager
            DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
            defaultSecurityManager.setRealm(iniRealm);//设置Realm
    
            //2、主体认证/授权请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject=SecurityUtils.getSubject();
    
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
            subject.login(usernamePasswordToken);
            System.out.println("isAuth:"+subject.isAuthenticated());//是否认证
    
            //判断是否授权等
            subject.checkRole("admin");//是否有角色权限
            subject.checkPermission("user:delete");//是否有删除权限
        }
    }

    运行结果:

      

    说明认证通过(用户名密码正确),但是跑出没有权限异常(因为admin没有user:deleteall全选)。

    四、第三个实例(JdbcRealm实例讲解)

      介绍:通过获取数据库用户数据来验证

    1、实例代码如下:

    public class JdbcRealmTest {
    
        DruidDataSource dataSource=new DruidDataSource();
        {
            dataSource.setUrl("jdbc:mysql://10.0.92.23:3306/test");
            dataSource.setUsername("root");
            dataSource.setPassword("123");
        }
    
        @Test
        public void JdbcRealmTest()
        {
            JdbcRealm jdbcRealm=new JdbcRealm();
            jdbcRealm.setDataSource(dataSource);
    
            String sql="select password from user where name=?";
            jdbcRealm.setAuthenticationQuery(sql);    //查询认证语句
            String sqlrole="select rolename from role where name=?";
            jdbcRealm.setUserRolesQuery(sqlrole);     //查询角色权限语句
    
            //1、创建SecurtyManager
            DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
            defaultSecurityManager.setRealm(jdbcRealm);
    
            //2、主体认证/授权请求
            SecurityUtils.setSecurityManager(defaultSecurityManager);
            Subject subject=SecurityUtils.getSubject();
    
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("aaa","dfdf");
            subject.login(usernamePasswordToken);
    
            System.out.println("isAuth:"+subject.isAuthenticated());//判断是有认证
    
            subject.checkRole("admin");//是否有权限
        }
    }

     运行结果也一样就不演示了。其实JdbcRealm里面也提供了默认的sql语句,但是考虑到查询不一样,所以需要单独编写。

    五、第四个实例(自定义Realm及Shiro加密)

    1、自定义Realm,分为doGetAuthenticationInfo()认证 和 doGetAuthorizationInfo()授权信息两部分,注意代码注释:

    代码如下:

    //自定义Realm,代码如下:
    public class CustomerRealm extends AuthorizingRealm {
    
        //继承父类AuthorizingRealm将获取Subject相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo);
    
        // 原理!!!!!!!!!
        /* doGetAuthenticationInfo获取身份验证相关信息:首先根据传入的用户名获取User信息;
        * 然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;如果user找到但锁定了抛出锁定异常LockedAccountException;
        * 最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,如果不匹配将抛出密码错误异常IncorrectCredentialsException;
        * 另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;
        * 在组装SimpleAuthenticationInfo信息时,需要传入:身份信息(用户名)、凭据(密文密码)、盐(username+salt),CredentialsMatcher使用盐加密传入的明文密码和此处的密文密码进行匹配。
        * */
    
        @Override
        //用于当前用户验证。认证login()方法执行会自动调用
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            //1、从主体传过来的认证信息中,获取用户名
            String UserName=(String)token.getPrincipal();  //        getPrincipal();身份 | getCredentials();凭据
    
            //2、通过用户名从数据库中获取密码凭证
            String Password=getPasswordByUsername(UserName);
            if(Password==null)
            {
                return null;
            }
            //组装SimpleAuthenticationInfo信息,(用户名、密文密码、盐)
            SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo("quentin",Password,"customerReal");
            simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt"));  //将盐注册到信息中去
    
            return simpleAuthenticationInfo;
        }
    
        //方便测试 设置用户数据
        Map<String,String> userMap=new HashMap<String, String>(16);
        {
            //将带盐"salt"也一起加密
            Md5Hash passMD5=new Md5Hash("123456","salt");
            userMap.put("quentin",passMD5.toString());
            super.setName("customerReal");
        }
        private String getPasswordByUsername(String userName) {
            return userMap.get(userName);
        }
    
        //----------------------------------------------------------------以上是认证,以下是授权------------------------------------------------------------------------------------------------------
    
        //doGetAuthorizationInfo获取授权信息:PrincipalCollection是一个身份集合,因为我们现在就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可;
        // 然后根据用户名调用UserService接口获取角色及权限信息。
    
        @Override
        //用于当前登录用户授权,用户的角色与权限初始化,授权会自动调用
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            //获取用户名
            String UserName=(String)principals.getPrimaryPrincipal();  //getPrimaryPrincipal()得到主要的身份  Set<String> getRealmNames(); //获取所有身份验证通过的Realm名字
    
            //获取角色和权限信息(一般从数据库或缓存中获取)
            Set<String> setRoles=getRolesByUserName();
            Set<String> setPression=getPressionByUserName();
    
            //设置SimpleAuthorizationInfo信息,(角色和权限)
            SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
            simpleAuthorizationInfo.setRoles(setRoles);
            simpleAuthorizationInfo.setStringPermissions(setPression);
            return simpleAuthorizationInfo;
        }
    
        //方便测试  设置用户权限数据
        private Set<String> getPressionByUserName() {
            Set<String> sets=new HashSet<String>();
            sets.add("user:add");
            sets.add("user:delete");
            sets.add("user:select");
            sets.add("admin:select");
            return sets;
        }
    
        //方便测试  设置用户角色数据
        private Set<String> getRolesByUserName() {
            Set<String> sets=new HashSet<String>();
            sets.add("admin");
            sets.add("user");
            return sets;
        }
    
    }

    2、实例运行代码如下:

    public class CustomerRealmTest {
    
        @Test
        public void CustomerRealmTest()
        {
            //引入自定义Realm
            CustomerRealm customerRealm=new CustomerRealm();
    
            //创建DefaultSecurityManager
            DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
            defaultSecurityManager.setRealm(customerRealm);
    
            //设置算法名称和加密次数
            HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
            matcher.setHashAlgorithmName("md5");
            matcher.setHashIterations(1);
            customerRealm.setCredentialsMatcher(matcher);
    
            //主体认证,得到SecurityManager实例 并绑定给SecurityUtils
            SecurityUtils.setSecurityManager(defaultSecurityManager);
    
            //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
            Subject subject=SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken("quentin","123456");
    
            //登录,即身份验证
            subject.login(usernamePasswordToken);
            System.out.println("isAuth:"+subject.isAuthenticated());
    
            subject.checkRole("admin");
            subject.checkPermission("admin:select");
    
        }
    }

    运行结果:

      

    单独解释下Shiro加密:

    在自定义Realm文件中

    Md5Hash passMD5=new Md5Hash("123456","salt");
    userMap.put("quentin",passMD5.toString());
    //由上面代码也能看到解释,其实就是将带盐"salt"也一起加密,作为身份验证的密码信息
    +
    simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("salt"));  //将盐注册到信息中去
    //将盐"salt"注册到信息中去验证
    =
    这样就更加安全。

    通过上面4个实例,动手敲一敲,相信你们都能快速了解Shiro认证授权原理了。
     
  • 相关阅读:
    IOS UIwebview 背景色调整
    文件的创建 判断是否存在文件 读取 写入
    IOS 关于ipad iphone5s崩溃 解决
    iOS tabbar 控制器基本使用
    iOS 关于流媒体 的初级认识与使用
    总结 IOS 7 内存管理
    iOS 应用首次开启 出现引导页面
    IOS UItableView 滚动到底 触发事件
    IOS 应用中从竖屏模式强制转换为横屏模式
    iOS 定位系统 知识
  • 原文地址:https://www.cnblogs.com/qiujianfeng/p/10274161.html
Copyright © 2011-2022 走看看