一、Shiro介绍
Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大灵活的开源安全框架,提供认证、授权、会话管理以及密码加密等功能,由Java语言开发。
Shiro可以做到:
- 验证用户身份
- 对用户进行访问控制(配置文件中配置拦截器,对请求地址进行拦截)
- 判断某个用户是否被赋予某个特定角色
- 判断某个用户是否被允许执行某些操作
- 可以在各种环境下使用Session API,即使不在web或EJB容器中
- 对认证、访问控制火灾会话生命周期中的事件进行响应处理
二、Shiro功能
2.1Apache Shiro 框架提供了很多功能,下图展示了Shiro的着重点
Shiro 主要面向 Shiro开发团队所谓的“应用安全的四大基础”——认证,授权,会话管理与密码加密
- 认证:或“登录”,用以验证用户身份。
- 授权:访问控制,比如决定谁可以访问某些资源
- 会话管理:管理用户相关的Session,即使是在非Web或EJB应用中。
- 加密:可以非常方便地使用(各种算法)加密算法来保证数据的安全。
2.2 Shiro中的几个重要概念
Shiro 的框架主要有三个*概念:Subject,SecurityManager 和 Realms
Subject:实际是安全邻域里的“当前执行用户”。Subject 可以是一个人,或者是第三方服务,守护进程账户,定时任务等等——可以是基本上任何与软件交互的事务。Subject的实例都会(也是必须)绑定一个 SecurityManager,对 Subject的操作会转换为 Subject 与 SecurityManager的交互。
SecurityManager:Shiro架构核心,协调内部组件运作。配置完成后,开发者只需要使用 Subject。
Realm:在 Shiro 和你的安全数据之间扮演“桥梁”或“连接器”的角色。用于用户账号的认证和授权。
*进行认证或授权时,Shiro会从应用配置的一个或多个Realms中来查找。这个意义上Realm其实就是安全(操作)特有的DAO:它封装了数据源的细节,并在Shiro需要时提供相关数据。配置Shiro时,必须至少得有一个Realm以用于认证(和/或)授权。在SecurityManager中可以配置Realm,至少得有一个。Shiro自带一些开箱即用的Realms用于连接像是LDAP,关系数据库(JDBC),文本配置源(比如INI)以及属性文件等等这样的安全数据来源。如果默认的不能满足你的需要,你也可以自己实现一个Realm。
2.3 Shiro 的核心架构
Subject:安全视角下与软件交互的实体(用户、第三方等等)
SecurityManager:安全管理器 协调内部各组件的作用;同时管理 Shiro 的所有用户,对用户进行相关的安全操作
Authenticator:认证器 结合Realm完成认证操作
Authorizer:授权器 结合Realm完成授权操作
CacheManager:缓存管理器 缓存权限数据
Realm:域
SessionManager:会话管理器 管理Session
CrypotoGraphy:加密 用于密码加密
三、Shiro 认证
3.1即身份验证:
在 Shiro 中,用户需要提供 principals 和 Credentials 给 Shiro 进行验证。
principals :身份(用户名/手机号等)
Credentials :证明/凭证(密码)
认证需要用到两个相关的概念是 Subject 及 Realm,分别是主体及验证主体的数据源。
【认证流程】
主体调用Subject.login()该方法后,由Realm查询数据源进行校验,把校验的结果给
Authenticator,由认证器分析完成认证操作:
如果身份错误,抛出UnknownAccountException;
如果凭证错误,抛出IncorrectCredentialException
认证时 Realm 有三种方式:下面的 Realm 都是测试,未查询数据库,用 ini 文件配置代替数据源
- 默认 Realm 无法支持加密功能
- 自定义 Realm
- 密码加密,凭证匹配器
3.2 环境搭建(用于JavaEE工程,进行测试;Maven-Web工程则只需要导入Shiro相关的依赖进行了,包含了这些依赖)
<%--在pomp文件中导入依赖--%>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>provided</scope>
</dependency>
3.3 默认 Realm 无需创建Realm类
此处使用 ini 配置文件,通过 [users] 指定了两个主体
配置文件 shiro-authenticate.ini 需要在 idea 中下载该插件(类似油桶,下载后要重启软件)
[users]
zhangsan=123
lisi=lisi
测试代码
//测试默认的Realm完成认证
@Test
public void test01(){
//读取配置文件,生成工厂对象
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:autenticate/shiro-authenticate.ini");
//从工厂中取出SecurityManager:单例
SecurityManager securityManager = factory.getInstance();
//把securityManager设置环境变量中 等同于把securityManager放入到了一个工具类中,方便获取securityManager中
//各个组件对象的
SecurityUtils.setSecurityManager(securityManager);
//获取Sebject主体
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1111");
try {
subject.login(token);
} catch (UnknownAccountException e) {
System.out.println("身份错误");
}catch (IncorrectCredentialsException e1){
System.out.println("凭证错误");
}
}
3.4自定义 Realm
自定义 Realm 需要实现或者继承框架中的 realm 类
配置文件 shiro-realm.ini
#声明一个realm,获取Realm路径
myRealm= com.bjpowernode.shiro.MyRealm
#指定securityManager的realms实现,即将Realm注入到安全管理器里
securityManager.realms=$myRealm
自定义Realm:
自定义Realm认证流程:先校验身份, 身份没有问题后,再校验凭证
/**
* AuthenticatingRealm:认证的Realm
* AuthorizingRealm:授权的Realm
*/
public class MyRealm extends AuthorizingRealm {
//认证的功能
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//先校验身份
//获取用户输入的身份
String principal = (String) authenticationToken.getPrincipal();
//拿着用户输入的身份,去数据库查询,过程.... where username=principal
String correctPrincipal = "abc";
if(correctPrincipal == null){
throw new UnknownAccountException();
}
//再校验凭证
//从查询出来的身份中取出凭证
String credential = "admin";
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo(principal, credential, "myRealm");
return simpleAuthenticationInfo;
}
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
测试代码
//自定义的Realm来完成认证
@Test
public void test02(){
//读取配置文件,生成工厂对象
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:autenticate/shiro-realm.ini");
//从工厂中取出SecurityManager:单例
SecurityManager securityManager = factory.getInstance();
//把securityManager设置环境变量中 等同于把securityManager放入到了一个工具类中,方便获取securityManager中
//各个组件对象的
SecurityUtils.setSecurityManager(securityManager);
//获取Sebject主体
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","admin");
subject.login(token);
//判断用户是否认证成功
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);
}
3.5自定义 realm,加密完成验证
散列算法
一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如 MD5、SHA 等。一般进行散列时最好提供一个 salt(盐),比如加密密码 “admin”,产生的散列值是 21232f297a57a5a743894a0e4a801fc3”,可以到一些 md5 解密网站很容易的通过散列值得到密码 “admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和 ID(即盐);这样散列的对象是 “密码 + 用户名 +ID”,这样生成的散列值相对来说更难破解。
配置文件 shiro-md5.ini
#凭证匹配器 整合Spring框架,实际就是个bean对象
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法 加密的方法MD5
credentialsMatcher.hashAlgorithmName=md5
#散列次数 即加密次数
credentialsMatcher.hashIterations=1
#声明一个realm
md5Realm= com.bjpowernode.shiro.Md5Reaml
#将散列设置到当前reaml中 把凭证匹配器注入到Realm中
md5Realm.credentialsMatcher=$credentialsMatcher
#指定securityManager的realms实现
securityManager.realms=$md5Realm
自定义Realm
public class Md5Realm extends AuthorizingRealm {
//认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//先校验身份
//获取用户输入的身份
String principal = (String) authenticationToken.getPrincipal();
//拿着用户输入的身份,去数据库查询,过程.... where username=principal
String correctPrincipal = "";
if(correctPrincipal == null){
throw new UnknownAccountException();
}
//再校验凭证
//从查询出来的身份中取出凭证,加密之后的密码
String credential = "f6fdffe48c908deb0f4c3bd36c032e72";
String salt = "admin";
SimpleAuthenticationInfo simpleAuthenticationInfo =
new SimpleAuthenticationInfo
(principal, credential, ByteSource.Util.bytes(salt),"myRealm");
return simpleAuthenticationInfo;
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
测试代码
//自定义的Realm来完成加密认证
@Test
public void test04(){
//读取配置文件,生成工厂对象
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:autenticate/shiro-md5.ini");
//从工厂中取出SecurityManager:单例
SecurityManager securityManager = factory.getInstance();
//把securityManager设置环境变量中 等同于把securityManager放入到了一个工具类中,方便获取securityManager中
//各个组件对象的
SecurityUtils.setSecurityManager(securityManager);
//获取Sebject主体
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","123");
subject.login(token);
//判断用户是否认证成功
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);
}
四、Shiro 授权
4.1授权概念
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
- 主体 主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
- 资源 在应用中用户可以访问的URL,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
- 权限 安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面 查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)打印文档等。如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)
- 粗粒度:小红以部门经理登陆到系统中,能操作用户模块的所有资源
4.2授权的三种方式
1、编程式:通过写 If/else 授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
2、注解式:通过在执行的 Java 方法上放置相应的注解完成
@RequiresPermissions("admin")
public void hello() {
//有权限
}
3、JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
编码式:
授权代码:
//授权
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//先根据用户的身份查询用户的所有权限
Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
//查询数据库省略 List<Permission>
// List<String> permissions = new ArrayList<String>();
//添加权限标识符
// permissions.add("user:*");
//定义一个集合,存储用户对应的角色
List<String> roles = new ArrayList<String>();
roles.add("role1");
roles.add("role2");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRoles(roles);
return simpleAuthorizationInfo;
}
测试类
@Test
public void test01(){
Factory<SecurityManager> factory =
new IniSecurityManagerFactory("classpath:authorize/shiro-authorize.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan","123" );
subject.login(usernamePasswordToken);
boolean authenticated = subject.isAuthenticated();
System.out.println(authenticated);
// subject.checkPermissions("user:query");
boolean has = subject.hasRole("role3");
System.out.println(has);
}