框架介绍
1、shiro框架及其基本功能
Apache Shiro 是Java 的一个安全框架。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在JavaEE 环境。Shiro 可以帮助我们完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
Shiro 的 API 也是非常简单;其基本功能点如下图所示:
Authentication :身份认证/登录,验证用户是不是拥有相应的身份;
Authorization :授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager :会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
Cryptography :加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support :Web 支持,可以非常容易的集成到 Web 环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency :shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
Testing :提供测试支持;
Run As :允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me :记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
注:Shiro 不会去维护用户、维护权限;这 些需要我们 自己去 设计/ 提供 ; 然后通过相应的 接口注入给 给 Shiro 即可。
2、shiro架构原理
应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个 API 的含义:
Subject :主体,代表了当前"用户",这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;SecurityManager :安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;
Realm: 域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
也就是说对于我们而言,最简单的一个 Shiro 应用:
1、 应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
2、 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
注:从以上也可以看出 ,Shiro 不提供维护用户/ 权限, 而是通过 Realm 让开发人员自己注入。
Subject :主体,可以看到主体可以是任何可以与应用交互的"用户";
SecurityManager : 相 当 于 SpringMVC 中 的 DispatcherServlet 或 者 Struts2 中 的FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator :认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authrizer :授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm :可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm;
SessionManager :如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所有呢,Shiro 就抽象了一个自己的 Session来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);
SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO中可以使用 Cache 进行缓存,以提高性能。
CacheManager :缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能。
Cryptography :密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密的。
一个简单的示例
maven配置文件:
1 | <project xmlns="http://maven.apache.org/POM/4.0.0" |
在src下创建shiro的ini配置文件/shiro_01/src/main/resources/shiro.ini:
1 | [users] |
登录测试java代码,这里使用junit测试:
1 | public class LoginLogoutTest { |
运行结果输出登录成功
注:如果身份验证失败请捕获 AuthenticationException 或其子类
常见的如 :DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的
凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如”用户名/密码错误”而不是”用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;
最后可以调用 subject.logout 退出,其会自动委托给 SecurityManager.logout 方法退出。
1、收集用户身份/凭证,即如用户名/密码;
2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功;
3、最后调用 Subject.logout 进行退出操作。
认证Authentication
在应用中谁能证明他就是他本人。一般提供如他们的身份ID 一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。
在 shiro 中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的 principals 和 credentials 组合就是用户名/密码了。接下来先进行一个基本的身份认证。
另外两个相关的概念是之前提到的 Subject 及 Realm,分别是主体及验证主体的数据源。
认证流程
1、首先调用 Subject.login(token)进行登录,其会自动委托给 Security Manager,调用之前必须通过 SecurityUtils. setSecurityManager()设置;
2、SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;
3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;
5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个 Realm,将按照相应的顺序及策略进行访问。
Realm
域,Shiro 从从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,安全数据源。前面的例子使用的是ini配置org.apache.shiro.realm.text.IniRealm。
Realm接口:
String getName(); //返回一个唯一的 Realm 名字
boolean supports(AuthenticationToken token); //判断此 Realm 是否支持此 Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; //根据 Token 获取认证信息
最基础的是Realm接口,CachingRealm负责缓存处理,AuthenticationRealm负责认证,AuthorizingRealm负责授权,通常自定义的realm继承AuthorizingRealm。
单个Realm配置
类可以通过实现接口
1 | public class MyRealm implements Realm { |
或者继承来实现
1 | public class MyRealm1 extends AuthorizingRealm { |
ini配置:
[users]
hu=123
[main]
#引入 自定义 realm
userRealm=com.hu.test.MyRealm
#将realm设置到securityManager
securityManager.realms=$userRealm
多个Realm配置
ini配置:
#声明一个 realm
myRealm1=com.hu.realm.MyRealm1
myRealm2=com.hu.realm.MyRealm2
#指定 securityManager 的 realms 实现
securityManager.realms=$myRealm1,$myRealm2
securityManager 会按照 realms 指定的顺序进行身份认证。此处我们使用显示指定顺序的方式指定了 Realm 的顺序,如果删除”securityManager.realms=$myRealm1,$myRealm2”,那么 securityManager 会按照 realm 声明的顺序进行使用(即无需设置 realms 属性,其会自动发现 )。
当我们显示指定realm后,其他没有指定realm将被忽略,”securityManager.realms=$myRealm1”,那么 myRealm2 不会被自动设置进去。
Shiro默认提供的的Realm
一般使用继承 AuthorizingRealm(授权),其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)。其中主要默认实现如下:
org.apache.shiro.realm.text.IniRealm:[users]部分指定用户名/密码及其角色;[roles]部分指定角色即权限信息;
org.apache.shiro.realm.text.PropertiesRealm: user.username=password,role1,role2 指定用户名/密码及其角色;role.role1=permission1,permission2 指定角色及权限信息;
org.apache.shiro.realm.jdbc.JdbcRealm: 通过 sql 查询相应的信息,如:
select password fromusers where username = ? 获取用户密码
select password, password_salt from users whereusername = ? 获取用户密码
select role_name from user_roles where username = ? 获取用户角色;
select permission from roles_permissions where role_name = ? 获取角色对应的权限信息,也可以调用相应的 api 进行自定义 sql;
JDBC Realm 使用
数据库及依赖: mysql 数据库及 druid 连接池
1 | <!-- 数据库依赖 使用 mysql 数据库及 druid 连接池 --> |
ini配置
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/maven_shiro
dataSource.username=root
dataSource.password=123456
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm
变量名=全限定类名 会自动创建一个类实例
变量名.属性=值 自动调用相应的 setter 方法进行赋值
$变量名 引用之前的一个对象实例
Junit测试:
1 | public class LoginLogoutTest2 { |
结果:信息: {dataSource-1} inited 登录成功
Authenticator 及 AuthenticationStrategy
Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点。
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;
如果验证成功,将返回 AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 实现。
SecurityManager 接口继承了 Authenticator,另外还有一个 ModularRealmAuthenticator 实现,其委托给多个 Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定,默认提供的实现,三种认证策略:
FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个 Realm 验证成功即可,和 FirstSuccessfulStrategy不同,返回所有 Realm 身份验证成功的认证信息;
AllSuccessfulStrategy:所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator 默认使用 AtLeastOneSuccessfulStrategy 策略。
ini 配置文件:
#配置验证器 指定 securityManager 的 authenticator 实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#配置验证策略 指定 securityManager.authenticator 的 authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
假如有三个realm:
realm1,realm2,realm3
realm1=com.hu.realm.Realm1
realm2=com.hu.realm.Realm2
realm3=com.hu.realm.Realm3
securityManager.realms=$realm1,$realm3
测试成功
自定义Realm
散列算法
一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt,比如加密密码admin产生的散列值是21232f297a57a5a743894a0e4a801fc3,可以到一些md5 解密网站很容易的通过散列值得到密码admin,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和ID(即盐);这样散列的对象是”密码+用户名+ID”,这样生成的散列值相对来说更难破解。
1 | public class ShiroMD5 { |
自定义散列
ini配置:
[users]
hu=123
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=2
#引入 自定义 realm
userRealm=com.hu.test.MyRealmMD5
userRealm.credentialsMatcher=$credentialsMatcher
#将realm设置到securityManager
securityManager.realms=$userRealm
自定义Realm
1 | public class MyRealmMD5 extends AuthorizingRealm { |
测试:
1 | @Test |
结果:
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
my Realm MD5
登录成功
授权Authorization
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体:即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
资源:在应用中用户可以访问的任何东西,比如访问JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限:安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的
权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)打印文档等等。
角色:角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
授权流程
授权方式
Shiro 支持三种方式的授权。
编程式:通过写 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>
授权实现
ini配置文件配置用户拥有的角色及角色-权限关系(shiro-permission.ini)
[users]
hu=123,role1,role2
yun=121,role1
[roles]
role1=user:create,user:update
role2=user:create,user:delete
规则:"用户名=密码,角色1,角色2" "角色=权限1,权限2",即首先根据用户名找到角色,然后根据角色再找到权限,角色是权限集合。
Shiro 不进行权限的维护,需要我们通过Realm返回相应的权限信息。只需要维护"用户--角色"之间的关系即可。
权限字符串的规则是:"资源标识符:操作:资源实例标识符",意思是对哪个资源的哪个实例具有什么操作,":"是资源/操作/实例的分割符,权限字符串也可以使用*通配符。
例子:
用户创建权限:user:create,或user:create:*
用户修改实例001的权限:user:update:001
用户实例001的所有权限:user:*:001
示例
[users]
hu=123,role1,role2
yun=121,role2
[roles]
role1=user:create,user:update
role2=user:create,user:delete
java伪代码:
1 | // 用户认证状态 |
结果:
登录成功
用户认证状态:true
-------------------------
true
true
[true, true, false]
true
true
false
Permission
字符串通配符权限
规则:资源标识符:操作:对象实例ID,即对哪个资源的哪个实例可以进行什么操作。
其默认支持通配符权限字符串,:表示资源/操作/实例的分割;,表示操作的分割;*表示任意资源/操作/实例。
1 、单个资源单个权限
subject().checkPermissions("system:user:update");
2、单个资源多个权限
ini:role1=system:user:update,system:user:delete
Java:subject().checkPermissions("system:user:update", "system:user:delete");
用户拥有资源system:user的update和delete权限。如上可以简写成:
ini:role1="system:user:update,delete"
Java:subject().checkPermissions("system:user:update,delete");
3、单个资源全部权限
ini:role1=system:user:create,update,delete,view
Java:subject().checkPermissions("system:user:create,delete,update:view");
用户拥有资源system:user的create、update、delete和view所有权限。
如上可以简写成:
ini:role1=system:user:*
4、所有资源全部权限
ini:role1=*:view
Java:subject().checkPermissions("user:view");
用户拥有所有资源的"view"所有权限。
5、实例级别的权限
单个实例单个权限
ini:roll=user:view:1
Java:subject().checkPermissions("user:view:1");
单个实例多个权限
ini:roll="user:update,delete:1"
Java:
subject().checkPermissions("user:delete,update:1");
subject().checkPermissions("user:update:1", "user:delete:1");
单个实例所有权限
ini:roll=user:*:1
Java:subject().checkPermissions("user:update:1", "user:delete:1", "user:view:1");
所有实例单个权限
ini:roll=user:auth:*
Java:subject().checkPermissions("user:auth:1", "user:auth:2");
所有实例所有权限
ini:roll=user:*:*
Java:subject().checkPermissions("user:view:1", "user:auth:2");
自定义Realm实现授权
ini:
[users]
hu=123,role1
yun=121,role2
[roles]
role1=user:create,user:update
role2=user:create,user:delete
[main]
#自定义 realm
userRealm=com.hu.test.MyRealmRole
#将realm设置到securityManager
securityManager.realms=$userRealm
Realm:
1 | /* |
测试:
1 | public class LoginLogoutTestRole { |
结果:
my Realm Role hu 123
登录成功
用户认证状态:true
-------------------------
true
true
false
Authorizer 的职责是进行授权(访问控制),是 Shiro API 中授权核心的入口点,其提供了相应的角色/权限判断接口,具体请参考其 Javadoc。SecurityManager 继承了 Authorizer 接口,且提供了 ModularRealmAuthorizer 用于多 Realm 时的授权匹配。PermissionResolver 用于解析权限字符串到 Permission 实例,而 RolePermissionResolver 用于根据角色解析相应的权限集合。
我们可以通过如下 ini 配置更改 Authorizer 实现:
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
securityManager.authorizer=$authorizer
对于 ModularRealmAuthorizer,相应的 AuthorizingSecurityManager 会在初始化完成后自动将相应的 realm 设置进去,我们也可以通过调用其 setRealms()方法进行设置。
设置 ModularRealmAuthorizer 的 permissionResolver,其会自动设置到相应的 Realm 上(其实现了 PermissionResolverAware 接口)
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
authorizer.permissionResolver=$permissionResolver
设置 ModularRealmAuthorizer 的 rolePermissionResolver,其会自动设置到相应的 Realm 上(其实现了 RolePermissionResolverAware 接口)
rolePermissionResolver=com.hu.test.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
会话管理
Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如 web 容器 tomcat),不管JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对 Web 的透明支持、SSO 单点登录的支持等特性。即直接使用 Shiro 的会话管理可以直接替换如 Web 容器的会话管理。是完整的会话模块。
登录成功后使用 Subject.getSession()即可获取会话;其等价于 Subject.getSession(true),即如果当前没有创建 Session 对象会创建一个;另外 Subject.getSession(false),如果当前没有创建 Session 则返回 null(不过默认情况下如果启用会话存储功能的话在创建 Subject 时会主动创建一个 Session)。
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession();
session.getId();//获取当前会话的唯一标识。
session.getHost();//获取当前 Subject 的主机地址,该地址是通过 HostAuthenticationToken.getHost()提供的。
session.getTimeout();
session.setTimeout(毫秒);
获取/设置当前 Session 的过期时间;如果不设置默认是会话管理器的全局过期时间。
session.getStartTimestamp();
session.getLastAccessTime();
获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用 session.touch()去更新最后访问时间;如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch()来更新最后访问时间。
session.touch();
session.stop();
更新会话最后访问时间及销毁会话;当 Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中,调用javax.servlet.http.HttpSession. invalidate()也会自动调用Shiro Session.stop方法进行销毁 Shiro 的会话。
session.setAttribute("key", "123");
session.getAttribute("key")
session.removeAttribute("key");
设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作。
会话管理器
会话管理器管理着应用中所有 Subject 的会话的创建、维护、删除、失效、验证等工作。是Shiro 的核心组件,顶层组件 SecurityManager 直接继承了 SessionManager,且提供了SessionsSecurityManager 实 现 直 接 把 会 话 管 理 委 托 给 相 应 的 SessionManager ,DefaultSecurityManager 及 DefaultWebSecurityManager 默认 SecurityManager 都继承了SessionsSecurityManager。
Session start(SessionContext context); //启动会话
Session getSession(SessionKey key) throws SessionException; //根据会话 Key 获取会话
用于 Web 环境的 WebSessionManager 又提供了如下接口
boolean isServletContainerSessions();//是否使用 Servlet 容器的会话
void validateSessions();//验证所有会话是否过期
Shiro 提供了三个默认实现:
DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于 JavaSE 环境;
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默认实现,用于 Web环境,其直接使用 Servlet 容器的会话;
DefaultWebSessionManager : 用于Web环境的实现 , 可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。
替换 SecurityManager 默认的 SessionManager 可以在 ini 中配置(shiro.ini)
[main]
sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager
securityManager.sessionManager=$sessionManager
Web 环境下的 ini 配置(shiro-web.ini):
[main]
sessionManager=org.apache.shiro.web.session.mgt.ServletContainerSessionManager
securityManager.sessionManager=$sessionManager
设置会话的全局过期时间(毫秒为单位),默认 30 分钟
sessionManager. globalSessionTimeout=1800000
默认情况下 globalSessionTimeout 将应用给所有 Session。可以单独设置每个 Session 的timeout 属性来为每个 Session 设置其超时时间。
如果使用 ServletContainerSessionManager 进行会话管理,Session 的超时依赖于底层Servlet 容器的超时时间,可以在 web.xml 中配置其会话的超时时间(分钟为单位)
<session-config>
<session-timeout>30</session-timeout>
</session-config>
在 Servlet 容器中,默认使用 JSESSIONID Cookie 维护会话,且会话默认是跟容器绑定的;在某些情况下可能需要使用自己的会话机制,此时我们可以使用DefaultWebSessionManager来维护会话:
sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie
sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager
sessionIdCookie.name=sid
#sessionIdCookie.domain=sishuok.com
#sessionIdCookie.path=
sessionIdCookie.maxAge=1800
sessionIdCookie.httpOnly=true
sessionManager.sessionIdCookie=$sessionIdCookie
sessionManager.sessionIdCookieEnabled=true
securityManager.sessionManager=$sessionManager
会话监听
会话监听器用于监听会话创建、过期及停止事件:
1 | public class MySessionListener1 implements SessionListener { |
在 shiro-web.ini 配置文件中可以进行如下配置设置会话监听器:
sessionListener1=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener1
sessionListener2=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener2
sessionManager.sessionListeners=$sessionListener1,$sessionListener2
会话存储/持久化
Shiro 提供 SessionDAO 用于会话的 CRUD,即 DAO(Data Access Object)模式实现,如 DefaultSessionManager 在创建完 session 后会调用该方法;如保存到关系数据库/文件系统/NoSQL 数据库;即可以实现会话的持久化;返回会话 ID;主要此处返回的ID.equals(session.getId());
Serializable create(Session session);
//根据会话 ID 获取会话
Session readSession(Serializable sessionId) throws UnknownSessionException;
//更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
void update(Session session) throws UnknownSessionException;
//删除会话;当会话过期/会话停止(如用户退出时)会调用
void delete(Session session);
//获取当前所有活跃用户,如果用户量多此方法影响性能
AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO 直接在内存中进行会话维护;而 EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用 ConcurrentHashMap 保存缓存的会话。
sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO
sessionManager.sessionDAO=$sessionDAO
Shiro 提供了使用 Ehcache 进行会话存储,Ehcache 可以配合 TerraCotta 实现容器无关的布式集群。
缓存机制
Shiro 提供了类似于 Spring 的 Cache 抽象,即 Shiro 本身不实现 Cache,但是对 Cache 进行了又抽象,方便更换不同的底层 Cache 实现。
Shiro 提供的 的 Cache :
1 | public interface Cache<K, V> { |
Shiro 提供的 的 CacheManager 接口:
public interface CacheManager {
//根据缓存名字获取一个 Cache
public <K, V> Cache<K, V> getCache(String name) throws CacheException;
}
Shiro 还 提供了 了 CacheManagerAware 用 于注入 CacheManager :
public interface CacheManagerAware {
//注入 CacheManager
void setCacheManager(CacheManager cacheManager);
}
Shiro 内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如 Realm)是实现了 CacheManagerAware 并自动注入相应的 CacheManager。
Realm缓存
Shiro 提供了 CachingRealm,其实现了 CacheManagerAware 接口,提供了缓存的一些基础实现;另外 AuthenticatingRealm 及 AuthorizingRealm 分别提供了对 AuthenticationInfo 和AuthorizationInfo 信息的缓存。
userRealm=com......realm.UserRealm
userRealm.credentialsMatcher=$credentialsMatcher
userRealm.cachingEnabled=true
userRealm.authenticationCachingEnabled=true
userRealm.authenticationCacheName=authenticationCache
userRealm.authorizationCachingEnabled=true
userRealm.authorizationCacheName=authorizationCache
securityManager.realms=$userRealm
cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager
cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache.xml
securityManager.cacheManager=$cacheManager
userRealm.cachingEnabled:启用缓存,默认 false;
userRealm.authenticationCachingEnabled:启用身份验证缓存,即缓存 AuthenticationInfo 信息,默认 false;
userRealm.authenticationCacheName:缓存 AuthenticationInfo 信息的缓存名称;
userRealm. authorizationCachingEnabled:启用授权缓存,即缓存 AuthorizationInfo 信息,默认 false;
userRealm. authorizationCacheName:缓存 AuthorizationInfo 信息的缓存名称;
cacheManager:缓存管理器,此处使用 EhCacheManager,即 Ehcache 实现,需要导入相应的 Ehcache 依赖,
Session缓存
当我们设置了 SecurityManager 的 CacheManager 时,如:securityManager.cacheManager=$cacheManager
当我们设置 SessionManager 时:
sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager
securityManager.sessionManager=$sessionManager
如 securityManager 实现了 SessionsSecurityManager,其会自动判断 SessionManager 是否实现了 CacheManagerAware 接口,如果实现了会把 CacheManager 设置给它。然后sessionManager 会判断相应的 sessionDAO(如继承自 CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager 设置给它;其会先查缓存,如果找不到才查数据库。
对于 CachingSessionDAO,可以通过如下配置设置缓存的名称:
sessionDAO=com.github.zhangkaitao.shiro.chapter11.session.dao.MySessionDAO
sessionDAO.activeSessionsCacheName=shiro-activeSessionCache
activeSessionsCacheName 默认就是 shiro-activeSessionCache。