简介
- 即访问url时进行权限匹配,如果没有权限则直接跳到错误页面。
- 在Shiro中,更多的是通过AOP进行方法级别的权限控制,而通过url进行权限控制是一种集中的权限控制。
示例
- 拦截器:如authc、anon等字符串为一个拦截器;拦截器链:url=authc为一条拦截器链;拦截器链集合:url1=authc,url2=anon为拦截器链集合。
- 仿chapter16的示例,主要功能是用户、公司、角色和资源之间关系的调整,如修改用户所属公司、所有角色,资源所需权限、角色所有资源等。
- 数据库:除user、organization、role、resource外,新增一张url-filter表,映射url和角色、权限之间的关系(多个角色、权限之间用逗号分隔),即动态的拦截器链。
- 实体:多一个URLFilter类,封装url和权限、角色间的关系。
public class UrlFilter implements Serializable {
private Long id;
private String name; //url名称或描述
private String url; //地址
private String roles; //所需角色,可省略,用逗号分隔
private String permissions; //所需权限,可省略,用逗号分隔
//setter/getter
}
- dao、service、controller层都增加对UrlFilter的增删查改。
- controller层撤销shiro的权限注解,因为要用数据库中动态的拦截器链来实现。
- 因为数据库中的拦截器链都是要合并到shiro中的,所以在service层中,更新数据库拦截器链后,要重置shiro中的拦截器链集合,以同步此更新,否则更新不生效。比如:
@Override
public void deleteUrlFilter(Long urlFilterId) {
urlFilterDao.deleteUrlFilter(urlFilterId);
initFilterChain();
}
/**
* @Author haien
* @Description 初始化Shiro的url拦截器,用于在url表改动后,
将数据库发送的改动同步到Shiro中。
* @Date 2019/4/5
* @Param []
* @return void
**/
@PostConstruct //当DI容器实例化当前bean时,该方法会自动执行
public void initFilterChain() {
//将配置文件和数据库的拦截器链合并
shiroFilerChainManager.initFilterChains(findAll()); //先清空集合再去加载
}
- ShiroFilterChainManager: 调用了FilterChainManager的方法,先清空拦截器链集合再重新加载。
@Service
public class ShiroFilterChainManager {
//过滤链管理器,管理所有过滤器,包含增删查改等操作
@Resource
private DefaultFilterChainManager filterChainManager; //注入的是
//CustomDefaultFilterChainManager
//默认过滤链,包含在配置文件中提到的所有过滤器
private Map defaultFilterChains;
/**
* @Author haien
* @Description 获取配置文件中all过滤器,之后会自动与数据库中包含的过滤器合并
* @Date 2019/4/5
* @Param []
* @return void
**/
@PostConstruct //DI容器实例化该bean时自动执行该方法
public void inti(){
//获取配置文件中all拦截器(静态的,不会变)
defaultFilterChains=new HashMap<>(filterChainManager.getFilterChains());
}
/**
* @Author haien
* @Description 初始化过滤链,默认+数据库
* @Date 2019/4/5
* @Param [urlFilters]
* @return void
**/
public void initFilterChains(List urlFilters){
//1. 删除以前老的filter chain并注册默认的
filterChainManager.getFilterChains().clear();
if(defaultFilterChains!=null)
filterChainManager.getFilterChains().putAll(defaultFilterChains);
//2. 注册数据库中的
for(UrlFilter urlFilter:urlFilters){
String url=urlFilter.getUrl();
//注册roles-filter关系的过滤器
if(!StringUtils.isEmpty(urlFilter.getRoles()))
filterChainManager.addToChain(url,"roles",urlFilter.getRoles());
//注册perms-filter关系的拦截器
if(!StringUtils.isEmpty(urlFilter.getPermissions()))
filterChainManager.addToChain(url,"perms",urlFilter.getPermissions());
}
}
}
- 为了实现本例功能,加载数据库中动态的拦截器链,我们需要自定义FilterChainManager;而为了实现一个url匹配多个拦截器链,我们还要自定义FilterChainResolver。下面先介绍一下这俩都是什么。
PathMatchingFilterChainResolver过滤链解析器
- 按照第八章过滤链步骤,第一步是要先根据当前请求的url获取拦截器链。
- PathMatchingFilterChainResolver:遍历配置文件中all拦截器链,如,/=athc,将其名字/与当前url/index比对,匹配则执行该拦截器链,如,/**匹配/index。所以,如果要改变url的拦截器匹配方法,则要自定义Resolver。
public FilterChain getChain(ServletRequest request, ServletResponse response,
FilterChain originalChain) {
//1、首先获取拦截器链管理器
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
//2、接着获取当前请求的URL(不带上下文)
String requestURI = getPathWithinApplication(request);
//3、遍历配置文件中all拦截器链(拦截器链的名字就是URL,如/**)
for (String pathPattern : filterChainManager.getChainNames()) {
//4、如匹配当前url
if (pathMatches(pathPattern, requestURI)) {
//5、和原始拦截器链进行合并后返回
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
缺点:最后一步找到即返回,如果后面还有其他拦截器也是匹配当url的呢?后面我们会自定义一个能将all拦截器链合并返回的解析器。
第1步中,默认使用DefaultFilterChainManager管理拦截器链,其内部采用Map管理url-拦截器链的关系,因为Map不可重复,所以每个url只能配置一条拦截器链(但可以包含多个拦截器),如果定义了多条则最终采用哪条也说不定。
- FilterChainManager接口:主要功能是注册拦截器链、添加拦截器以及对原始拦截器链生成代理之后的拦截器链。如果要实现动态url-拦截器配置,即注册数据库中定义的拦截器链的话,就要自定义Manager。
public interface FilterChainManager {
Map getFilters(); //得到注册的拦截器
void addFilter(String name, Filter filter); //注册拦截器
void addFilter(String name, Filter filter, boolean init); //注册拦截器
//根据拦截器链定义创建拦截器链
void createChain(String chainName, String chainDefinition);
void addToChain(String chainName, String filterName); //添加拦截器到指定的拦截器链
//添加拦截器(带配置,指明所需权限或角色)到指定的拦截器链
void addToChain(String chainName, String filterName,
String chainSpecificFilterConfig) throws ConfigurationException;
NamedFilterList getChain(String chainName); //获取拦截器链
boolean hasChains(); //是否有拦截器链
Set getChainNames(); //得到所有拦截器链的名字
//使用指定的拦截器链代理原始拦截器链
FilterChain proxy(FilterChain original, String chainName);
}
解析以下配置文件:filters定义了拦截器,filterChainDefinitions定义了拦截器链,/login为拦截器链名字,user,sysUser是拦截器名字。
…… /login = authc /logout = logout /authenticated = authc /** = user,sysUser 前面说过PathMatchingFilterChainResolver和DefaultFilterChainManager不能满足我们的需求,我们稍微扩展一下。
- CustomPathMatchingFilterChainResolver:本例自定义的Resolver;返回匹配当前url的多条拦截器链,默认的只能返回第一条。
public class CustomPathMatchingFilterChainResolver
extends PathMatchingFilterChainResolver { //继承自默认解析器
private CustomDefaultFilterChainManager customDefaultFilterChainManager;
//Manager setter
public void setCustomDefaultFilterChainManager(
CustomDefaultFilterChainManager customDefaultFilterChainManager) {
this.customDefaultFilterChainManager = customDefaultFilterChainManager;
setFilterChainManager(customDefaultFilterChainManager);
}
//为当前url匹配拦截器链,匹配多条,而不是遇到一条就返回
@Override
public FilterChain getChain(ServletRequest request, ServletResponse response,
FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
//遍历配置文件all拦截器链
String requestURI = getPathWithinApplication(request);
List chainNames = new ArrayList(); //拦截器链集合
for (String pathPattern : filterChainManager.getChainNames()) {
if (pathMatches(pathPattern, requestURI)) {
//遇到匹配的不急着返回,而是添加到集合
chainNames.add(pathPattern);
}
}
//集合为空则返回null
if(chainNames.size() == 0) {
return null;
}
return customDefaultFilterChainManager.proxy(originalChain, chainNames);
}
}
- CustomDefaultFilterChainManager:本例自定义的Manager;主要是改变注册拦截器链的方式,比如说吧数据库中的合并进来。
public class CustomDefaultFilterChainManager extends DefaultFilterChainManager {
//配置文件中all拦截器链,即默认的静态拦截器链,会与数据库中动态的拦截器链合并
private Map filterChainDefinitionMap = null;
//登录地址
private String loginUrl;
//登录成功后默认跳转地址
private String successUrl;
//未授权跳转地址
private String unauthorizedUrl;
public CustomDefaultFilterChainManager() {
//调用父类方法,注册默认拦截器
setFilters(new LinkedHashMap());
//注册默认拦截器链
setFilterChains(new LinkedHashMap());
addDefaultFilters(true);
}
//省略setter
//注册我们自定义的拦截器,相当于ShiroFilterFactoryBean的filters属性
//只要是我们在配置文件中声明注册bean的,不管shiro本身提供的还是我们自定义的拦截器,
//都算自定义拦截器
public void setCustomFilters(Map customFilters) {
for(Map.Entry entry : customFilters.entrySet()) {
addFilter(entry.getKey(), entry.getValue(), false);
}
}
//解析配置文件中all拦截器链,相当于ShiroFilterFactoryBean的filterChainDefinitions
public void setDefaultFilterChainDefinitions(String definitions) {
Ini ini = new Ini();
ini.load(definitions);
Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
if (CollectionUtils.isEmpty(section)) {
section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
}
setFilterChainDefinitionMap(section);
}
@PostConstruct //Spring容器启动时自动调用
public void init() {
给拦截器配置上属性,这里是三个url
Map filters = getFilters();
if (!CollectionUtils.isEmpty(filters)) { //判空
//注册拦截器
for (Map.Entry entry : filters.entrySet()) {
String name = entry.getKey(); //拦截器名
Filter filter = entry.getValue(); //配置,即角色或权限
applyGlobalPropertiesIfNecessary(filter); //给拦截器设置三个url
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
addFilter(name, filter, false);
}
}
//将拦截器构建成拦截器链
Map chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) { //判空
for (Map.Entry entry : chains.entrySet()) {
String url = entry.getKey(); //如,/**
String chainDefinition = entry.getValue(); //如,authc,user
createChain(url, chainDefinition); //创建过滤器链
}
}
}
protected void initFilter(Filter filter) {
//ignore,因为交给Spring管理了,因此Filter的相关配置会在Spring配置中完成
}
//组合多个拦截器链为一个新的FilterChain代理
public FilterChain proxy(FilterChain original, List chainNames) {
//chainNames中的元素,如,/index、/**
NamedFilterList configured = new SimpleNamedFilterList(chainNames.toString());
//遍历过滤器链名,找到对应的过滤器链,添加到集合
for(String chainName : chainNames) {
configured.addAll(getChain(chainName));
}
//再和原始的过滤器链合并
return configured.proxy(original);
}
private void applyGlobalPropertiesIfNecessary(Filter filter) {
applyLoginUrlIfNecessary(filter);
applySuccessUrlIfNecessary(filter);
applyUnauthorizedUrlIfNecessary(filter);
}
private void applyLoginUrlIfNecessary(Filter filter) {
...配置loginUrl...
}
private void applySuccessUrlIfNecessary(Filter filter) {
...配置SuccessUrl...
}
private void applyUnauthorizedUrlIfNecessary(Filter filter) {
...配置UnauthorizedUrl...
}
}
spring-config-shiro.xml: 其中拦截器链的配置较特别。
/login = authc /logout = logout /authenticated = authc /** = user,sysUser 测试:
- 访问/urlFilter;
- 跳转登录页面,登录后点击新增,增加以下拦截器链:
不用分配任何权限。
- 再访问/user则要求用户拥有aa角色,因此跳转未授权页面。
- 在数据库中将aa角色添加给用户,再访问/user则可以访问了。
- 代码实例:ideaProjects/shiro-chapter19
- 《跟我学Shiro》