一、Shiro介绍
1、百科对shiro的定义如下:
Apache Shiro 一个强大且易于使用的 Java 安全框架,它提供了身份验证、授权、加密和会话管理等功能。Shiro 的设计目标是简化企业级应用程序的安全性开发过程,同时保持代码的简洁和易于维护。
2、Shiro 核心功能介绍
2.1、Authentication(认证)
认证和授权是Shiro最核心的功能,认证操作主要用来处理2方便的工作,即:
1)验证用户身份,如:用户名密码、OAuth、LDAP等。
LDAP(轻量级目录访问协议)种在互联网上标准的数据协议,用于访问和维护分布式
目录信息服务。在许多企业和组织中,LDAP被用来存储用户认证信息,如用户名、密
码、电子邮件地址等,用于实现LDAP统一用户认证和单点登录
2)支持多 Realm(数据源),如数据库、LDAP、Active Directory 等。
2.2、Authorization(授权)
授权主要用来做 :
1)基于角色(Role)和权限(Permission)的资源控制访问
2)支持细粒度的权限控制,如:user:create、file:edit等
2.3、Session Management(会话管理)
会话管理主要用来做:
1)提供会话管理,即使没有 Web 容器(如 Tomcat)也能使用。
2)支持分布式会话,如:Redis缓存。
2.4、Cryptography(加密)
1)Shiro加密模块提供哈希(如 MD5、SHA)、加解密(AES、DES)等工具。
2)Shiro支持密码加盐(Salt)和多次哈希迭代
2.5、Web Integration(Web集成)
1)提供 ShiroFilter 拦截请求,实现 URL 级别的安全控制
2)支持 RESTful API 安全认证(如 JWT 集成)
2.6、Cache(缓存)
1)Shiro 支持缓存认证和授权信息,提高性能(如 Redis、Ehcache)。
3、Shiro 核心角色和架构:
3.1、Shiro 核心架构图如下:
3.2、Shiro 核心角色如下:
1)Subject
表示当前“用户”,Subject 用户并不是单单指登录程序的user;与程序交互的任何主体都是
Subject用户,如:网络爬虫、user、机器设备等等。
所有的Subject 用户都由 SecurityManager管理,Subject由当前线程绑定(通过
ThreadLocal实现),即Subject 是线程安全的;
Shiro 是所有安全操作的入口;在开发中通常通过Shiro的工具类SecurityUtils.getSubject()
来获取Subject,如下图所示:
2)SecurityManager
SecurityManager 是Shrio 的核心,管理所有的安全操作,协调所有安全组件,
如:认证、授权、会话管理。
在Shiro 中 SecurityManager 的重要实现有2个,即:
1)DefaultSecurityManager 默认实现
2)SessionsSecurityManager 支持会话管理
3)Authenticator
认证器,用于Subject用户的认证逻辑;
Shiro 默认实现是 ModularRealmAuthenticator
认证流程:
(1)用户提交凭证(如 UsernamePasswordToken)。
(2)Authenticator 调用 Realm 的 doGetAuthenticationInfo() 方法。
(3)匹配凭证与 Realm 返回的数据(如密码加盐哈希比较)。
4)Authorizer
授权器,用于判断认证通过的用户是否具有某些资源的访问权限的过程;
Shiro 支持基于角色和权限的资源访问控制。
Shiro 的默认实现是 ModularRealmAuthorizer
5)SessionManager
管理Session 生命周期的组件,是Shiro 自带的session会话管理器,这就表示即使在非
Web环境中也可以使用Shiro进行认证和授权。
Shiro中 SessionManager 的核心实现如下:
(1)DefaultSessionManager(默认实现)
(2)ServletContainerSessionManager(委托给 Servlet 容器,如 Tomcat)
(3)EnterpriseCacheSessionManager(支持分布式缓存,如 Redis)
6)CacheManager
该组件用于缓存认证、授权、角色、会话等的数据,提升性能。
Shiro中默认的实现有:
(1)MemoryConstrainedCacheManager(内存缓存)
(2)EhCacheManager(集成 Ehcache)
(3)RedisCacheManager(将数据缓存到Redis,分布式缓存)
7)Realm
Realm,安全数据源, 是连接Shiro 与 安全数据的桥梁(如:JDBC数据库数据、LADP、
内存数据);用于获取安全数据进行认证、授权;
Realm 可以有多个,由用户自己提供;一般应用中需要用户定义自己的Realm;
Shiro默认提供了3个Realm 实现,即:
1)IniRealm:基于 .ini文件的配置,从Shiro.ini读取用户 认证/授权 和 角色数据
2)JdbcRealm:通过JDBC查询数据库中的安全数据(认证/授权 相关的数据)
3)TextConfigurationRealm:安全数据配置在内存中,用于替代 IniRealm
8)Filter
Web过滤器,在 Web 环境中拦截请求,实现 URL 级别的安全控制
二、Shiro 简单使用
下边分别以 SimpleAccountRealm、IniRealm、JdbcRealm、自定义Realm 分别来演示下Shiro
认证和授权的基本流程。
1)认证流程
2)授权流程
1、SimpleAccountRealm
SimpleAccountRealm是Shiro 提供的一个最基本的 Realm 内存实现,适用于快速原型开发
或测试场景。它直接在内存储存用户、角色和权限信息,无需连接数据库或外部配置。
示例代码如下:
@Test
public void authen(){
/**
* 认证需要准备3个角色
* 1)认证得发起者(一般叫Subject)
* 2)SecurityManager
* 3)Realm
* 认证发起者得能够找到 SecurityManager,SecurityManager得能够找到 Realm,所以这三者需要存在依赖关系
*
* 认证流程如下:
*/
//1、准备 Realm
SimpleAccountRealm realm = new SimpleAccountRealm();
//添加用户
//模拟拿到用户信息:用户名称、密码、角色信息
realm.addAccount("admin","123456","超级管理员","商家");
//2、准备 SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//3、SecurityManager 与 Realm 建立连接
securityManager.setRealm(realm);
//4、Subject 与 SecurityManager 建立链接
SecurityUtils.setSecurityManager(securityManager);
//5、准备 Subject
Subject subject = SecurityUtils.getSubject();
//6、发起认证,认证失败抛出异常 AuthenticationException,认证通过不抛出异常
subject.login(new UsernamePasswordToken("admin","123456"));
//7、判断是否认证成功
System.out.println(subject.isAuthenticated());
//8、判断用户角色
boolean b1 = subject.hasRole("超级管理员");//判断是否有超级管理员角色
//使用 checkRole/checkRoles判断角色时,若角色不存在,则会抛出异常AuthorizationException
// SimpleAccountRealm只支持角色的授权
System.out.println("是否拥有超级管理员角色:" +b1);
subject.checkRole("商家");
//9、退出校验
subject.logout();
}
2、IniRealm
IniRealm 是基于文件存储的授权认证,IniRealm 可以将用户的用户名、密码、角色信息存储
到.ini文件中,来实现持久化;所示使用IniRealm 需要先准备一个.ini文件,如:Shiro.ini;
Shiro.ini 文件内容如下:
示例代码如下:
@Test
public void authen(){
//1、构建 Realm
IniRealm realm = new IniRealm("classpath:shiro.ini");
//2、构建 SecurityManager 并绑定Realm
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
//3、基于 SecurityUtils 绑定 SecurityManager 并声明认证主题(即认证发起者)Subject
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//4、执行认证操作
subject.login(new UsernamePasswordToken("admin","123456"));
//5、角色校验
boolean b1 = subject.hasRole("超级管理员");//判断是否有超级管理员角色
System.out.println(b1);
//6、权限校验
//权限校验失败会抛出异常
subject.checkPermission("user:add");
subject.checkPermission("user:select");
}
3、JdbcRealm
JdbcRealm 是把认证授权的数据保存到数据库中,所以基于JdbcRealm的认证授权需要连接
数据库,示例代码如下:
@Test
public void authen(){
//1、构建Realm
JdbcRealm realm = new JdbcRealm();
//设置数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://47.100.7.152:3306/shiro");
dataSource.setUsername("javademo");
dataSource.setPassword("123qwe!@#");
realm.setDataSource(dataSource);
//开启权限校验,JdbcRealm 默认是不能进行权限校验的
realm.setPermissionsLookupEnabled(true);
/**
* JdbcRealm 自定义 认证查询、权限查询、角色查询sql
*/
//认证查询sql
//realm.setAuthenticationQuery(sql);
//权限查询sql
//realm.setPermissionsQuery(sql);
//角色查询sql
//realm.setUserRolesQuery(sql);
//2、构建 SecurityManager,并绑定Realm
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(realm);
//3、并基于SecurityUtil 绑定SecurityManager,并声明主题(认证者)Subject
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
//4、执行认证操作
subject.login(new UsernamePasswordToken("admin","123456"));
//5. 授权操作(角色)
System.out.println(subject.hasRole("超级管1理员"));
//6. 授权操作(权限)
System.out.println(subject.isPermitted("user:add"));
}
4、CustomRealm
CustomRealm是一个自定义的Realm;
自定义Realm 一般需要继承 AuthorizingRealm 并重写方法doGetAuthenticationInfo(授权)与
方法doGetAuthenticationInfo(认证)
示例代码如下:
/****************************************************
* 模拟JdbcRealm 自定义 Realm(即 CustomRealm) 来完成认证和授权
* 自定义 CustomRealm 需要继承 AuthorizingRealm 并实现 AuthorizingRealm 的2个
* 核心方法:doGetAuthorizationInfo 和 doGetAuthenticationInfo
*
* @author lbf
* @date
****************************************************/
public class CustomRealm extends AuthorizingRealm {
/**
* 授权
* todo 注意:
* 授权是在认证之后的操作,授权方法需要用到认证方法返回的 AuthenticationInfo 中的用户信息
*
* @param principals 即 doGetAuthenticationInfo 方法返回的 AuthenticationInfo 中的用户信息(这里是User )
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1. 获取认证用户的信息
User user = (User) principals.getPrimaryPrincipal();
//2. 基于用户信息获取当前用户拥有的角色。
Set<String> roleSet = this.findRolesByUser();
//3. 基于用户拥有的角色查询权限信息
Set<String> permSet = this.findPermsByRoleSet(roleSet);
//4. 声明AuthorizationInfo对象作为返回值,传入角色信息和权限信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roleSet);
info.setStringPermissions(permSet);
//5. 返回
return info;
}
private Set<String> findPermsByRoleSet(Set<String> roleSet) {
Set<String> set = new HashSet<>();
set.add("user:add");
set.add("user:update");
return set;
}
private Set<String> findRolesByUser() {
Set<String> set = new HashSet<>();
set.add("超级管理员");
set.add("运营");
return set;
}
/**
* 认证 用户执行认证操作传入的用户名和密码
* 只需要完成用户名校验即可,密码校验由Shiro内部完成
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1、获取用户名称
String userName = (String) token.getPrincipal();
//2、判断用户名称是否为空
if(StringUtils.isEmpty(userName)){
// 返回null,会默认抛出一个异常,org.apache.shiro.authc.UnknownAccountException
return null;
}
//4、如果用户名称不为空,则基于用户名称去查询用户信息
//这一步一般是自己的UserService 服务
//模拟查询用户信息
User user = this.findUserByUsername(userName);
if(user == null){
return null;
}
//5、构建 AuthenticationInfo 对象,并填充用户信息
/**
* todo 注意:
* SimpleAuthenticationInfo 第一个参数是用户信息,第二个参数是用户密码,第三个参数是Realm名称(这个参数没有意义)
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),"CustomRealm!!!");
//返回 AuthenticationInfo 对象
return info;
}
// 模拟数据库操作
private User findUserByUsername(String username) {
if("admin".equals(username)){
User user = new User();
user.setId(1);
user.setUsername("admin");
user.setPassword("admin");
return user;
}
return null;
}
}
public class User {
private Integer id;
private String username;
private String password;
}