shiro
概述
shior的话,在第一次听说的时候单纯的任务它就是一个安全框架,可以对访问接口的用户进行验证等工作,类似拦截器或过滤器的东西,但是在学习后,发现远远不止这些,它的灵活性和易用性让我震惊,这么好的东西早就应该发现的呀!!!
特性
既然说了它这么好,让我们来看看它到底好在哪里:
易于使用:使用 Shiro 构建系统安全框架非常简单。就算第一次接触也可以快速掌握。
全面:Shiro 包含系统安全框架需要的功能,满足安全需求的“一站式服务”。
灵活:Shiro 可以在任何应用程序环境中工作。虽然它可以在 Web、EJB 和 IoC 环境 中工作,但不需要依赖它们。Shiro 也没有强制要求任何规范,甚至没有很多依赖项。
强力支持 Web:Shiro 具有出色的 Web 应用程序支持,可以基于应用程序 URL 和 Web 协议(例如 REST)创建灵活的安全策略,同时还提供一组 JSP 库来控制页面输出。
兼容性强:Shiro 的设计模式使其易于与其他框架和应用程序集成。Shiro 与 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架无缝集成。
看那么多字是不是都很恶心,总结起来就一个字,好用!
spirng security
其实提到安全框架的话,还有一个耳熟能详的框架,就是spring系列的spring security,那这俩有什么区别呢?
- spring security是基于spring的,对spring有一定的依赖性,不过当前的话,大部分互联网项目都是基于spring的,所以这个也不能算缺点
- spring security功能比shiro更丰富一些,例如安全维护方面
- spring security的使用比起shiro来较为复杂
学习方式
在我看来学习任何东西只要掌握其原理和运行流程,其实编码也就好说了,最多也就看看源码之类的东西,知道哪个类是干嘛的,都🆗的,而学习这些东西最好的地方就是官网啦,这里是shiro的官网,里面有更加详尽的介绍及使用,如果有什么问题,里面应该都有解决方案
learning
基本功能及简介
下面就来看看shiro的基本功能吧,以图的方式来展示(摘自apache shiro官网):
功能介绍:
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限
- Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
- Web Support:Web 支持,可以非常容易的集成到 Web 环境
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率
- Concurrency:Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
架构概述
在最高概念层面上,Shiro的架构有3个主要概念:Subject、SecurityManager和Realms。下图是这些组件如何交互的高级概述(摘自apache shiro官网):
组件介绍:
Subject:这里通常指与软件交互的任何东西,通常指用户
SecurityManager:shiro架构的核心,所有和安全相关的操作都会与之交互,它管理所有的subject,类似于springmvc中的DispatcherServlet
Realms:充当的是shiro和应用程序安全数据之间的
桥梁
,当需要实际与安全相关的数据(如用户帐户)进行交互以执行身份验证(登录)和授权(访问控制)时,Shiro 会从为应用程序配置的一个或多个 Realm 中查找其中的许多内容简单来说,可以把Realm看作是一个一个的特定的dao,它封装了数据源的连接细节,并根据需要提供数据给shiro,可以配置多个,但是至少提供一个Realm
详细架构
下图显示了 Shiro 的核心架构概念以及每个概念的简短摘要(摘自apache shiro官网):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-moiBv4d1-1669008965948)(assets/ShiroArchitecture.png)]
- Subject( org.apache.shiro.subject.Subject ) 当前与软件交互的实体(用户、第 3 方服务、cron 作业等)的特定于安全的“视图”。
- SecurityManager ( org.apache.shiro.mgt.SecurityManager ) 主要是用来协调其他组件顺利工作的
- Authenticator ( org.apache.shiro.authc.Authenticator ) 负责Subject认证,可以自定义实现
- Authentication Strategy ( org.apache.shiro.authc.pam.AuthenticationStrategy )是身份认证策略,当定义了多个Realm,它就可以去使用指定的策略去进行认证
- Authorizer ( org.apache.shiro.authz.Authorizer )负责访问控制,是最终决定是否允许用户做某事的机制
- SessionManager ( org.apache.shiro.session.mgt.SessionManager )负责管理session生命周期,不仅可以用在web环境
- SessionDAO ( org.apache.shiro.session.mgt.eis.SessionDAO ) 负责代表SessionManager执行增删改查操作,这允许任何数据插入会话管理基础结构
- CacheManager ( org.apache.shiro.cache.CacheManager )负责创建和管理其他shiro组件使用的缓存实例的生命周期
- Cryptography ( org.apache.shiro.crypto.* )的中文释义是密码学,很自然的可以想到,安全框架肯定会包含加密模块,在数据方面进行加密
- Realm( org.apache.shiro.realm.Realm )就没什么好说的了,和上面的意思一样,充当的是shiro和应用程序安全数据之间的
桥梁
术语
身份:验证身份是验证用户身份的过程
授权:也称为访问控制,是确定用户是否被允许做某件事的过程
凭证:是验证用户身份的一条信息,只有用户自己知道
权限:至少在 Shiro 的解释中,权限是一种描述应用程序中原始功能的声明,仅此而已。权限是安全策略中最低级别的结构。它们仅定义应用程序可以做什么。他们没有描述“谁”能够执行这些操作。许可只是一种行为声明,仅此而已。
主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只 有授权 后才允许访问相应的资源。
角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。
基本使用
登录认证
流程:
- 收集用户身份和凭证,即用户名和密码
- 调用subject.login进行登录,如果失败则报异常AuthenticationException
- 创建自定义的Realm类,继承org.apache.shiro.realm.AuthenticatingRealm类,实现doGetAuthenticationInfo()方法
身份认证
流程:
- 首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
- SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证
- Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现
- Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证
- Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序 及策略进行访问
角色授权
流程:
- 首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager,而 SecurityManager接着会委托给 Authorizer
- Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会让PermissionResolver把字符串转换成相应的Permission实例
- 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted/hasRole 会返回 true,否则返回false表示授权失败
实例
- pom文件
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
shiro获取权限相关信息可以通过数据库获取,也可以通过shiro.ini文件获取,此处使用后者实现基本演示
- shiro.ini
# 提供了对根对象 securityManager 及其依赖的配置
[main]
md5CredentialsMatcher = org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations = 3
myrealm = gyl.top.MyRealm
myrealm.credentialsMatcher = $md5CredentialsMatcher
securityManager.realms = $myrealm
# 提供了对用户/密码及其角色的配置,用户名=密码,角色 1,角色 2
[users]
zhangsan = 7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi = l4
# 提供了角色及权限之间关系的配置,角色=权限 1,权限 2
[roles]
role1 = user:insert,user:select
# 这里还有一种叫urls,不过这里并未配置,它主要用来对url拦截相关的配置,
# 想更多了解shiro.ini配置文件
# 可以参考https://blog.csdn.net/u011781521/article/details/74892074
- 自定义的Realm:这里其实使用了shiro加密的认证方式,在下面的小节中会提到
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
/**
* @author GYL
* @version V1.0
*/
public class MyRealm extends AuthenticatingRealm {
/**
* 自定义登录认证方法,shiro的login方法底层会调用该类的认证方法进行认证
* 需要配置自定义的realm生效,在ini文件中可以配置,或者在springboot中进行配置
* 该方法只是获取进行对比的信息,认证逻辑还是安装shiro底层认证逻辑完成
*
* @param authenticationToken :获取登录信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取身份信息
String principal = authenticationToken.getPrincipal().toString();
// 获取凭证信息
String password = authenticationToken.getCredentials().toString();
System.out.println("认证信息:" + principal);
System.out.println("凭证信息:" + password);
// 访问数据库,获取数据库中存储的数据信息
if ("zhangsan".equals(principal)) {
// 数据库中存储着加盐三次迭代的密码
String passwordInfo = "7174f64b13022acd3c56e2781e098a5f";
// 创建封装校验逻辑的对象,把数据封装进去,然后返回
return new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),
passwordInfo,
ByteSource.Util.bytes("salt"),
principal
);
}
return null;
}
}
- 测试类
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
/**
* @author GYL
* @version V1.0
*/
public class ShiroTest {
public static void main(String[] args) {
// 初始化获取SecurityManager
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager manager = factory.getInstance();
SecurityUtils.setSecurityManager(manager);
// 获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 创建token对象,web应用用户名密码从页面传递
AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z3");
// 调用login方法进行登录认证
try {
subject.login(token);
System.out.println("登录成功");
if (subject.hasRole("role1")) {
System.out.println("有相关角色");
}
if (subject.isPermitted("user:insert")) {
System.out.println("有相关权限");
}
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户不存在");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密码错误");
} catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
}
shiro加密
在开发中,常会对一些敏感数据进行加密,比如用户的密码等信息
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
/**
* @author GYL
* @version V1.0
*/
public class ShiroMd5 {
public static void main(String[] args) {
// 密码明文:
String password = "z3";
// 使用md5加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println(md5Hash);
// 带盐的md5加密,盐就是在密码明文后拼接新字符串,然后再进行加密
Md5Hash md5Hash1 = new Md5Hash(password, "salt");
System.out.println(md5Hash1);
// 为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
Md5Hash md5Hash2 = new Md5Hash(password, "salt", 3);
System.out.println(md5Hash2);
// 使用父类实现加密
SimpleHash simpleHash = new SimpleHash("MD5", password, "salt", 3);
System.out.println(simpleHash);
}
}
- 有关shiro的基本介绍和使用就到这里,本文章参考了尚硅谷shiro讲解及shiro官方文档
每一点滴的进展,都是缓慢而艰苦的