基本概念
- Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能,对于任何一个应用程序,Shiro都可以提供全面的安全管理服务
- 权限管理包括用户身份认证和授权两个部分,简称认证授权
- 身份认证 : 为判断用户是否为合法用户的过程,最常用的身份认证方式就是系统通过核对用户输入的用户名和口令,将其与系统中存储的该用户信息相对比,继而来判断用户身份是否正确
- 授权 : 既访问权限,控制用户访问资源的权限. 主体(subject)进行身份认证后需要分配权限方可访问系统的资源
架构
- Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境. Shiro可以帮助我们完成 : 认证、授权、加密、会话管理、与Web集成、缓存等
- Authentication : 身份认证 / 登录,验证用户是不是拥有相应的身份
- Authorization : 授权,即权限验证,验证某个已认证的用户是否拥有某个权限. 即判断用户是否能做事情,常见的如 : 验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限
- Session Manager : 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中. 会话可以是普通JavaSE环境的,也可以是如Web环境的
- SessionDAO : DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的 SessionDAO,通过如JDBC写到数据库. 比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO. 另外SessionDAO中可以使用Cache进行缓存,以提高性能
- Cryptography : 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
- CacheManager : 缓存控制器,来管理如用户、角色、权限等的缓存的. 因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
- Web Support : Web支持,可以非常容易的集成到Web环境中
- Caching : 缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率
- Concurrency : shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去
- Testing : 提供测试支持
- Run As : 允许一个用户假装为另一个用户(在他们允许的情况下)的身份进行访问
- Remember Me : 记住我,这个是非常常见的功能,即一次登录后,下次不用重复登录了
- shiro API含义:
- Subject : 主体,代表了当前”用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,即为一个抽象概念. 所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager. 可以把Subject认为是一个门面,SecurityManager才是实际的执行者
- SecurityManager : 安全管理器,即所有与安全有关的操作都会与SecurityManager交互,且它管理着所有Subject. 可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成
DispatcherServlet
前端控制器 - Realm : 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法,也需要从Realm得到用户相应的角色 / 权限进行验证用户是否能进行操作. 可以把Realm看成 DataSource,即安全数据源。注意 : Shiro不知道你的用户 / 权限存储在哪及以何种格式存储,所以我们一般在应用中都需要实现自己的Realm
-
Shiro不提供维护用户 / 权限,而是通过Realm让开发人员自己注入内部结构
- 最简单的一个Shiro应用可以基本分为以下两个步骤 :
- 应用代码通过Subject来进行认证和授权,而Subject又将所有的互交都委托给了SecurityManager
- 我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断
- Authenticator : 认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现. 其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了
- Authrizer : 授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能
登录登出
- 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,继而来验证用户的身份信息,最常见的princpals和 credentials组合就是用户名 / 密码
- principals : 身份,即主体的标识属性,如用户名、邮箱等,需唯一. 一个主体可以有多个principals,但只有一个Primary principals,一般是用户名 / 手机号
- credentials : 证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等
案例
- maven依赖
<dependencies>
<!--Junit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Shiro核心包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- slf4j的接口实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
</dependencies>
- shiro文件:shiro.ini(存储用户身份信息(账户=密码))相当于从数据库中查询出的账号密码
[users]
root=yubuntu0109
- 测试
/**
* Shiro认证测试:验证用户登录信息
*/
public class ShiroTest {
@Test
public void testLogin() {
//1:加载配置文件,创建SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2:获得securityManager实例对象
SecurityManager securityManager = factory.getInstance();
//3:将securityManger实例绑定到当前运行环境中,便于访问
SecurityUtils.setSecurityManager(securityManager);
//4:创建当前登录的主体
Subject currentUser = SecurityUtils.getSubject();
//5:绑定主体登录的身份/凭证,既账户及密码(相当于从前台form表单获取到的账号密码)
UsernamePasswordToken token = new UsernamePasswordToken("root", "yubuntu0109");
//6:主体登录
try {
currentUser.login(token);
System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
} catch (UnknownAccountException e) {
System.err.println("用户账户信息错误 !");
} catch (IncorrectCredentialsException e) {
System.err.println("用户密码信息错误 !");
} catch (AuthenticationException e) {
e.printStackTrace();
}
//8:注销登录
currentUser.logout();
System.out.println("身份身份是否注销成功 :" + !currentUser.isAuthenticated());
}
}
源码流程
- 1:调用
subject.login(AuthenticationToken token)
方法进行用户登录,其会自动委托给securityManager.login(Subject subject, AuthenticationToken token)
方法进行登录 - 2:
securityManager
(安全管理器)通过Authenticator
(认证器)进行认证 - 3:
Authenticator
的实现类ModularRealmAuthenticator
通过调用realm
从shiro.ini
配置文件中获取用户真实的信息(账户和密码),这里的Realm(域)可以看成DataSource,即安全数据源 - 4:
IniRealm
(可通过加载.ini
文件生成reaml对象)先根据token
中的账号去shiro.ini
配置文件中去匹配该账号,如果找不到则ModularRealmAuthenticator
返回null,如果找到则继续匹配密码,若匹配成功则认证通过,反之不通过 - 5:最后可以使用
Subject.logout()
进行退出操作
自定义realm
- Realm : 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法. 也需要从Realm得到用户相应的角色 / 权限进行验证用户是否能进行操作,可以把Realm看成 DataSource,即安全数据源. 如我们之前的ini配置方式使用的是
org.apache.shiro.realm.text.IniRealm
接口
//org.apache.shiro.realm.Realm接口
//返回一个唯一的Realm名字
String getName();
//判断此Realm是否支持此Token
boolean supports(AuthenticationToken token);
//根据Token获取认证信息
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
案例
- 依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Shiro核心包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- slf4j的接口实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<!-- java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
- 信息文件:shiro.ini
#自定义realm
myRealm = com.xxx.MyRealm
#指定SecurityManager的realms实现
securityManager.realm = $myRealm
- 自定义realm:MyRealm
public class MyRealm extends AuthorizingRealm {
@Override
public String getName() {
return "myRealm"; //区分不同的Realm
}
@Override
//授权操作
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
//认证操作:其token存储着传入的用户登录信息(usernamePasswordToken)
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取并验证用户账号信息(前端传入)
String username = (String) token.getPrincipal();
//与数据库中的数据进行比较
if (!"root".equals(username)) {
return null;
}
//假设数据库中用户密码信息
String password = "yubuntu0109";
//SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password, getName());
return info;
}
}
- 测试
/**
* 测试自定义realm
*/
public class loginByMyRealm {
public static void main(String[] args) {
//1:加载配置文件,创建SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:my-shiro.ini");
//2:获得securityManager实例对象
SecurityManager securityManager = factory.getInstance();
//3:将securityManger实例绑定到当前运行环境中,便于访问
SecurityUtils.setSecurityManager(securityManager);
//4:创建当前登录的主体
Subject currentUser = SecurityUtils.getSubject();
//5:绑定主体登录的身份/凭证,既账户及密码
UsernamePasswordToken token = new UsernamePasswordToken("root", "yubuntu0109");
//6:主体登录
try {
currentUser.login(token);
System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
} catch (UnknownAccountException e) {
System.err.println("用户账户错误 !");
} catch (IncorrectCredentialsException e) {
System.err.println("用户密码错误 !");
} catch (AuthenticationException e) {
e.printStackTrace();
}
//8:注销登录
currentUser.logout();
System.out.println("身份身份是否注销成功 :" + !currentUser.isAuthenticated());
}
}
realm密码加密
- 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等.
- 通过使用MD5加密自定义Realm,
[main]
#定义凭证匹配器
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName = md5
#散列次数
credentialsMatcher.hashIterations = 10
#将凭证匹配器设置到realm
myRealm = com.xxx.EncryRealm
myRealm.credentialsMatcher = $credentialsMatcher
securityManager.realms = $myRealm
/**
*加密realm
*/
public class EncryRealm extends AuthorizingRealm {
@Override
public String getName() {
return "myRealm"; //区分不同的Realm
}
@Override
//授权操作
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
@Override
//认证操作
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户账户信息
String username = token.getPrincipal().toString();
if (!"root".equals(username)) {
return null;
}
//MD5加密后的用户密码信息
String password = "f3005f7acf36cec973498845460b0c33";//password + username + 10
//指定盐值:ByteSource.Util.bytes(username)
return new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(username), getName());
}
}
用户授权
- 授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等). 在授权中需了解的几个关键对象 : 主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
- 主体 : 主体,即访问应用的用户,在Shiro中使用Subject代表该用户. 用户只有授权后才允许访问相应的资源
- 资源 : 在应用中用户可以访问的任何资源,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等..用户需要授权后方可访问
- 权限 : 安全策略中的原子授权单位,可用权限控制用户在应用中是否能访问某个资源,如访问用户列表页面,查看/新增/修改/删除用户数据(基本为CRUD式权限控制)
- 角色 : 角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,不同的角色拥有一组不同的权限
- 隐式角色 : 即直接通过角色来验证用户有没有操作权限,即粒度是以角色为单位进行访问控制的,粒度较粗. 若进行变更可能需要多处代码的修改
- 显示角色 : 在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合. 这样若需要哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除指定的访问权限即可,无须修改多处
授权方式
- 编程式 : 通过写if/else授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("admin")) {
//有权限
} else {
//无权限
}
- 注解式 : 通过在执行的Java方法上放置相应的注解完成
@RequiresRoles("admin")
public void hello() {
//有权限
}
- JSP/GSP 标签 : 在JSP/GSP页面通过相应的标签完成
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
授权流程
- 首先调用
Subject.isPermitted/hasRole
接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer - Authorizer是真正的授权者,如果我们调用如
isPermitted(“user:create”)
,其首先会通过PermissionResolver把字符串转换成相应的Permission实例 - 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限
- Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,则会委托给ModularRealmAuthorizer进行循环判断,如果匹配如
isPermitted/hasRole
会返回true,否则返回false以表示授权失败
检查角色
- 依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- Shiro核心包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- slf4j的接口实现 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<!-- java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
- 配置信息:shiro.ini
#权限表达式的定义:首先根据用户名查找角色,再根据角色查找权限,角色是权限的集合
[users]
#用户hunagyuhui的密码为loveyourself,且具有student和programmer两个角色
huangyuhui = loveyourself,student,programmer
[roles]
#角色student对资源'user'拥有create,update权限
student = user:create,user:update
#角色programmer对资源'user'read,delete权限
programmer = user:read,user:delete
- 测试
public class RolesTest {
public static void main(String[] args) {
//1:加载配置文件,创建SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:per-shiro.ini");
//2:获得securityManager实例对象
SecurityManager securityManager = factory.getInstance();
//3:将securityManger实例绑定到当前运行环境中,便于访问
SecurityUtils.setSecurityManager(securityManager);
//4:创建当前登录的主体
Subject currentUser = SecurityUtils.getSubject();
//5:绑定主体登录的身份/凭证,既账户及密码
UsernamePasswordToken token = new UsernamePasswordToken("huangyuhui", "loveyourself");
//6:主体登录
try {
currentUser.login(token);
System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
//7:进行用户角色判断
System.out.println("判断当前登录用户是否拥有'student'角色 : " + currentUser.hasRole("student"));
System.out.println("判断当前登录用户是否同时拥有'student'与'programmer'角色 : " + currentUser.hasAllRoles(List.of("student", "programmer")));
System.out.println("判断当前登录用户是否拥有'student','programmer','singer'角色 : " + Arrays.toString(currentUser.hasRoles(List.of("student", "programmer", "singer"))));
//判断当前用户是否拥有某个角色,若拥有该角色则不做任何操作(无返回值),反之则抛出:UnauthorizedException
currentUser.checkRole("singer");
} catch (UnknownAccountException e) {
System.err.println("用户账户错误 !");
} catch (IncorrectCredentialsException e) {
System.err.println("用户密码错误 !");
} catch (UnauthorizedException e) {
System.err.println("用户并不拥有'singer'角色 !");
} catch (AuthenticationException e) {
e.printStackTrace();
}
//8:注销登录
currentUser.logout();
System.out.println("用户信息是否注销成功 :" + !currentUser.isAuthenticated());
}
}
/*
// ······
用户身份是否验证成功 :true
// ······
判断当前登录用户是否拥有'student'角色 : true
// ······
判断当前登录用户是否同时拥有'student'与'programmer'角色 : true
// ······
判断当前登录用户是否拥有'student','programmer','singer'角色 : [true, true, false]
// ······
用户并不拥有'singer'角色 !
// ······
用户信息是否注销成功 :true
*/
检查权限
-
依赖和配置同检查角色
-
测试
public class PermissionsTest {
public static void main(String[] args) {
//1:加载配置文件,创建SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:per-shiro.ini");
//2:获得securityManager实例对象
SecurityManager securityManager = factory.getInstance();
//3:将securityManger实例绑定到当前运行环境中,便于访问
SecurityUtils.setSecurityManager(securityManager);
//4:创建当前登录的主体
Subject currentUser = SecurityUtils.getSubject();
//5:绑定主体登录的身份/凭证,既账户及密码
UsernamePasswordToken token = new UsernamePasswordToken("huangyuhui", "loveyourself");
//6:主体登录
try {
currentUser.login(token);
System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
//7:进行用户权限判断
System.out.println("当前用户是否拥有对资源'user'的'create'与'read'权限 : " + Arrays.toString(currentUser.isPermitted("user:create", "user:read")));
System.out.println("当前用户是否拥有对资源'user'的'update'与'delete'权限 : " + Arrays.toString(currentUser.isPermitted("user:update", "user:delete")));
System.out.println("当前用户是否拥有对资源'user'的'import'与'export'权限 : " + Arrays.toString(currentUser.isPermitted("user:import", "user:export")));
//判断当前用户是否拥有某个权限,若有则不做任何操作(无返回值),反之抛出:UnauthorizedException
currentUser.checkPermission("user:getUserList");
} catch (UnknownAccountException e) {
System.err.println("用户账户错误 !");
} catch (IncorrectCredentialsException e) {
System.err.println("用户密码错误 !");
} catch (UnauthorizedException e) {
System.err.println("当前用户并未拥有对资源'user'的'getUserList'权限 !");
} catch (AuthenticationException e) {
e.printStackTrace();
}
//8:注销登录
currentUser.logout();
System.out.println("用户信息是否注销成功 :" + !currentUser.isAuthenticated());
}
}
/*
// ······
用户身份是否验证成功 :true
// ······
当前用户是否拥有对资源'user'的'create'与'read'权限 : [true, true]
// ······
当前用户是否拥有对资源'user'的'update'与'delete'权限 : [true, true]
// ······
当前用户是否拥有对资源'user'的'import'与'export'权限 : [false, false]
// ······
当前用户并未拥有对资源'user'的'getUserList'权限 !
// ······
用户信息是否注销成功 :true
*/
- 自定义Realm检查用户拥有的权限
#指定自定义realm
myRealm = com.xxx.PermissRealm
#指定SecurityManager的realms实现
securityManager.realm = $myRealm
public class PermissRealm extends AuthorizingRealm {
@Override
public String getName() {
return "permissRealm";
}
@Override
//授权:其principals存储着用户认证的凭证信息
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取当前登录的用户名信息,既用户凭证
//Object username = principals.getPrimaryPrincipal();
//模拟查询数据库操作:查询用户所拥有的的角色及权限信息
List<String> roles = new ArrayList<>();
List<String> permiss = new ArrayList<>();
//假设该用户拥有'student'与'programmer'角色
roles.add("student");
roles.add("programmer");
//假设该用户拥有对资源'user'的'read'与'update'权限
permiss.add("user:create");
permiss.add("user:update");
//封装用户的角色与权限信息并返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRoles(roles);
info.addStringPermissions(permiss);
return info;
}
@Override
//认证:其token存储着传入的用户身份信息(usernamePasswordToken)
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的账户信息并验证
String username = (String) token.getPrincipal();
if (!"github".equals(username)) {
return null;
}
//模拟用户密码信息
String password = "yubuntu0109";
//SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
return new SimpleAuthenticationInfo(username, password, getName());
}
}
public class PermissTest {
public static void main(String[] args) {
//1:加载配置文件,创建SecurityManager工厂对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:my-per-shiro.ini");
//2:获得securityManager实例对象
SecurityManager securityManager = factory.getInstance();
//3:将securityManger实例绑定到当前运行环境中,便于访问
SecurityUtils.setSecurityManager(securityManager);
//4:创建当前登录的主体
Subject currentUser = SecurityUtils.getSubject();
//5:绑定主体登录的身份/凭证,既账户及密码
UsernamePasswordToken token = new UsernamePasswordToken("github", "yubuntu0109");
//6:主体登录
try {
currentUser.login(token);
System.out.println("用户身份是否验证成功 :" + currentUser.isAuthenticated());
//7:验证用户角色
System.out.println("判断当前登录用户是否拥有'student','programmer','singer'角色 : "
+ Arrays.toString(currentUser.hasRoles(List.of("student", "programmer", "singer"))));
//8:验证用户权限
System.out.println("当前用户是否拥有对'user'资源的'create','update','read','delete'权限 : "
+ Arrays.toString(currentUser.isPermitted("user:create", "user:update", "user:read", "user:delete")));
} catch (UnknownAccountException e) {
System.err.println("用户账户错误 !");
} catch (IncorrectCredentialsException e) {
System.err.println("用户密码错误 !");
} catch (AuthenticationException e) {
e.printStackTrace();
}
//8:注销登录
currentUser.logout();
System.out.println("用户信息是否注销成功 :" + !currentUser.isAuthenticated());
}
}
/*
// ······
用户身份是否验证成功 :true
// ······
判断当前登录用户是否拥有'student','programmer','singer'角色 : [true, true, false]
// ······
当前用户是否拥有对'user'资源的'create','update','read','delete'权限 : [true, true, false, false]
// ······
用户信息是否注销成功 :true
*/
权限注解
-
一般把注解加在controller层的方法上进行权限控制
- @RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即Subject. isAuthenticated() 返回true
- @RequiresUser:表示当前Subject 已经身份验证或者通过记住我登录的
- @RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份
- @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前Subject 需要角色admin 和user
- @RequiresPermissions(value={“user:a”, “user:b”}, logical= Logical.OR):表示当前Subject 需要权限user:a或user:b
拦截器
- Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的.更多默认拦截器可以参考
org.apache.shiro.web.filter.mgt.DefaultFilter
中的枚举拦截器
过滤器简称 | 对应的java类 |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
-
anon : 匿名拦截器,即不需要登录即可访问. 一般用于静态资源过滤,示例”/static/**=anon”
-
authc : 表示需要认证(登录)才能使用,示例”/**=authc”,主要属性有 :
- usernameParam : 表单提交的用户名参数名(username)
- passwordParam : 表单提交的密码参数名(password)
- rememberMeParam : 表单提交的密码参数名(rememberMe)
- loginUrl : 登录请求地址(/login.jsp)
- successUrl : 登录成功后的默认重定向地址
- failureKeyAttribute : 登录失败后错误信息存储key(shiroLoginFailure)
-
authcBasic : Basic HTTP身份验证拦截器,主要属性 : applicationName:弹出登录框显示的信息(application)
-
roles : 角色授权拦截器,验证用户是否拥有资源角色. 示例”/admin/**=roles[admin]”
-
perms : 权限授权拦截器,验证用户是否拥有资源权限. 示例”/user/create=perms[“user:create”]”
-
user : 用户拦截器,用户已经身份验证/记住我的登录. 示例”/index=user”
-
logout : 退出拦截器,主要属性 : redirectUrl:退出成功后重定向的地址(/). 示例”/logout=logout”
-
port : 端口拦截器,主要属性 : port(80):可以通过的端口. 示例”/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等相同
-
rest : rest风格拦截器,自动根据请求方法构建权限字符串。
(GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串. 示例”/users=rest[user]”,会自动拼出”user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配:isPermittedAll)
-
ssl : SSL拦截器,只有请求协议是https才能通过. 否则自动跳转会https端口(443). 其他和port拦截器一样
-
注 : anon,authcBasic,auchc,user是认证过滤器. perms,roles,ssl,rest,port是授权过滤器
整合WEB
整合SSM
- 数据库表设计
CREATE TABLE `tb_user` (
`uid` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
`password` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码',
`rid` int(11) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`uid`),
KEY `user_rid_tb_role_key_idx` (`rid`),
CONSTRAINT `user_rid_role_key` FOREIGN KEY (`rid`) REFERENCES `tb_role` (`rid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `tb_role` (
`rid` int(11) NOT NULL COMMENT '角色id',
`role_name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '角色名称',
PRIMARY KEY (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `tb_permission` (
`pid` int(11) NOT NULL COMMENT '权限id',
`permission_name` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '权限名称',
`rid` int(11) DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`pid`),
KEY `permission_rid_role_key_idx` (`rid`),
CONSTRAINT `permission_rid_role_key` FOREIGN KEY (`rid`) REFERENCES `tb_role` (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
- 1:添加依赖
- 2:配置web.xml
- 3:配置applicationContext-shiro.xml
- 4:自定义Realm实现授权认证和登录认证
整合springboot
- Spring集成Shiro一般通过的 xml 配置,比较繁琐,而Spring Boot集成Shiro相对简单,只需要配置两个类 : ShiroConfiguration类及继承AuthorizingRealm的Realm类
- ShiroConfig : 顾名思义就是对Shiro的一些配置,相对于Spring中的xml配置. 包括 : 包括过滤器(ShiroFilter)、安全事务管理器(SecurityManager)、密码凭证匹配器(CredentialsMatcher)、缓冲管理器(EhCacheManager)、aop注解支持(authorizationAttributeSourceAdvisor)、等等
- CustomRealm : 自定义的CustomRealm继承自AuthorizingRealm,重写了父类中的doGetAuthorizationInfo(授权认证)、doGetAuthenticationInfo(登陆认证)这两个方法
@Configuration
public class ShiroConfig {
/**
* @description: 配置过滤器
* @param: securityManager
* @date: 2019-08-05 7:59 AM
* @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//设置自定义过滤器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
//设置用户登录页,默认: http://localhost:8080/login.jsp
shiroFilterFactoryBean.setLoginUrl("/loginView");
//设置用户未授权操作提示页
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizedView");
return shiroFilterFactoryBean;
}
/**
* @description: 初始化自定义Realm
* @param: credentialsMatcher
* @date: 2019-08-05 7:50 AM
* @return: pers.huangyuhui.ss.shiro.UserRealm
*/
@Bean
public UserRealm userRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
UserRealm userRealm = new UserRealm();
//设置凭证匹配器
userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return userRealm;
}
/**
* @description: 安全事务管理器
* @param: credentialsMatcher
* @date: 2019-08-05 7:53 AM
* @return: org.apache.shiro.web.mgt.DefaultWebSecurityManager
*/
@Bean
public SecurityManager securityManager(UserRealm userRealm, EhCacheManager ehCacheManager) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//关联自定义realm
defaultWebSecurityManager.setRealm(userRealm);
//关联缓存管理
defaultWebSecurityManager.setCacheManager(ehCacheManager);
return defaultWebSecurityManager;
}
/**
* @description: 哈希密码匹配器:比较用户登录时输入的密码,跟数据库密码配合盐值salt解密后是否一致
* @date: 2019-08-05 9:01 PM
* @return: org.apache.shiro.authc.credential.HashedCredentialsMatcher
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列算法
hashedCredentialsMatcher.setHashIterations(3); //散列的次数
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); //默认是true:Hex编码.false:Base64编码
return hashedCredentialsMatcher;
}
/**
* @description: 设置缓存管理, 缓存用户及其权限信息
* @date: 2019-08-07 7:51 AM
* @return: org.apache.shiro.cache.ehcache.EhCacheManager
*/
@Bean
public EhCacheManager ehCacheManager() {
//注意:myEhcache对应ehcache-shiro.xml中的'<ehcache name="myEhcache">'
CacheManager cacheManager = CacheManager.getCacheManager("myEhcache");
if (cacheManager == null) {
cacheManager = CacheManager.create();
}
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
/**
* @description: 设置资源的权限控制
* @date: 2019-08-05 8:31 AM
* @return: java.util.Map
*/
private Map<String, String> filterChainDefinitionMap() {
Map<String, String> filterMap = new LinkedHashMap<>();
//需身份认证
filterMap.put("/stuListView", "authc");
filterMap.put("/teaListView", "authc");
//无需身份认证:防止验证用户登录信息操作被'filterMap.put("/**", "authc")'拦截
filterMap.put("/login", "anon");
//注销过滤器:其具体的注销逻辑代码Shiro已经替我们实现了哟
filterMap.put("/logout", "logout");
//角色过滤:需要用户拥有'admin'角色
filterMap.put("/teaListView", "roles[admin]");
//权限过滤:除身份认证外,还需要用户拥有对stuListView资源的view权限
filterMap.put("/stuListView", "perms[stuListView:view]");
filterMap.put("/teaListView", "perms[teaListView:view]");
//拦截需要登录(用户认证)方可访问的资源(一般将/**放在最下边,不然会导致所有url都被拦截哟)
filterMap.put("/**", "authc");
return filterMap;
}
/**
* @description: 配置ShiroDialect, 用于thymeleaf和shiro标签配合使用
* @date: 2019-08-05 6:37 PM
* @return: at.pollux.thymeleaf.shiro.dialect.ShiroDialect
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
- ehcache-shiro.xml : EhCache缓存框架的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false"
name="myEhcache">
<diskStore path="java.io.tmpdir"/>
<!-- 授权信息缓存 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
statistics="true">
</cache>
<!-- 身份信息缓存 -->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
statistics="true">
</cache>
<!-- Session缓存 -->
<cache name="activeSessionCache"
maxEntriesLocalHeap="2000"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
statistics="true">
</cache>
<!-- 缓存半小时 -->
<cache name="halfHour"
maxElementsInMemory="10000"
maxElementsOnDisk="100000"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
overflowToDisk="false"
diskPersistent="false"/>
<!-- 缓存一小时 -->
<cache name="hour"
maxElementsInMemory="10000"
maxElementsOnDisk="100000"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
overflowToDisk="false"
diskPersistent="false"/>
<!-- 缓存一天 -->
<cache name="oneDay"
maxElementsInMemory="10000"
maxElementsOnDisk="100000"
timeToIdleSeconds="86400"
timeToLiveSeconds="86400"
overflowToDisk="false"
diskPersistent="false"/>
<!--
name: 缓存名称。
maxElementsInMemory: 缓存最大个数
eternal: 对象是否永久有效,一但设置了,timeout将不起作用
timeToIdleSeconds: 设置对象在失效前的允许闲置时间(单位:秒). 仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds: 设置对象在失效前允许存活时间(单位:秒). 最大时间介于创建时间和失效时间之间. 仅当eternal=false对象不是永久有效时使用,默认是0,也就是对象存活时间无穷大
overflowToDisk: 当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中
diskSpoolBufferSizeMB: 这个参数设置DiskStore(磁盘缓存)的缓存区大小,默认是30MB. 每个Cache都应该有自己的一个缓冲区
maxElementsOnDisk: 硬盘最大缓存个数
diskPersistent: 是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds: 磁盘失效线程运行时间间隔,默认是120秒
memoryStoreEvictionPolicy: 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存. 默认策略是LRU(最近最少使用). 你可以设置为FIFO(先进先出)或是LFU(较少使用)
clearOnFlush: 内存数量最大时是否清除
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* @description: 授权认证:提供用户信息,返回权限信息
* @param: principalCollection
* @date: 2019-08-06 6:02 PM
* @return: org.apache.shiro.authz.AuthorizationInfo
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.err.println("------------> 授权认证 ------------>");
//获取当前登录的用户信息
Subject currentUser = SecurityUtils.getSubject();
User u = (User) currentUser.getPrincipal();
//从数据库中获取用户所拥有的角色及权限信息
User user = userService.findByName(u.getUsername());
if (user != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//用于存储用户的角色及权限信息
Collection<String> rolesCollection = new HashSet<>();
Collection<String> permissionsCollection = new HashSet<>();
Set<Role> roles = user.getRoles(); //获取用户Role的Set集合
//通过遍历用户所拥有的角色,来获取其对应的权限信息
for (Role role : roles) {
rolesCollection.add(role.getName()); //将每一个role的name封装到集合中
Set<Permission> permissionSet = role.getPermissions(); //获取每一个role所对应的permission的set集合
//遍历用户所拥有的权限信息
for (Permission permission : permissionSet) {
permissionsCollection.add(permission.getName()); //将每一个permission的name封装到集合中
}
info.addStringPermissions(permissionsCollection); //为用户授权
}
info.addRoles(rolesCollection); //为用户授予角色
System.out.println("[roles]------------>" + rolesCollection.toString());
System.out.println("[permissions]------------>" + permissionsCollection.toString());
return info;
}
return null;
}
/**
* @description: 登录认证:提供帐户信息,返回认证信息
* @param: authenticationToken
* @date: 2019-08-06 6:12 PM
* @return: org.apache.shiro.authc.AuthenticationInfo
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.err.println("------------> 开始认证 ------------>");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//从数据库中获取用户信息
User user = userService.findByName(token.getUsername());
//验证账户信息
if (user == null) {
return null; //it's will be throw a UnknownAccountException
}
//验证密码信息
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), this.getName());
}
}
- 加密逻辑
public class SecurityUtils {
public static void main(String[] args) {
//admin-pwd [source:demo+salt:admin+hashIterations:3] : 257e3b15d67a9127d230175e43118e40
//tea-pwd [source:demo+salt:tea+hashIterations:3] : f97ccdcf125073d5f19bd3de0b67eb40
//stu-pwd [source:demo+salt:stu+hashIterations:3] : e1a1c9a1340d179077086b5dbee621b4
String md5Pwd = new SimpleHash("md5", "demo", ByteSource.Util.bytes("stu"), 3).toHex();
System.out.println(md5Pwd);
}
}
整合jsp
- 引入shiro标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
认证通过
- 表示认证已通过,但不包括remember me登录的
- 说明 : 只有已通过用户认证,但不是通过记住我(remember me)浏览才会看到标签内的内容
<shiro:authenticated>
<label>用户身份验证已通过</label>
</shiro:authenticated>
认证不通过
- 表示没有通过验证
- 说明 : 只有没有通过验证的才可以看到标签内的内容,包括通过记住我(remember me)登录的
<shiro:notAuthenticated>
<label>用户身份验证没有通过</label>
</shiro:notAuthenticated>
游客身份
- 表示是游客身份,没有登录
- 说明 : 只有是没有登录过,以游客的身份浏览才会看到标签内的内容
<shiro:guest>
<label>您当前是游客,</label><a href="/login.jsp" >请登录</a>
</shiro:guest>
已登录
- 表示已登录
- 说明 : 只有已经登录(包含通过记住我(remember me)登录的)的用户才可以看到标签内的内容. 一般和标签
shiro : principal
一起用,来做显示用户的名称
<shiro:user>
<label>欢迎[<shiro:principal/>],</label><a href="/logout.jsp">退出</a>
</shiro:user>
有任一角色
- 表示拥有这些角色中其中一个
- 说明 : 只有成功登录后,且具有admin或者user角色的用户才会看到标签内的内容. name属性中可以填写多个角色名称,以逗号( , )分隔
<shiro:hasAnyRoles name="admin,user">
<label>这是拥有admin或者是user角色的用户</label>
</shiro:hasAnyRoles>
有某一角色
- 表示拥有某一角色
- 说明 : 只有成功登录后,且具有admin角色的用户才可以看到标签内的内容,name属性中只能填写一个角色的名称
<shiro:hasRole name="admin">
<label>这个用户拥有的角色是admin</label>
</shiro:hasRole>
无某一角色
- 表示不拥有某一角色
- 说明 : 只有成功登录后,且不具有admin:delete权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称
<shiro:lacksRole name="admin">
<label>这个用户不拥有admin的角色</label>
</shiro:lacksRole>
有某一权限
- 表示拥有某一权限
- 说明 : 只有成功登录后,且具有admin:add权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称
<shiro:hasPermission name="admin:add">
<label>这个用户拥有admin:add的权限</label>
</shiro:hasPermission>
无某一权限
- 表示不拥有某一权限
- 说明 : 只有成功登录后,且不具有admin:delete权限的用户才可以看到标签内的内容,name属性中只能填写一个权限的名称
<shiro:lacksPermission name="admin:delete">
<label>这个用户不拥有admin:delete的权限</label>
</shiro:lacksPermission>
principal
- 表示用户的身份 : 取值取的是你登录的时候,在Realm实现类中的
SimpleAuthenticationInfo(Object principal, Object hashedCredentials, ByteSource credentialsSalt, String realmName)
放的第一个参数
return new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), this.getName());
- 如果第一个放的是username或者是一个值,那么就可以直接用
<!--取到username-->
<shiro: principal/>
- 如果第一个参数放的是对象,比如放User对象. 那么如果要取其中某一个值,可以通过property属性来指定
<!--需要指定property-->
<shiro:principal property="username"/>
标签嵌套
- shiro的jsp标签可以嵌套使用,可以根据业务的具体场景进行使用. 例如一个按钮需要排除不是admin或user角色的用户才可以显示,则可以像如下这样实现 :
<shiro:lacksRole name="admin">
<shiro:lacksRole name="user">
<label>这个用户不拥有admin或user的角色</label>
</shiro:lacksRole>
</shiro:lacksRole>
整合thymeleaf
- 添加依赖
<!-- Thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Thymeleaf对shiro的扩展 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 添加配置
/**
* @description: 配置ShiroDialect, 用于thymeleaf和shiro标签配合使用
* @date: 2019-08-05 6:37 PM
* @return: at.pollux.thymeleaf.shiro.dialect.ShiroDialect
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
- 命名空间
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
- 标签
<!-- guest标签:用户没有身份验证时显示相应信息,即游客访问信息 -->
<shiro:guest></shiro:guest>
<!-- user标签:用户已经身份验证/记住我登录后显示相应的信息 -->
<shiro:user></shiro:user>
<!-- authenticated标签:用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的 -->
<shiro:authenticated></shiro:authenticated>
<!-- notAuthenticated标签:用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我,自动登录的也属于未进行身份验证 -->
<shiro:notAuthenticated></shiro:notAuthenticated>
<!-- principal标签:相当于((User)Subject.getPrincipals()).getUsername() -->
<shiro: principal/>
<shiro:principal property="username"/>
<!-- lacksPermission标签:如果当前Subject没有权限将显示body体内容 -->
<shiro:lacksPermission name="org:create"></shiro:lacksPermission>
<!-- hasRole标签:如果当前Subject有角色将显示body体内容 -->
<shiro:hasRole name="admin"></shiro:hasRole>
<!-- hasAnyRoles标签:如果当前Subject有任意一个角色(或的关系)将显示body体内容 -->
<shiro:hasAnyRoles name="admin,user"></shiro:hasAnyRoles>
<!-- lacksRole标签:如果当前Subject没有角色将显示body体内容 -->
<shiro:lacksRole name="abc"></shiro:lacksRole>
<!-- hasPermission标签:如果当前Subject有权限将显示body体内容 -->
<shiro:hasPermission name="user:create"></shiro:hasPermission>
缓存
- 当权限信息存放在数据库中时,对于每次前端的访问请求都需要进行一次数据库查询. 特别是在大量使用shiro的jsp标签的场景下,对应前端的一个页面访问请求会同时出现很多的权限查询操作,这对于权限信息变化不是很频繁的场景,每次前端页面访问都进行大量的权限数据库查询是非常不经济的! 因此非常有必要对权限数据使用缓存方案
- 关于shiro权限数据的缓存方式,可以分为以下两类 :
- 将权限数据缓存到集中式存储中间件中,比如redis或者memcached
- 将权限数据缓存到本地
- Shiro只提供了一个可以支持具体缓存实现(如 : Hazelcast, Ehcache, OSCache, Terracotta, Coherence, GigaSpaces, JBossCache 等)的抽象API接口,这样就允许Shiro用户根据自己的需求灵活地选择具体的CacheManager,当然,其实Shiro也自带了一个本地内存CacheManager(
org.apache.shiro.cache.MemoryConstrainedCacheManager
) - 从以上分析我们知道Shiro支持在2个地方定义缓存管理器,既可以在SecurityManager中定义,也可以在Realm中定义,任选其一即可. 通常我们都会自定义Realm实现,例如将权限数据存放在数据库中,那么在Realm实现中定义缓存管理器再合适不过了