Shiro学习01:使用Shiro实现身份管理和权限验证

Shiro的基本概念

  1. Shiro的基本功能:

    在这里插入图片描述

    • Authentication: 身份验证,即登录
    • Authorization: 权限验证,即授权
    • Cryptography: 加密,将密码以密文形式存储进数据库
  2. Shiro的三个基本概念: Subject,SecurityManager,Realms

    • Subject表示当前操作的用户,可以通过Subject currentUser = SecurityUtils.getSubject()获得当前Subject.
    • SecurityManager安全控制器,是实现Shiro功能的核心,与其他组件进行交互,实现Subject委托的各种功能,类似于Spring MVC中的DispatcherServlet.
    • Realms安全数据源,Shiro从Realm获取安全数据(如用户,角色,权限).
  3. Shiro结构:

    在这里插入图片描述

    • Subject: 当前登录的用户
    • SecurityManager: 安全控制器,Shiro的核心,它管理着所有Subject,且负责进行认证和授权,及会话,缓存的管理
    • Authenticator: 认证器,负责用户登录
    • Authorizer: 授权器,控制用户能访问哪些功能
    • Realm: 安全数据源,用于获取安全实体,一般在应用中我们需要自己实现Realm.

Shiro入门实例1: 通过ini配置文件实现身份验证

项目准备

新建MAVEN项目,在pom.xml中导入如下依赖:

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

使用默认Realm组件

在不进行配置的情况下,Shiro会使用从ini文件中读取的数据创建数据源.

  1. main/resources目录下创建shiro.ini文件如下,用来构建Realm数据源

    [users]
    # 模拟用户数据库列表: 账号=密码
    user1 = password1
    user2 = password2
    
  2. 编写代码测试登录功能

    @Test
    public void TestLogin() {
        // 通过ini配置文件创建SecurityManager工厂对象
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // 通过工厂对象生产SecurityManager对象
        SecurityManager securityManager = factory.getInstance();
        // 将SecurityManager绑定到当前运行环境中
        SecurityUtils.setSecurityManager(securityManager);
        
        // 创建当前的登陆主体
        Subject currentUser = SecurityUtils.getSubject();
        // 模拟收集当前登录主体的身份凭证
        UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");
        
        // 尝试登陆账号
        try {
            currentUser.login(token);
            // 可以通过当前Subject对象的isAuthenticated()方法判断当前登录状态
            System.out.println("当前登录状态" + currentUser.isAuthenticated());
        } catch (UnknownAccountException uae) {
            System.out.println("username wasn't in the system");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("password didn't match");
        } catch (LockedAccountException lae) {
            System.out.println("account for that username is locked");
        }
    
        // 退出账号
        currentUser.logout();
        System.out.println("当前登录状态" + currentUser.isAuthenticated());
    }
    

    输出:

    当前登录状态true
    当前登录状态false
    

若认证操作失败,系统可能抛出如下三种常见的异常:

  • UnknownAccountException: 账号不存在
  • IncorrectCredentialsException: 账号存在但密码错误
  • LockedAccountException: 账户被锁定

观察源代码,发现程序的执行过程如下

在这里插入图片描述

使用自定义Realm

  1. 实现我们自定义的Realm类,继承自AuthorizingRealm.

    [外链图片转存失败(img-TZxVWmQI-1565533563049)(1565340862010.png)]

    package cn.maoritian.realm;
    
    public class MyRealm extends AuthorizingRealm {
    
        // 使用该方法返回值区分不同Realm
        public String getRealm() {
            return "MyRealm";
        }
    
        // doGetAuthorizationInfo()进行授权操作
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        // doGetAuthenticationInfo()进行认证操作
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            // 从token中获取用户名
            String username = (String) token.getPrincipal();
    
            // 模拟根据用户名在数据库中查询密码
            String password;
            if (!"user1".equals(username)) {
                return null;
            } else {
                password = "password1";
            }
    
            // 返回登录比对信息
            AuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
            return info;
        }
    }
    
  2. 编写shiro.ini配置文件,指定使用我们自定义的Realm

    [main]
    # 自定义realm
    myRealm =cn.maoritian.realm.MyRealm
    # 指定SecurityManager的realms实现
    securityManager.realms=$myRealm
    
  3. 编写代码测试登录功能,测试代码与上个测试代码完全相同

    @Test
    public void TestLoginMyRealm() {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
    
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");
    
        try {
            currentUser.login(token);
            System.out.println("当前登录状态" + currentUser.isAuthenticated());
        } catch (UnknownAccountException uae) {
            System.out.println("username wasn't in the system");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("password didn't match");
        } catch (LockedAccountException lae) {
            System.out.println("account for that username is locked");
        }
    
        currentUser.logout();
        System.out.println("当前登录状态" + currentUser.isAuthenticated());
    }
    

使用密文存储密码

在实际项目中,我们都是将密码以加盐且多次迭代的方式存储在数据库中,使用shiro内置的方法,我们可以完成对明文的加密.

String password = "password";       // 明文
Md5Hash md5Hash = null;             // 密文

// 直接对明文进行1次md5加密
md5Hash = new Md5Hash(password);
System.out.println(md5Hash);		// 5f4dcc3b5aa765d61d8327deb882cf99    

// 对明文加盐进行1次md5加密
md5Hash = new Md5Hash(password, "username"); 
System.out.println(md5Hash);		// d51c9a7e9353746a6020f9602d452929

// 对明文加盐进行3次md5加密
md5Hash = new Md5Hash(password, "username", 3);
System.out.println(md5Hash);		// c69d335dc6b83db4ca13ee576672ddf7

