一、基本概念及架构
1. 什么是shiro?
shiro是一个基于java的开源的安全管理框架,可以完成认证,授权,会话管理,加密,缓存等功能。
2.为什么学习shiro?
在java世界中,安全管理框架有spring security 和shiro ,spring security要依赖于spring并且比较复杂,学习曲线比较高。shiro比较简单。
3. Shiro能做什么呢?
验证用户身份 用户访问权限控制,比如:
- 判断用户是否分配了一定的安全角色。
- 判断用户是否被授予完成某个操作的权限
- 在非 web 或 EJB 容器的环境下可以任意使用Session API
- 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
- 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
- 支持单点登录(SSO)功能
- 支持提供“Remember Me”服务,获取用户关联信息而无需登录
4. Shiro的架构图
Apache Shiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:
Authentication
(认证), Authorization
(授权),Session Management
(会话管理), Cryptography
(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石
- Authentication(认证):用户身份识别,通常被称为用户“登录”
- Authorization(授权):访问控制。比如某个用户是否具有某个操作资源的使用权限。
- Session Management(会话管理):特定于用户的会话管理,甚至可以用在在非web 或 EJB 应 用程序。
- Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
并发:Apache Shiro 支持多线程应用程序的并发特性。
测试:支持单元测试和集成测试,确保代码和预想的一样安全。
"Run As":这个功能允许用户假设另一个用户的身份(在许可的前提下)。
“Remember Me”:跨 session 记录用户的身份,只有在强制需要时才需要登录。
5.Shiro三大组件 Subject、SecurityManager、Realms
5.1 Subject
Subject: 访问系统的用户,主体可以是用户、程序等,凡是通过shiro进行认证的都被称作为主体;
Subject一词是一个安全术语,其基本意思是“当前的操作用户” , 他是一个抽象的概念,可以是人,也可以是第三方的进程或者其他类似事务。比如爬虫、机器人等。 我们可以再程序的任意位置使用
Subject currentUser = SecurityUtils.getSubject()
获取shiro的Subject主体对象。一旦获得Subject,你就可以立即获得你希望用Shiro为当前用户做的90%的事情,比如登陆、登出、访问会话、执行授权检查等。
5.2 SecurityManager
这个组件充当一个安全管理器,他是shiro功能实现的核心,负责与后边介绍的其他组件(认证器/授权器/缓存控制器)进行交互,实现subject委托的各种功能。有点类似于springmvc的dispatchServlet前端控制器,将不同的请求委托到不同的处理方法。
5.3 Realms
这个组件充当了Shiro与应用安全数据间的“桥梁”或者”连接器“ , 可以把Realm看成DataSource,即安全数据源,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找相关的比例数据,以确认用户是否合法,操作是否合理
5.4 三个核心组件的大致关系
其中Subject持有页面传入的用户输入的数据信息。而Realms类似于数据库,记录着用户的信息在数据库中。利用SecurityManager来管理这两者,它可以去判断认证,Subject传入的信息是否在realms中有相应的权限。
二、Shiro第一次登录登出功能实战:
下图是这次实战的大致的登陆流程图:
实验步骤:
- 第一步: 创建一个smaven项目并导入shiro所需要的依赖
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-aspectj -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-aspectj</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-cas -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-cas</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-guice -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-guice</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-quartz -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro.tools/shiro-tools-hasher -->
<dependency>
<groupId>org.apache.shiro.tools</groupId>
<artifactId>shiro-tools-hasher</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
- 第二步:在resources目录下创建一个
shiro.ini
文件并加入下面的内容,用于模拟数据库数据列表。
#模拟数据库的用户列表,格式对应的是 账号=密码
[users] #注意事users,不是user
lisi=9999
luohaizhang=123456
2368521029@qq.com=1111
- 实验用例
public class ShiroTest {
@Test
public void testForShiro(){
//生成SecurityManager的工厂,加载shiro.ini中的配置。
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager instance = factory.getInstance();
//保存一个SecurityManager ,使得全局可以引用。
SecurityUtils.setSecurityManager(instance);
Subject subject = SecurityUtils.getSubject();
//模拟用户名和密码
AuthenticationToken authenticationToken = new UsernamePasswordToken("luohaizhang","123456");
//模拟用户登录,会去shiro.ini中对比
subject.login(authenticationToken);
//查看是否登录成功
System.out.println("用户登入后是否存在授权:"+subject.isAuthenticated());
//用户登出
subject.logout();
System.out.println("用户登出后,是否存在授权:"+subject.isAuthenticated());
}
}
运行上面的用例可能出现两种情况:
1.如果你填写了一个realms中不存在的账户会抛出
UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@3f0ee7cb] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - luoha2izhang, rememberMe=false].
2.如果你的密码出了错会抛出:
org.apache.shiro.authc.IncorrectCredentialsException
我们对上面这两种可能出现的异常可以进行捕获并告知前天显示相应的提示信息,将我们的测试用例升级如下:
try {
//模拟用户名和密码
AuthenticationToken authenticationToken = new UsernamePasswordToken("luohaiz2hang", "1223456");
//模拟用户登录,会去shiro.ini中对比
subject.login(authenticationToken);
}
catch (UnknownAccountException e){
System.out.println(e.getMessage());
System.out.println("找不到账号");
}
catch (IncorrectCredentialsException e){
System.out.println(e.getMessage());
System.out.println("密码错误");
}