目录贴: 跟我学Shiro目录贴
用过Spring Security的朋友应该比较熟悉对URL进行全局的权限控制,即访问URL时进行权限匹配;如果没有权限直接跳到相应的错误页面。Shiro也支持类似的机制,不过需要稍微改造下来满足实际需求。不过在Shiro中,更多的是通过AOP进行分散的权限控制,即方法级别的;而通过URL进行权限控制是一种集中的权限控制。本章将介绍如何在Shiro中完成动态URL权限控制。
本章代码基于《第十六章 综合实例》,请先了解相关数据模型及基本流程后再学习本章。
表及数据SQL
请运行shiro-example-chapter19/sql/ shiro-schema.sql 表结构
请运行shiro-example-chapter19/sql/ shiro-schema.sql 数据
实体
具体请参考com.github.zhangkaitao.shiro.chapter19包下的实体。
public class UrlFilter implements Serializable { private Long id; private String name; //url名称/描述 private String url; //地址 private String roles; //所需要的角色,可省略 private String permissions; //所需要的权限,可省略 }
表示拦截的URL和角色/权限之间的关系,多个角色/权限之间通过逗号分隔,此处还可以扩展其他的关系,另外可以加如available属性表示是否开启该拦截。
DAO
具体请参考com.github.zhangkaitao.shiro.chapter19.dao包下的DAO接口及实现。
Service
具体请参考com.github.zhangkaitao.shiro.chapter19.service包下的Service接口及实现。
public interface UrlFilterService { public UrlFilter createUrlFilter(UrlFilter urlFilter); public UrlFilter updateUrlFilter(UrlFilter urlFilter); public void deleteUrlFilter(Long urlFilterId); public UrlFilter findOne(Long urlFilterId); public List<UrlFilter> findAll(); }
基本的URL拦截的增删改查实现。
@Service public class UrlFilterServiceImpl implements UrlFilterService { @Autowired private ShiroFilerChainManager shiroFilerChainManager; @Override public UrlFilter createUrlFilter(UrlFilter urlFilter) { urlFilterDao.createUrlFilter(urlFilter); initFilterChain(); return urlFilter; } //其他方法请参考源码 @PostConstruct public void initFilterChain() { shiroFilerChainManager.initFilterChains(findAll()); } }
UrlFilterServiceImpl在进行新增、修改、删除时会调用initFilterChain来重新初始化Shiro的URL拦截器链,即同步数据库中的URL拦截器定义到Shiro中。此处也要注意如果直接修改数据库是不会起作用的,因为只要调用这几个Service方法时才同步。另外当容器启动时会自动回调initFilterChain来完成容器启动后的URL拦截器的注册。
ShiroFilerChainManager
@Service public class ShiroFilerChainManager { @Autowired private DefaultFilterChainManager filterChainManager; private Map<String, NamedFilterList> defaultFilterChains; @PostConstruct public void init() { defaultFilterChains = new HashMap<String, NamedFilterList>(filterChainManager.getFilterChains()); } public void initFilterChains(List<UrlFilter> urlFilters) { //1、首先删除以前老的filter chain并注册默认的 filterChainManager.getFilterChains().clear(); if(defaultFilterChains != null) { filterChainManager.getFilterChains().putAll(defaultFilterChains); } //2、循环URL Filter 注册filter chain 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()); } } } }
1、init:Spring容器启动时会调用init方法把在spring配置文件中配置的默认拦截器保存下来,之后会自动与数据库中的配置进行合并。
2、initFilterChains:UrlFilterServiceImpl会在Spring容器启动或进行增删改UrlFilter时进行注册URL拦截器到Shiro。
拦截器及拦截器链知识请参考《第八章 拦截器机制》,此处再介绍下Shiro拦截器的流程:
AbstractShiroFilter //如ShiroFilter/ SpringShiroFilter都继承该Filter
doFilter //Filter的doFilter
doFilterInternal //转调doFilterInternal
executeChain(request, response, chain) //执行拦截器链
FilterChain chain = getExecutionChain(request, response, origChain) //使用原始拦截器链获取新的拦截器链
chain.doFilter(request, response) //执行新组装的拦截器链
getExecutionChain(request, response, origChain) //获取拦截器链流程
FilterChainResolver resolver = getFilterChainResolver(); //获取相应的FilterChainResolver
FilterChain resolved = resolver.getChain(request, response, origChain); //通过FilterChainResolver根据当前请求解析到新的FilterChain拦截器链
默认情况下如使用ShiroFilterFactoryBean创建shiroFilter时,默认使用PathMatchingFilterChainResolver进行解析,而它默认是根据当前请求的URL获取相应的拦截器链,使用Ant模式进行URL匹配;默认使用DefaultFilterChainManager进行拦截器链的管理。
PathMatchingFilterChainResolver默认流程:
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、循环拦截器管理器中的拦截器定义(拦截器链的名字就是URL模式) for (String pathPattern : filterChainManager.getChainNames()) { //4、如当前URL匹配拦截器名字(URL模式) if (pathMatches(pathPattern, requestURI)) { //5、返回该URL模式定义的拦截器链 return filterChainManager.proxy(originalChain, pathPattern); } } return null; }
默认实现有点小问题:
如果多个拦截器链都匹配了当前请求URL,那么只返回第一个找到的拦截器链;后续我们可以修改此处的代码,将多个匹配的拦截器链合并返回。
DefaultFilterChainManager内部使用Map来管理URL模式-拦截器链的关系;也就是说相同的URL模式只能定义一个拦截器链,不能重复定义;而且如果多个拦截器链都匹配时是无序的(因为使用map.keySet()获取拦截器链的名字,即URL模式)。
FilterChainManager接口:
public interface FilterChainManager { Map<String, Filter> 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<String> getChainNames(); //得到所有拦截器链的名字 FilterChain proxy(FilterChain original, String chainName); //使用指定的拦截器链代理原始拦截器链 }
此接口主要三个功能:注册拦截器,注册拦截器链,对原始拦截器链生成代理之后的拦截器链,比如
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> …… <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> <entry key="sysUser" value-ref="sysUserFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /login = authc /logout = logout /authenticated = authc /** = user,sysUser </value> </property> </bean>
filters属性定义了拦截器;filterChainDefinitions定义了拦截器链;如/**就是拦截器链的名字;而user,sysUser就是拦截器名字列表。
之前说过默认的PathMatchingFilterChainResolver和DefaultFilterChainManager不能满足我们的需求,我们稍微扩展了一下:
CustomPathMatchingFilterChainResolver
public class CustomPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver { private CustomDefaultFilterChainManager customDefaultFilterChainManager; public void setCustomDefaultFilterChainManager( CustomDefaultFilterChainManager customDefaultFilterChainManager) { this.customDefaultFilterChainManager = customDefaultFilterChainManager; setFilterChainManager(customDefaultFilterChainManager); } public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) { FilterChainManager filterChainManager = getFilterChainManager(); if (!filterChainManager.hasChains()) { return null; } String requestURI = getPathWithinApplication(request); List<String> chainNames = new ArrayList<String>(); for (String pathPattern : filterChainManager.getChainNames()) { if (pathMatches(pathPattern, requestURI)) { chainNames.add(pathPattern); } } if(chainNames.size() == 0) { return null; } return customDefaultFilterChainManager.proxy(originalChain, chainNames); } }
和默认的PathMatchingFilterChainResolver区别是,此处得到所有匹配的拦截器链,然后通过调用CustomDefaultFilterChainManager.proxy(originalChain, chainNames)进行合并后代理。
CustomDefaultFilterChainManager
public class CustomDefaultFilterChainManager extends DefaultFilterChainManager { private Map<String, String> filterChainDefinitionMap = null; private String loginUrl; private String successUrl; private String unauthorizedUrl; public CustomDefaultFilterChainManager() { setFilters(new LinkedHashMap<String, Filter>()); setFilterChains(new LinkedHashMap<String, NamedFilterList>()); addDefaultFilters(true); } public Map<String, String> getFilterChainDefinitionMap() { return filterChainDefinitionMap; } public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) { this.filterChainDefinitionMap = filterChainDefinitionMap; } public void setCustomFilters(Map<String, Filter> customFilters) { for(Map.Entry<String, Filter> entry : customFilters.entrySet()) { addFilter(entry.getKey(), entry.getValue(), false); } } 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); } public String getLoginUrl() { return loginUrl; } public void setLoginUrl(String loginUrl) { this.loginUrl = loginUrl; } public String getSuccessUrl() { return successUrl; } public void setSuccessUrl(String successUrl) { this.successUrl = successUrl; } public String getUnauthorizedUrl() { return unauthorizedUrl; } public void setUnauthorizedUrl(String unauthorizedUrl) { this.unauthorizedUrl = unauthorizedUrl; } @PostConstruct public void init() { Map<String, Filter> filters = getFilters(); if (!CollectionUtils.isEmpty(filters)) { for (Map.Entry<String, Filter> entry : filters.entrySet()) { String name = entry.getKey(); Filter filter = entry.getValue(); applyGlobalPropertiesIfNecessary(filter); if (filter instanceof Nameable) { ((Nameable) filter).setName(name); } addFilter(name, filter, false); } } Map<String, String> chains = getFilterChainDefinitionMap(); if (!CollectionUtils.isEmpty(chains)) { for (Map.Entry<String, String> entry : chains.entrySet()) { String url = entry.getKey(); String chainDefinition = entry.getValue(); createChain(url, chainDefinition); } } } protected void initFilter(Filter filter) { //ignore } public FilterChain proxy(FilterChain original, List<String> chainNames) { 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) { //请参考源码 } private void applySuccessUrlIfNecessary(Filter filter) { //请参考源码 } private void applyUnauthorizedUrlIfNecessary(Filter filter) { //请参考源码 } }
1、CustomDefaultFilterChainManager:调用其构造器时,会自动注册默认的拦截器;
2、loginUrl、successUrl、unauthorizedUrl:分别对应登录地址、登录成功后默认跳转地址、未授权跳转地址,用于给相应拦截器的;
3、filterChainDefinitionMap:用于存储如ShiroFilterFactoryBean在配置文件中配置的拦截器链定义,即可以认为是默认的静态拦截器链;会自动与数据库中加载的合并;
4、setDefaultFilterChainDefinitions:解析配置文件中传入的字符串拦截器链配置,解析为相应的拦截器链;
5、setCustomFilters:注册我们自定义的拦截器;如ShiroFilterFactoryBean的filters属性;
6、init:初始化方法,Spring容器启动时会调用,首先其会自动给相应的拦截器设置如loginUrl、successUrl、unauthorizedUrl;其次根据filterChainDefinitionMap构建默认的拦截器链;
7、initFilter:此处我们忽略实现initFilter,因为交给spring管理了,所以Filter的相关配置会在Spring配置中完成;
8、proxy:组合多个拦截器链为一个生成一个新的FilterChain代理。
Web层控制器
请参考com.github.zhangkaitao.shiro.chapter19.web.controller包,相对于第十六章添加了UrlFilterController用于UrlFilter的维护。另外,移除了控制器方法上的权限注解,而是使用动态URL拦截进行控制。
Spring配置——spring-config-shiro.xml
<bean id="filterChainManager" class="com.github.zhangkaitao.shiro.spring.CustomDefaultFilterChainManager"> <property name="loginUrl" value="/login"/> <property name="successUrl" value="/"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="customFilters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> <entry key="sysUser" value-ref="sysUserFilter"/> </util:map> </property> <property name="defaultFilterChainDefinitions"> <value> /login = authc /logout = logout /unauthorized.jsp = authc /** = user,sysUser </value> </property> </bean>
filterChainManager是我们自定义的CustomDefaultFilterChainManager,注册相应的拦截器及默认的拦截器链。
<bean id="filterChainResolver" class="com.github.zhangkaitao.shiro.spring.CustomPathMatchingFilterChainResolver"> <property name="customDefaultFilterChainManager" ref="filterChainManager"/> </bean>
filterChainResolver是自定义的CustomPathMatchingFilterChainResolver,使用上边的filterChainManager进行拦截器链的管理。
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> </bean>
shiroFilter不再定义filters及filterChainDefinitions,而是交给了filterChainManager进行完成。
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="targetObject" ref="shiroFilter"/> <property name="targetMethod" value="setFilterChainResolver"/> <property name="arguments" ref="filterChainResolver"/> </bean>
最后把filterChainResolver注册给shiroFilter,其使用它进行动态URL权限控制。
其他配置和第十六章一样,请参考第十六章。
测试
1、首先执行shiro-data.sql初始化数据。
2、然后再URL管理中新增如下数据:
3、访问http://localhost:8080/chapter19/user时要求用户拥有aa角色,此时是没有的所以会跳转到未授权页面;
4、添加aa角色然后授权给用户,此时就有权限访问http://localhost:8080/chapter19/user。
实际项目可以在此基础上进行扩展。
示例源代码:https://github.com/zhangkaitao/shiro-example;可加群 231889722 探讨Spring/Shiro技术。
相关推荐
在《跟我学Shiro》的第十七章中,作者开涛介绍了如何集成OAuth2,使用Apache Oltu作为OAuth2服务端的实现。实现中涉及以下关键部分: 1. **依赖**:引入了`authzserver`(授权服务器依赖)和`resourceserver`(资源...
《跟我一起学Shiro——张开涛》这本书是针对初学者的优秀教程,旨在帮助读者快速理解和掌握Shiro的基本用法和核心概念。 **1. Shiro基础** Shiro的基础概念包括Subject、Realms、Cryptography和Session。Subject是...
通过《跟我学Shiro》.pdf,你将学习到如何创建 Realm 实现数据源连接、配置 Shiro 安全框架、处理登录和登出逻辑、实现权限控制以及在实际项目中部署和调优 Shiro。 10. **最佳实践** 学习 Shiro 的过程中,了解...
Apache Shiro 是一个强大且易用的 Java 安全框架,提供了认证、授权、加密和会话管理功能,可以非常方便地开发出足够安全的应用。...阅读 "[资料][Java]跟我学Shiro教程.pdf",你将得到更详细的步骤指导和实践案例。
除了基础的URL拦截,Shiro还提供了丰富的API和扩展点,允许开发者自定义复杂的权限逻辑,比如基于业务规则的动态权限控制。同时,Shiro的灵活性使得它可以很好地与其他框架(如Spring Boot、MyBatis等)集成,实现...
Apache Shiro是一个强大的Java安全框架,它为应用程序提供了身份验证(Authentication)、授权...通过阅读"跟我学Shiro教程.pdf",你应该能够了解如何将Shiro集成到你的项目中,以及如何利用它来实现安全控制。
***txt文件中含有下载地址** 《跟我学Shiro》- 张开涛,PDF版本,带目录,清晰。 示例源代码:https://github.com/zhangkaitao/shiro-example; 加qun 231889722 探讨Spring/Shiro技术。
《跟我学Shiro-java开发+spring开发》是一个深入学习Java安全框架Shiro和Spring集成的教程,旨在帮助开发者掌握这两个关键技术在实际项目中的应用。Shiro是一个强大的且易用的Java安全框架,提供了认证、授权、加密...
- **Shiro 权限注解**:使用 Spring 的注解来控制访问权限。 #### 十三、RememberMe - **概念**:允许用户记住登录状态的功能。 - **配置**:通过配置 RememberMe 功能来实现。 #### 十四、SSL - **概念**:使用 ...
Apache Shiro是一个强大易用的Java安全框架,...我找了一版 跟我学Shiro教程PDF,里面讲的很详细.里面还附带了每个章节的源码.值得你收藏哟!饮水思源——原文出自:http://jinnianshilongnian.iteye.com/blog/2049092
《跟我学Shiro第12章Demo:Java SE、Web与Shiro权限注解实践》 Apache Shiro是一款强大的安全框架,广泛应用于Java项目中,提供了身份验证、授权、会话管理和加密等功能。本Demo主要涵盖了Shiro在Java Standard ...
"跟我学Shiro教程"资源包包含了全面学习Shiro所需的重要材料,包括文档和实践示例。 首先,我们来看《Apache_Shiro参考手册中文版.pdf》。这本书籍详细介绍了Shiro框架的各个组件和使用方法。通过阅读,你可以了解...
《跟我学Shiro》PDF完结版下载, Apache Shiro是Java的一个安全框架。目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不...
在"跟我学Shiro第11章Demo"中,我们将深入探讨Shiro的核心组件,特别是其在缓存管理和会话管理中的应用。 首先,我们关注的是Cache缓存。Shiro支持缓存来提高性能,避免频繁的数据库查询。它允许开发者将敏感操作的...
SpringBoot集成Shiro实现动态URI权限是一个常见的权限管理实践,主要目的是为了实现更灵活、更安全的用户访问控制。在Web应用中,权限控制通常包括角色管理、菜单管理、操作权限(URI)管理等,而动态URI权限则允许...