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

  • Shiro的基本概念
  • Shiro入门实例1: 通过ini配置文件实现身份验证
    • 项目准备
    • 使用默认Realm组件
    • 使用自定义Realm
    • 使用密文存储密码
  • Shiro入门实例2: 通过ini配置文件实现权限管理
    • BRAC权限管理
    • 使用ini配置文件为用户授权
    • 使用自定义Realm为用户授权
    • 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 {// 使用该方法返回值区分不同Realmpublic 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 {// 使用该方法返回值区分不同Realmpublic 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进行循环判断.

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

  1. shiro学习系列:shiro自定义filter过滤器

    shiro学习系列:shiro自定义filter过滤器 自定义JwtFilter的hierarchy(层次体系) 上代码 package com.finn.springboot.common.conf ...

  2. shiro学习(1):shiro简介

    Apache Shiro是Java的一个安全框架.对比另一个安全框架Spring Sercurity,它更简单和灵活. Shiro可以帮助我们完成:认证.授权.加密.会话管理.Web集成.缓存等. A ...

  3. Shiro学习笔记四(Shiro集成WEB)

    这两天由于家里出了点事情,没有准时的进行学习.今天补上之前的笔记 -----没有学不会的技术,只有不停找借口的人 学习到的知识点: 1.Shiro 集成WEB 2.基于角色的权限控制 3.基于权限的控 ...

  4. shiro学习(8):shiro连接数据库 三

    工具idea 先看看数据库 shiro_role_permission 数据 shiro_user shiro_user_role 数据 我们先看一下目录结构 首先 log4j.properties ...

  5. Shiro学习(24)在线回话管理

    有时候需要显示当前在线人数.当前在线用户,有时候可能需要强制某个用户下线等:此时就需要获取相应的在线用户并进行一些操作. 本章基于<第十六章 综合实例>代码构建. 会话控制器 Java代码 ...

  6. shiro学习(4):shiro认证流程

    Shiro登录校验流程实现与分析 什么是Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.使用Shiro的易于理解的API,您可以快速.轻松地 ...

  7. shiro学习(7):shiro连接数据库 方式二

    工具idea 先看看数据库 shiro_role_permission 数据 shiro_user shiro_user_role 数据 我们先看一下目录结构 首先 jar包引入 pom.xml文件 ...

  8. shiro学习(6):shiro连接数据库

    首先我们先看一下数据库 再看看数据库的测试数据 在我们创建好的maven项目中看一下目录结构 在pom.xml引入 <dependency><groupId>com.mchan ...

  9. linux用户没有创建文件的权限设置密码,Linux学习第五章用户身份与文件权限

    一.用户身份与能力 Linux系统中一共有三种用户 第一种:管理员  root  UID =0 第二种:系统用户 不需要登录系统  负责单一服务的运行  UID = 0-1000 第三种:普通用户 日 ...

  10. SpringBoot整合Shiro学习(上)

    SpringBoot整合Shiro(上) 基于[编程不良人]2020最新版Shiro教程,整合SpringBoot项目实战教程 哔哩哔哩链接:https://www.bilibili.com/vide ...

最新文章

  1. oracle 越南字符,ORACLE 12.2RAC之问题 ora.chad OFFLINE
  2. 命令行的基本使用方法(权限)
  3. Ubuntu中启用 ThinkPad指纹识别
  4. XSuperTooltip - Office 2007 Super Tooltip class
  5. U3D assetbundle加载
  6. Discuz! $_DCACHE数组变量覆盖漏洞
  7. Python基础--Python3基础语法
  8. mysql创建存储时覆盖_总结到位的MySQL 的覆盖索引与回表
  9. 工作225:当前导致name报错
  10. python常用开放工具_python学习笔记16-各种模块和开放工具收集整理
  11. AutoFac文档9(转载)
  12. python and or 详解
  13. 最长双重叠字符串java_java – 重复但重叠的字符串的算法
  14. 自编Python机器人,内置词库可改写。
  15. 计算机专业实习报告-5000字+,以及计算机专业实习周记-15篇
  16. 形式语言与自动机 第五章 课后题答案
  17. Redis单机版和集群搭建部署
  18. 快速傅里叶变换(FFT)学习
  19. ajax data=text,jQuery ajax dataType值为text json探索分享
  20. iOS - 解决Warning: Attempt to present which is already presenting

热门文章

  1. linux命令行下的BT软件
  2. python 获取网页视频
  3. Android手机解网络锁软件,GalaxSIM解锁工具 GalaxSim Unlock
  4. 搜狐公司董事局主席兼首席执行官——张朝阳名言4
  5. PHP留言并展示_php留言簿功能实现
  6. 【原创】JS 数字转换成英文写法(包含小数)
  7. dingo php,Laravel Lumen RESTFul API 扩展包:Dingo API(一) —— 安装配置篇
  8. git rebase
  9. 微信小程序的注册流程
  10. [996]如何申请高德地图用户Key