shiro入门
1.Shiro的简介
公司项目中,常见的权限框架:shiro | spring security
Apache Shiro是一个功能强大且灵活的开源安全框架,可以清晰地处理身份验证,授权,企业会话管理和加密。
Apache Shiro 的首要目标是易于使用和理解。权限是很复杂的,甚至是痛苦的,但它没有必要这样。框架应该尽可能掩盖( 黑盒 | 空调)复杂的地方,露出一个干净而直观的 API(遥控器),来简化开发人员在使他们的应用程序安全上的努力。
Shiro能帮系统做什么:
1、做用户的身份认证(登录),判断用户是否系统用户(重点)
2、给系统用户授权,用来帮助系统实现不同的用户展示不同的功能(重点)
3、针对密码等敏感信息,进行加密处理(明文变成密文)(重点)
4、提供了Session管理,但是它的Session不是HttpSession,是它自己自带的
5、做授权信息的缓存管理,降低对数据库的授权访问
6、提供测试支持,因为它也是一个轻量级框架,它也可以直接针对代码进行使用Junit单元测试
7、提供Remeber me的功能,可以做用户无需再次登录即可访问某些页面
8、启用单点登录(SSO)功能。
2.Shiro提供的10大功能
-
Authentication 身份认证/登录,验证用户是不是拥有相应的身份;
-
Authorization 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
-
Session Management 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
-
Cryptography 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
-
Web Support Web支持,可以非常容易的集成到Web环境;
-
Caching 缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
-
Concurrency shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
-
Testing 提供测试支持;
-
Run As :让我的用户暂时模拟其他用户权限登录使用;
-
Remember Me 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3.Shiro的架构图
接下来从Shiro内部来看下Shiro的架构,如下图所示:
**Subject主体:**可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager: 相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
**Authenticator认证器:**负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证(登录)通过了;
Authrizer授权器: 或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm域(面试常问): 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
SessionManager: 如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;
接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可
以实现自己的分布式会话(如把数据放到Memcached服务器);
SessionDAO: DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
CacheManager 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography 密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
AuthenticationStrategy 认证策略器: 两个及以上的自定义域 realm存在的时候,才使用这个! 我们这次上课不用!
4.Shiro框架的3个核心类
首先,从外部来看Shiro,即从应用程序角度的来观察如何使用Shiro完成工作。如下图:
Subject主体: 需要登录系统的东西,都是主体。 代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager,可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
SecurityManager安全管理器: 即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与其他组件进行交互,相当于DispatcherServlet前端控制器;
Realm域: 一个用来做身份认证,以及授权的对象 Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,就是一个跟权限数据有关的数据源。
记住一点: Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
5.基于角色权限ini配置文件的开发流程
-
创建maven项目, 在maven项目中, 引入 shiro-core 的核心包, junit 和common-loggin.
-
在resource文件夹下创建2个配置ini文件
[users] #模拟从数据库查询的用户 #数据格式 用户名=密码,角色1,角色2.. mayun=123456,role1,role2 mayuhang=654321,role3 [roles] #模拟从数据库查询的角色和权限列表 #数据格式 角色名=权限1,权限2 role1=user:save,user:update role2=user:update,user.delete role3=user:find
-
在测试包下 test里面, 新建一个类(核心是BasicIniEnvironment剩下全是推理出来的),流程如下:
- 首先通过BasicIniEnvironment环境 ,去获取到我们的ini配置文件(去数据库判断账号密码是否正确, 同时 查询用户所有的角色 以及所有角色对应的权限)
- 通过这个环境对象创建SecurityManager对象
- 使用工具类SecurityUtils.set** 放入刚创建的SecurityManager对象
- SecurityUtils.getSubject 获取主体对象
- subject.login(UsernamePasswordToken …)进行登录认证 注意这个形参 需要用UsernamePasswordToken实现类
- subject.isAuthenticated()来判断该subject主体有没有登录认证成功
- subject.hasRole(String…)来判断是否有某个角色
- subject.isPermitted(String…)来判断有么有某个权限
6.基于realm自定义域ini配置文件的开发流程
1.新建配置文件shiro-realm.ini
后期要被替换掉!Springboot项目中, ShiroConfig.java类, 在这个类中 进行 放入自定义域 创建SecurityManager对象(后期配置类中返回的对象应该是DefaultSecurityManager, 把自定义域放入DefaultSecurityManager对象中 返回给springboot)!
[main]
#声明realm 自定义域YourRealm自定义的
permReam=com.woniuxy.utils.OurRealm
#注册realm到securityManager中
securityManager.realms=$permReam
以后在项目中需要配置这个配置方法 创建一个对象交给spring管理!
<bean class="org.apache.shiro...DefaultSecurityManager">
//setter注入
<property name="realms" ref="ourRealm上面配置好的 域的bean">
</bean>
@Bean
public SecurityManager doDefaultSecurityManager(OurRealm ourRealm){
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealms(ourRealm)
retuen defaultSecurityManager;
}
2.开发我们的自定义域
-
首先要继承AuthorizingRealm。
-
重写2个方法 一个是 doGetAuthenticationInfo 认证 , doGetAuthorizationInfo 授权.
-
认证: doGetAuthenticationInfo ( 需要一个参数) 这个参数 就是 subject.login( 这个参数 );
这参数是 UsernamePasswordToken; 返回对象SimpleAuthenticationInfo(username,password ,myRealmName)
到这里 登录认证就结束。
-
授权: doGetAuthorizationInfo ( 需要一个参数) 这个参数 就是 UsernamePasswordToken中的"校长"; 在什么时候进入我的授权方法方法?? 答案是:需要判断有没有角色 subject.hasRole 以及 判断有没有这个权限, subject.isPermission()。
授权返回值是SimpleAuthorizationInfo,这个对象中 有所有的角色和所有的权限!
-
认证和授权方法里面需要干嘛???? 认证中:需要去数据库查出信息 进行比对,成功就认证成功! 授权中: 通过用户名查出该用户下所有的角色, 和所有的权限!
登录和认证流程是分开的哈, 请看下图:
7.shiro入门程序 含代码
1.搭建基于ini文件的运行环境
pom中导入shiro坐标:(plugins插件仅仅只是为了处理maven和idea兼容导致的启动问题)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.woniuxy</groupId>
<artifactId>shiro01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>2.7</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.shared</groupId>
<artifactId>maven-filtering</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.用户认证 BasicIniEnvironment
认证:身份认证/登录,验证用户是不是拥有相应的身份。基于shiro的认证,是通过subject的login方法完成用户认证工作的
(1)在resource目录下创建shiro的ini配置文件构造模拟数据(shiro-auth.ini)
[users]
#模拟从数据库查询的用户
#数据格式 用户名=密码
mayun=123456
mayuhang=654321
(2)测试用户认证
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.env.BasicIniEnvironment;
import org.apache.shiro.env.Environment;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
/**
* @author: mayuhang <br/>
* Date: 2021/4/29:0:32 <br/>
* Description:
*/
public class TestLogin {
@Test
public void testLogin() throws Exception{
//1.加载ini配置文件创建SecurityManager
Environment environment = new BasicIniEnvironment("classpath:shiro-auth.ini");
//2.获取securityManager
SecurityManager securityManager = environment.getSecurityManager();
//3.将securityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
//4.创建主体(此时的主体还为经过认证)
Subject subject = SecurityUtils.getSubject();
/**
* 模拟登录,和传统登陆了方式等不同的是需要使用主体进行登录
*/
//5.构造主体登录的凭证(即用户名/密码)
//第一个参数:登录用户名,第二个参数:登录密码
UsernamePasswordToken token = new UsernamePasswordToken("mayun","123456");
//6.主体登录
subject.login(token);
//7.验证是否登录成功
System.out.println("用户登录成功, 认证状态:"+subject.isAuthenticated());
//8.登录成功获取数据
//getPrincipal 获取登录成功的安全数据
System.out.println("从subject中获取用户信息:"+subject.getPrincipal());
}
}
打印结果:
3.用户授权
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限
(1)在resource目录下创建shiro的ini配置文件构造模拟数据(shiro-prem.ini)
[users]
#模拟从数据库查询的用户
#数据格式 用户名=密码,角色1,角色2..
mayun=123456,role1,role2
mayuhang=654321,role3
[roles]
#模拟从数据库查询的角色和权限列表
#数据格式 角色名=权限1,权限2
role1=user:save,user:update
role2=user:update,user.delete
role3=user:find
(2)完成用户授权
import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.config.IniSecurityManagerFactory;import org.apache.shiro.env.BasicIniEnvironment;import org.apache.shiro.env.Environment;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.apache.shiro.util.Factory;import org.junit.Test;/** * @author: mayuhang <br/> * Date: 2021/4/29:22:44 <br/> * Description: */public class TestPerm { @Test public void testPerm() throws Exception { //1.加载ini配置文件创建SecurityManager Environment environment = new BasicIniEnvironment("classpath:shiro-auth.ini"); //2.获取securityManager SecurityManager securityManager = environment.getSecurityManager(); //3.将securityManager绑定到当前运行环境 SecurityUtils.setSecurityManager(securityManager); //4.创建主体(此时的主体还为经过认证) Subject subject = SecurityUtils.getSubject(); /** * 模拟登录,和传统等不同的是需要使用主体进行登录 */ //5.构造主体登录的凭证(即用户名/密码) //第一个参数:登录用户名,第二个参数:登录密码 UsernamePasswordToken token = new UsernamePasswordToken("mayuhang", "654321"); //6.主体登录 subject.login(token); //7.用户认证成功之后才可以完成授权工作 boolean hasPerm = subject.isPermitted("user:find"); System.out.println("从subject中获取用户信息:"+subject.getPrincipal()); System.out.println("用户是否具有find权限=" + hasPerm); }}
subject.login(UsernamePasswordToken … token) //执行shiro内置的登录认证
subject.isAuthenticated() //是否认证(登录)成功(成功就是true)
subject.getPrincipal() //获取登录的用户名
subject.hasRole(String … perm) //是否这个角色
subject.isPermitted(String …perm) //是否又这个权限
4.自定义域(重点 面试) 只要记住 继承AuthorizingRealm
Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。
(1)自定义域(Realm)
package com.woniuxy.shiro;import org.apache.shiro.authc.*;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.util.ArrayList;import java.util.List;/** * @author: mayuhang <br/> * Date: 2021/4/29:23:14 <br/> * Description: 自定义域 * 继承AuthorizingRealm * 重写 AuthorizationInfo Z 授权 * AuthenticationInfo C 认证 */public class MyRealm extends AuthorizingRealm { @Override public void setName(String name){ super.setName("myRealm"); } /** * 授权:授权的主要目的就是查询 数据库获取当前用户的所有角色和权限信息 * * 在前后端不分离的情况下, * 在html页面使用<shiro>|后端使用shiro的@checkRole,@checkPermission相关注解才能触发 * 其实前后端分离项目, 这个授权意义就不大了, 不通过controller加注解来控制权限 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // 1.从principals获取已认证用户的信息 类比之前打印的方法 "从subject中获取用户信息:"+subject.getPrincipal(); String username = (String) principals.getPrimaryPrincipal(); /** * 正式系统:应该从数据库中根据用户名或者id查询 所有权限 * 这里为了演示,手动构造 */ // 2.模拟从数据库中查询的用户所有权限 List<String> permissions = new ArrayList<String>(); permissions.add("user:save");// 用户的创建 permissions.add("user:update");// 商品添加权限 // 3.模拟从数据库中查询的用户所有角色 List<String> roles = new ArrayList<String>(); roles.add("role1"); roles.add("role2"); // 4.构造权限数据(就是个实体类^ ^) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); // 5.通过当前查询的权限数据保存到simpleAuthorizationInfo simpleAuthorizationInfo.addStringPermissions(permissions); // 6.通过当前用户,查询的角色数据保存到simpleAuthorizationInfo simpleAuthorizationInfo.addRoles(roles); return simpleAuthorizationInfo; } /** * 身份认证方法:认证的主要目的,比较用户输入的用户名密码是否和数据库中的一致 * * 需要在用户登录系统时触发 * 该方法将是我们主要的方法, 流程是登录后, 携带token 与权限信息 发送到 vue中 存储浏览器 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //1.一个简单的用户名/密码身份验证令牌,以支持使用最广泛的身份验证机制 UsernamePasswordToken upToken = (UsernamePasswordToken)token; //2.获取输入的用户名密码 String username = upToken.getUsername(); String password = new String(upToken.getPassword()); /** * 3.验证用户名密码是否正确 * 正式系统:应该从数据库中通过用户查询密码(一般会加密), 比较密码是否一致 * 此处测试,只要输入的密码为123456则登录成功 */ if(!password.equals("123456")) { throw new RuntimeException("用户名或密码错误");//抛出异常表示认证失败 }else { SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName()); return info; } }}
(2)配置shiro的ini配置文件(shiro-realm.ini)
[main]#声明realmpermReam=com.woniuxy.shiro.MyRealm#注册realm到securityManager中securityManager.realms=$permReam
(3)测试验证流程
import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.UsernamePasswordToken;import org.apache.shiro.env.BasicIniEnvironment;import org.apache.shiro.env.Environment;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.subject.Subject;import org.junit.Test;/** * @author: mayuhang <br/> * Date: 2021/4/29:23:43 <br/> * Description: */public class TestRealm { @Test public void testPerm() throws Exception { //1.加载ini配置文件创建SecurityManager Environment environment = new BasicIniEnvironment("classpath:shiro-realm.ini"); //2.获取securityManager SecurityManager securityManager = environment.getSecurityManager(); //3.将securityManager绑定到当前运行环境 SecurityUtils.setSecurityManager(securityManager); //4.创建主体(此时的主体还为经过认证) Subject subject = SecurityUtils.getSubject(); /** * 模拟登录,和传统等不同的是需要使用主体进行登录 */ //5.构造主体登录的凭证(即用户名/密码) //第一个参数:登录用户名,第二个参数:登录密码 UsernamePasswordToken upToken = new UsernamePasswordToken("mayuhang", "123456"); //6.主体登录 subject.login(upToken); //7.用户认证成功之后才可以完成授权工作 //认证 是否登录成功 System.out.println("判断"+subject.getPrincipal()+"是否认证成功"+subject.isAuthenticated()); //是否有这个角色 或者 权限 System.out.println("判断是否有role1的角色"+subject.hasRole("role1")); System.out.println("判断是否有user:insert的权限"+subject.isPermitted("user:insert")); System.out.println("判断是否有user:add的权限"+subject.isPermitted("user:add")); }}
我们通过自定义realm实现了认证! 不再通过 ini文件写死账号密码来认证! 自定义域 也就是以后我们在项目开发过程中, 用的方式!
课上代码:
package com.woniuxy.shiro;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.crypto.hash.SimpleHash;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;import java.util.HashSet;import java.util.Set;/** * @author: mayuhang <br/> * Date: 2021/4/30:15:21 <br/> * Description: 自定义域 这个是给 单一用户 进行 认证和授权的! * 认证的数据放什么实体中: SimpleAuthenticationInfo * 授权的数据(角色和权限)放什么实体中: SimpleAuthorizationInfo * 这个两个对象去了哪里呢?? * SecurityManager 它管理了我们单一用户的认证授权信息 以及 所有的 subject */public class MyRealm extends AuthorizingRealm{ @Override public String getName() { return "MyRealm"; } //授权 授予登录的这个角色和权限! @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //通过形参 获取用户名 String username = (String) principals.getPrimaryPrincipal(); /* *通过用户名 去数据库查询 所有的角色 和 权限 * *先去数据库查询当前用户 所有的角色 * *再去通过 所有的角色 查询出所有的权限 * */ Set<String> roles = new HashSet(); roles.add("role1"); roles.add("role2"); Set<String> perms = new HashSet(); perms.add("user:add"); perms.add("user:delete"); perms.add("user:update"); perms.add("user:find"); perms.add("user:find"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRoles(roles); simpleAuthorizationInfo.addStringPermissions(perms); return simpleAuthorizationInfo; } //认证 确定你的用户名 密码正确 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //自定义实现 登录验证 UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; //我在自定义认证方法中,可以通过 入参 拿到我的账号和密码 String username = usernamePasswordToken.getUsername(); String password =new String(usernamePasswordToken.getPassword()); //1.用账号去数据库查询出你的密码! 密码一般加密的 //2.把当前变量的 password用同样的方式 加密, 然后 和数据库查出的密码进行比对! //3.比对两个密码! 我们一般使用的是非对称加密!(找回密码!忘记密码! 一般验证账号正确, 重置密码!) if(password.equals("123456")){ return new SimpleAuthenticationInfo(username,password,getName()); } return null; }}