下面我们测试在自定义的Realm中使用密文进行登陆验证

  1. 编写shiro.ini配置文件,配置匹配凭证器

    [main]
    # 定义凭证匹配器
    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    # 定义散列算法
    credentialsMatcher.hashAlgorithmName=md5
    # 定义散列迭代次数
    credentialsMatcher.hashIterations=3
    
    # 自定义realm
    myRealm=cn.maoritian.realm.MyRealm
    # 指定SecurityManager的realms实现
    securityManager.realms=$myRealm
    
  2. 配置自定义Realm,模拟从数据库中查询密文密码,注意此时的密文密码应为原密码不加盐迭代三次的结果

    package cn.maoritian.realm;
      
    public class MyRealm extends AuthorizingRealm {
    
       // 使用该方法返回值区分不同Realm
       public String getRealm() {
           return "MyRealm";
       }
       
       // doGetAuthorizationInfo()进行授权操作
       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
           return null;
       }
       
       // doGetAuthenticationInfo()进行认证操作
       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
       
           // 从token中获取用户名
           String username = (String) token.getPrincipal();
       
           // 模拟根据用户名在数据库中查询密码
           String password;
           if (!"user1".equals(username)) {
               return null;
           } else {
               // 此时返回的密文密码应为明文密码"password"不加盐迭代三次的结果
               password = "918f43923202882e53f05b3f02cb68f1";
           }
       
           // 返回登录比对信息
           AuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
           return info;
       }
    }
    
  3. 编写代码测试登录功能,测试代码与上面测试代码完全相同

    @Test
    public void TestLoginWithCredential() {
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
    
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");
    
        try {
            currentUser.login(token);
            System.out.println("当前登录状态" + currentUser.isAuthenticated());
        } catch (UnknownAccountException uae) {
            System.out.println("username wasn't in the system");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("password didn't match");
        } catch (LockedAccountException lae) {
            System.out.println("account for that username is locked");
        }
    
        currentUser.logout();
        System.out.println("当前登录状态" + currentUser.isAuthenticated());
    }
    

Shiro入门实例2: 通过ini配置文件实现权限管理

BRAC权限管理

BRAC为基于角色的权限管理,有如下三个概念:

  • user: 用户,指当前操作用户
  • role: 角色,是权限的集合
  • permission: 权限,对资源的操作许可

角色和权限的对应关系可以使用权限表达式来表示,其形式为资源:操作:实例,示例如下:

权限表达式意义
user:createuser:create:*对用户资源的创建权限
user:update:001对用户实例001的修改权限
user:*:001对用户实例001的所有权限

使用ini配置文件为用户授权

  1. 编写shiro.ini文件,为用户配置权限

    [users]
    # 模拟用户数据库列表: 账号=密码,权限
    user1=password1,role1,role2
    user2=password2,role3
    
    [roles]
    # 角色role1对资源user具有所有权限
    role1=user:*
    # 角色role2对资源user具有create,delete权限
    role2=user:create,user:delete
    # 角色role3对资源user具有create权限
    role3=user:create
    
  2. 编写测试代码如下:

    @Test
    public void TestRoles() {
        // 使用user1登录系统
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");
        currentUser.login(token);
    
    	
        // 判断当前用户是否拥有某角色
        System.out.println(currentUser.hasRole("role1"));
        // 判断当前用户是否拥有列表中所有角色
        System.out.println(currentUser.hasAllRoles(Arrays.asList("role1", "role2")));
        // 以数组形式返回当前用户是否拥有某角色,此方法可以减少hasRole()方法的调用次数
        System.out.println(Arrays.toString(currentUser.hasRoles(Arrays.asList("role1", "role2", "role3"))));
    
    	// 若当前用户没有某角色则报UnauthorizedException异常
        currentUser.checkRole("role1");
    	// 若当前用户没有所有角色则报UnauthorizedException异常
        currentUser.checkRoles("role1", "role2", "role3");
    }
    
    @Test
    public void TestPermissions() {
        // 使用user1登录系统
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        Subject currentUser = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken("user1", "password1");
        currentUser.login(token);
    
    	// 判断当前用户是否拥有某权限
        System.out.println(currentUser.isPermitted("user:delete"));
        // 判断当前用户是否拥有列表中所有权限
        System.out.println(currentUser.isPermittedAll("user:delete", "user:list"));
        // 以数组形式返回当前用户是否拥有某权限,此方法可以减少isPermitted()方法的调用次数
    	System.out.println(Arrays.toString(currentUser.isPermitted("user:delete", "user:list")));
    }
    

使用自定义Realm为用户授权

  1. 实现自定义Realm,重写其doGetAuthorizationInfo()方法

    public class MyRealm extends AuthorizingRealm {
    
        public String getRealm() {
            return "MyRealm";
        }
    
        // doGetAuthorizationInfo()进行授权操作
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            // 获取用户名,用于查询角色权限信息
            String username = (String) principals.getPrimaryPrincipal();
    
            // 模拟根据用户名到数据库中查询角色和权限信息
            List<String> roles = new ArrayList<String>();		//角色集合
            List<String> permissions = new ArrayList<String>();	//权限集合
            roles.add("role1");
            permissions.add("user:*");
    
            // 将角色和权限信息封装进infoAuthorizationInfo对象
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            info.addRoles(roles);
            info.addStringPermissions(permissions);
    
            return info;
        }
    
        // doGetAuthenticationInfo()进行认证操作
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    		// 从数据库中查询所有用户名
        }
    }
    
  2. 测试代码与上面完全相同,略

Shiro权限管理的执行流程

在这里插入图片描述

当我们调用Subject对象的isPermitted()hasRole()方法,会被委托给SecurityManager,进而被委托给Authorizer.Authorizer在进行授权之前,会调用PermissionResolver将字符串转化为相应的Permission实例.若有多个Realm,会委托给ModularRealmAuthorizer进行循环判断.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值