shiro主要有三大功能模块
1. Subject:主体,一般指用户。
2. SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)
3. Realms:用于进行权限信息的验证,一般需要自己实现。
具体功能
1. Authentication:身份认证/登录(账号密码验证)。
2. Authorization:授权,即角色或者权限验证。
3. Session Manager:会话管理,用户登录后的session相关管理。
4. Cryptography:加密,密码加密等。
5. Web Support:Web支持,集成Web环境。
6. Caching:缓存,用户信息、角色、权限等缓存到如redis等缓存中。
7. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
8. Testing:测试支持;
9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
10. Remember Me:记住我,登录后,下次再来的话不用登录了。
整合shiro
创建数据库表如下
pom
1 |
|
控制器
1 |
|
业务类
1 | package com.hu.service.impl; |
主html
1 |
|
密码规则
1 | public class PasswordHelper { |
Realm
1 |
|
ShiroConfig
1 |
|
测试,必须要登录才可以使用功能。
权限控制
权限表增加权限,在角色表添加角色,并且选了用户和角色的对应关系。
授权
1 |
|
在配置类中加入AOP
1 | // 授权使用注解 需要开启Spring AOP |
增加处理类:
1 |
|
在控制器中加入相应的注解,测试、没有权限的不能访问相关功能。
会话管理
会话管理器管理着应用中所有 Subject 的会话的创建、维护、删除、失效、验证等工作。是 Shiro 的核心组件,顶层组件 SecurityManager 直接继承了 SessionManager,且提供了SessionsSecurityManager 实现直接把会话管理委托给相应的 SessionManager,DefaultSecurityManager 及 DefaultWebSecurityManager 默认 SecurityManager 都继承了 SessionsSecurityManager。
Shiro 提供了三个默认实现:
DefaultSessionManager:DefaultSecurityManager 使用的默认实现,用于 JavaSE 环境;
ServletContainerSessionManager:DefaultWebSecurityManager 使用的默认实现,用于 Web 环境,其直接使用 Servlet 容器的会话;
DefaultWebSessionManager:用于 Web 环境的实现,可以替代 ServletContainerSessionManager,自己维护着会话,直接废弃了 Servlet 容器的会话管理。
可以设置会话的全局过期时间(毫秒为单位),默认30分钟:sessionManager. globalSessionTimeout=1800000
会话监听器
会话监听器用于监听会话创建、过期及停止事件:
1 | public class MySessionListener1 implements SessionListener { |
如果只想监听某一个事件,可以继承SessionListenerAdapter实现:
1 | public class MySessionListener2 extends SessionListenerAdapter { |
会话存储 / 持久化
Shiro 提供 SessionDAO 用于会话的 CRUD,即 DAO(Data Access Object)模式实现,如 DefaultSessionManager 在创建完 session 后会调用该方法;如保存到关系数据库/文件系统/NoSQL 数据库;即可以实现会话的持久化;返回会话 ID;主要此处返回的ID.equals(session.getId());
1 | //如DefaultSessionManager在创建完session后会调用该方法;如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;返回会话ID;主要此处返回的ID.equals(session.getId()); |
AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的CacheManager即可;MemorySessionDAO直接在内存中进行会话维护;而EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
Shiro 提供了使用 Ehcache 进行会话存储,Ehcache 可以配合 TerraCotta 实现容器无关的分布式集群。
首先增加依赖:
1 | <dependency> |
shiro-web.ini 文件:
1 | sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO |
sessionDAO. activeSessionsCacheName:设置 Session 缓存名字,默认就是 shiro-activeSessionCache;
cacheManager:缓存管理器,用于管理缓存的,此处使用 Ehcache 实现;
cacheManager.cacheManagerConfigFile:设置 ehcache 缓存的配置文件;
securityManager.cacheManager:设置 SecurityManager 的 cacheManager,会自动设置实现了 CacheManagerAware 接口的相应对象,如 SessionDAO 的 cacheManager;
配置 ehcache.xml:
1 | <cache name="shiro-activeSessionCache" |
Cache 的名字为 shiro-activeSessionCache,即设置的 sessionDAO 的 activeSessionsCacheName 属性值。
另外可以通过如下 ini 配置设置会话 ID 生成器:
1 | sessionIdGenerator=org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator |
用于生成会话 ID,默认就是 JavaUuidSessionIdGenerator,使用 java.util.UUID 生成。
如果需要存储到数据库 自定义实现 SessionDAO,继承 CachingSessionDAO 即可:
1 | public class MySessionDAO extends CachingSessionDAO { |
doCreate/doUpdate/doDelete/doReadSession 分别代表创建 / 修改 / 删除 / 读取会话;此处通过把会话序列化后存储到数据库实现;接着在 shiro-web.ini 中配置:
sessionDAO=com.github.zhangkaitao.shiro.chapter10.session.dao.MySessionDAO
其他设置和之前一样,因为继承了 CachingSessionDAO;所有在读取时会先查缓存中是否存在,如果找不到才到数据库中查找。
使用Redis作为Session缓存
需要shiro重写SessionDAO,继承AbstractSessionDAO,实现Redis Session的增刪改查操作
1 |
|
配置Redis不做介绍
配置ShiroConfig
1 | /* @Bean |
会话验证
定期的检测会话是否过期,Shiro 提供了会话验证调度器 SessionValidationScheduler实现。
可以通过如下 ini 配置开启会话验证:
1 | sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler |
Shiro 也提供了使用 Quartz 会话验证调度器:
1 | sessionValidationScheduler=org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler |
使用时需要导入 shiro-quartz 依赖:
1 | <dependency> |
会话验证调度器实现都是直接调用 AbstractValidatingSessionManager 的 validateSessions 方法进行验证,其直接调用 SessionDAO 的 getActiveSessions 方法获取所有会话进行验证,如果会话比较多,会影响性能;可以考虑如分页获取会话并进行验证,如:
1 |
|
在此方法中分页获取,如果在会话过期时不想删除过期的会话,可以通过如下 ini 配置进行设置:
sessionManager.deleteInvalidSessions=false
默认是开启的,在会话过期后会调用 SessionDAO 的 delete 方法删除会话:如会话时持久化存储的,可以调用此方法进行删除。
如果是在获取会话时验证了会话已过期,将抛出 InvalidSessionException。
sessionFactory
sessionFactory 是创建会话的工厂,根据相应的 Subject 上下文信息来创建会话;默认提供了 SimpleSessionFactory 用来创建 SimpleSession 会话。
首先自定义一个 Session:
1 | public class OnlineSession extends SimpleSession { |
OnlineSession 用于保存当前登录用户的在线状态,支持如离线等状态的控制。
接着自定义 SessionFactory:
1 | public class OnlineSessionFactory implements SessionFactory { |
根据会话上下文创建相应的 OnlineSession。
最后在 shiro-web.ini 配置文件中配置:
1 | sessionFactory=org.apache.shiro.session.mgt.OnlineSessionFactory |
缓存
Shiro 提供了类似于 Spring 的 Cache 抽象,即 Shiro 本身不实现 Cache,但是对 Cache 进行了又抽象,方便更换不同的底层 Cache 实现。Shiro提供的缓存抽象API接口正是:org.apache.shiro.cache.CacheManager。
Shiro支持在2个地方定义缓存管理器,既可以在SecurityManager中定义,也可以在Realm中定义,任选其一即可。
通常我们都会自定义Realm实现,例如将权限数据存放在数据库中,那么在Realm实现中定义缓存管理器再合适不过了。
Shiro设计成既可以在Realm,也可以在SecurityManager中设置缓存管理器,分别在Realm和SecurityManager定义的缓存管理器,他们的区别和联系,请看源码:
下面,我们追踪一下org.apache.shiro.mgt.RealmSecurityManage的源码实现:
1 | protected void applyCacheManagerToRealms() { |
真正使用CacheManager的组件是Realm。
缓存方案
基于Redis的集中式缓存方案
本地缓存的实现有几种方式:(1)直接存放到JVM堆内存(2)使用NIO存放在堆外内存,自定义实现或者借助于第三方缓存组件。
不论是采用集中式缓存还是使用本地缓存,shiro的权限数据本身都是直接存放在本地的,不同的是缓存标志的存放位置。采用本地缓存方案是,我们将缓存标志也存放在本地,这样就避免了查询缓存标志的网络请求,能更进一步提升缓存效率
缓存更新
shiro框架的服务端进行了多实例部署,首先需要对session进行同步,因为shiro的认证信息是存放在session中的;其次,当前端操作在某个实例上修改了权限时,需要通知后端服务的多个实例重新获取最新的权限数据。
zookeeper最核心的功能就是统一配置,同时还可以用来实现服务注册与发现,在这里使用的zookeeper特性是:watcher机制。当某个节点的状态发生改变时,监控该节点状态的组件将会收到通知。
缓存实现
把权限信息存到redis
Cache
1 |
|
CacheManager
1 |
|
1 | public class Contans { |
修改配置信息,把缓存管理器配置到Shiro中
1 | //配置核心安全事务管理器 |
之后,访问用到权限的页面,就会在redis中缓存授权信息。
详细,见 https://github.com/huingsn/my-project