JavaWeb环境下Shiro动态权限管理的一次实践
唉,好久没更新博客了,原因一个是懒,一个是忙。
懒癌这种事的话就……不提了~最近忙的当然也是工作上项目相关的事情,主要在研究折腾Shiro权限管理框架这块,我是打算弄出完全动态权限管理的效果,然而Google上的结果全都是针对Spring框架集成的,在我们公司这个没有用什么框架的破烂项目上当然没法套用!
无奈之下只能寄希望于翻阅API文档和代码,最终还是天无绝人之路让我走出一条大道~所以这篇文章就是记录一下这个思路以及实现,虽然因为个人实力原因很多东西也不是太懂,可能说的不是很清晰明了,但这篇文章是99%的个人原创哦!
表的设计
太复杂的设计我这样的萌新也不懂,这次就用相对较简单的结构,仅仅用来管理URL地址访问的权限, 一个用户可以有多个角色,一个角色拥有多个权限,然后每个URL对应一个指定的权限,当系统要知道这个用户能不能访问指定URL时,只需要查出该用户所有的权限然后丢给Shiro框架即可。
总共六个表,用户表、角色表、权限表、用户角色关系表、角色权限关系表以及URL规则表,这里表弄的比较简单,真正开发的时候还是要根据需求来设计表结构。
准备工作
这里的步骤我不会详细说明,因为这篇文章主要集中在动态权限读取的实践上,不会的同学可以先去Google找找Shiro基础教程,推荐开涛大佬的《跟我学Shiro》。
首先当然是将依赖的jar包丢进项目。
在src根目录下创建shiro.ini配置文件
[main]
authc.loginUrl=/my/index.jsp
perms.unauthorizedUrl=unauthorized.jsp
#声明一个自定义Realm
userRealm=com.fivestar.my.shiro.realm.userRealm
#指定securityManager的realms实现
#securityManager会根据指定的顺序进行认证,若不显示配置此行则会按照声明的顺序进行认证
securityManager.realms=$userRealm
要通过Shiro实现动态权限管理,需要操作Shiro的FilterChainResolver
对象,最终Shiro框架是从这个对象里获取权限规则(URL和对应要经过的过滤器)信息的,这里说一下和思路(不使用Spring套件的纯J2E项目)。
以下代码皆来自母婴项目。
在web.xml下加入下面配置:
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>iniShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
<init-param>
<param-name>configPath</param-name>
<param-value>classpath:shiro.ini</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>iniShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>com.fivestar.my.shiro.env.MyIniWebEnvironment</param-value>
</context-param>
WebEnvironment
相当于Shiro框架的默认设置,项目启动时,EnvironmentLoaderListener
监听器会读取配置文件并初始化WebEnvironment
对象。
WebEnvironment
的默认实现类是IniWebEnviroment
,这里我们将自定义的MyIniWebEnvironment
类(继承了默认的实现类IniWebEnvironment
)设置为Shiro框架要初始化的WebEnvironment
对象。
自定义WebEnvironment
继承了IniWebEnviroment
类后,我们只用重写createFilterChainResolver
方法,这个方法便是WebEnviroment
初始化时创建FilterChainResolver
对象的地方。
IniWebEnviroment
中该方法最终的结果是从ini配置文件中读取[urls]
部分的过滤器规则并依此创建FilterChainResolver
对象,重写后我们增加了从数据库总读取url规则到FilterChainResolver
中的代码。(Shiro框架里创建FilterChainResolver
的过程运用了精妙的工厂设计模式,值得研究学习)
/**
* 自定义Shiro环境
* 用于应用启动时从数据库载入规则列表到FilterChain规则链
*/
public class MyIniWebEnvironment extends IniWebEnvironment {
@Override
protected FilterChainResolver createFilterChainResolver() {
// 调用父类方法获得默认的FilterChainResolver对象(此时已经加载好ini文件中的规则)
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) super.createFilterChainResolver();
// 获取FilterChainManager对象
DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 这部分是从数据库中读取权限规则信息
Map<String, String> params = new HashMap<String, String>();
params.put("enable", "1");
Ls rules = new permissionDao().getPermissionRuleList(params, 0, 0);
// 循环读取到的权限规则信息并用FilterChainManager提供的方法添加规则
for (Rs rule : rules.getItems()) {
String url = rule.get("url");
String alias = rule.get("alias");
if (!url.isEmpty() && !alias.isEmpty()) {
filterChainManager.addToChain(url, "perms", alias);
}
}
// 将操作完毕的FilterChainManager对象更新到FilterChainResolver中
filterChainResolver.setFilterChainManager(filterChainManager);
return filterChainResolver;
}
}
实现动态读取权限规则
完成上面代码之后实际上我们的系统权限规则已经是从数据库中获取了,但上面的代码只会在系统运行时跑一次,那能不能在不重启项目的情况下更新规则信息呢?
当然是可以的,从WebEnvironment
对象中可以获取到FilterChainResolver
对象,而在项目中想要获取WebEnviroment
对象的话,只需要使用WebUtils.getWebEnvironment(ServletContext)
方法即可!
之后我们就能直接操作FilterChainResolver
对象了。
下面是一些操作方法,但这里实现的并不是很完善,有待改进。
/**
* Shiro框架FilterChain规则链管理
* @author Luxnk
*/
public class ShiroFilterChainManager {
/**
* 初始化Shiro权限管理的FilterChain规则链
*/
public void initFilterChains() {
// 获取WebEnvironment对象
MyIniWebEnvironment env = (MyIniWebEnvironment) WebUtils.getWebEnvironment(App.get().getRequest().getServletContext());
// 线程同步,同一时间只能有一个线程访问WebEnvironment
synchronized (env) {
// 获取FilterChianResolver对象
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) env.getFilterChainResolver();
// 获取FilterChainManager对象
DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 获取过滤器Map列表
Map<String, Filter> defaultFilters = new HashMap<String, Filter>(filterChainManager.getFilters());
// 获取默认FilterChain
Map<String, NamedFilterList> defaultFilterChains = new HashMap<String, NamedFilterList>(filterChainManager.getFilterChains());
// 删除以前老的FilterChain并注册默认的
filterChainManager.getFilterChains().clear();
if (defaultFilterChains != null) {
filterChainManager.getFilterChains().putAll(defaultFilterChains);
}
// 从数据库中获取权限规则并注册到FilterChain中
Map<String, String> params = new HashMap<String, String>();
params.put("enable", "1");
Ls rules = new permissionDao().getPermissionRuleList(params, 0, 0);
for (Rs rule : rules.getItems()) {
String url = rule.get("url");
String alias = rule.get("alias");
if (!url.isEmpty() && !alias.isEmpty()) {
filterChainManager.addToChain(url, "perms", alias);
}
}
filterChainResolver.setFilterChainManager(filterChainManager);
env.setFilterChainResolver(filterChainResolver);
}
}
/**
* 添加规则到FilterChain规则链
*/
public void addToFilterChain(String url, String alias) {
MyIniWebEnvironment env = (MyIniWebEnvironment) WebUtils.getWebEnvironment(App.get().getRequest().getServletContext());
synchronized (env) {
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) env.getFilterChainResolver();
DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 获取默认FilterChain
Map<String, NamedFilterList> defaultFilterChains = new HashMap<String, NamedFilterList>(filterChainManager.getFilterChains());
// 删除以前老的FilterChain并注册默认的
filterChainManager.getFilterChains().clear();
if (defaultFilterChains != null) {
filterChainManager.getFilterChains().putAll(defaultFilterChains);
}
// 添加规则
filterChainManager.addToChain(url, "perms", alias);
filterChainResolver.setFilterChainManager(filterChainManager);
env.setFilterChainResolver(filterChainResolver);
}
}
/**
* 从FilterChain规则链中移除规则
*/
public void removeToFilterChain(String url) {
MyIniWebEnvironment env = (MyIniWebEnvironment) WebUtils.getWebEnvironment(App.get().getRequest().getServletContext());
synchronized (env) {
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) env.getFilterChainResolver();
DefaultFilterChainManager filterChainManager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
// 获取默认FilterChain
Map<String, NamedFilterList> defaultFilterChains = new HashMap<String, NamedFilterList>(filterChainManager.getFilterChains());
// 删除以前老的FilterChain并注册默认的
filterChainManager.getFilterChains().clear();
if (defaultFilterChains != null) {
// 如果有则删除规则
if (defaultFilterChains.containsKey(url)) {
defaultFilterChains.remove(url);
}
filterChainManager.getFilterChains().putAll(defaultFilterChains);
}
filterChainResolver.setFilterChainManager(filterChainManager);
env.setFilterChainResolver(filterChainResolver);
}
}
}