Shiro 动态URL权限管理

JAVA herman 1501浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

这是Shiro 教程的最有一篇文章了,本文将重点学习在实际项目中的动态URL权限管理功能。

用过Spring Security的朋友应该比较熟悉对URL进行全局的权限控制,即访问URL时进行权限匹配;如果没有权限直接跳到相应的错误页面。Shiro也支持类似的机制,不过需要稍微改造下来满足实际需求。不过在Shiro中,更多的是通过AOP进行分散的权限控制,即方法级别的;而通过URL进行权限控制是一种集中的权限控制。本章将介绍如何在Shiro中完成动态URL权限控制。

动态URL实体类

com.xttblog.shiro20.entity.UrlFilter

public class UrlFilter implements Serializable {  
    private Long id;  
    private String name; //url名称/描述  
    private String url; //地址  
    private String roles; //所需要的角色,可省略  
    private String permissions; //所需要的权限,可省略  
}  

表示拦截的URL和角色/权限之间的关系,多个角色/权限之间通过逗号分隔,此处还可以扩展其他的关系,另外可以加如available属性表示是否开启该拦截。

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());  
            }  
        }  
    }  
}   
  • init:Spring容器启动时会调用init方法把在spring配置文件中配置的默认拦截器保存下来,之后会自动与数据库中的配置进行合并。
  • initFilterChains:UrlFilterServiceImpl会在Spring容器启动或进行增删改UrlFilter时进行注册URL拦截器到Shiro。

默认情况下如使用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不能满足我们的需求,我们稍微扩展了一下:

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)进行合并后代理。

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) {  
        //请参考源码  
    }  
}   
  • CustomDefaultFilterChainManager:调用其构造器时,会自动注册默认的拦截器;
  • loginUrl、successUrl、unauthorizedUrl:分别对应登录地址、登录成功后默认跳转地址、未授权跳转地址,用于给相应拦截器的;
  • filterChainDefinitionMap:用于存储如ShiroFilterFactoryBean在配置文件中配置的拦截器链定义,即可以认为是默认的静态拦截器链;会自动与数据库中加载的合并;
  • setDefaultFilterChainDefinitions:解析配置文件中传入的字符串拦截器链配置,解析为相应的拦截器链;
  • setCustomFilters:注册我们自定义的拦截器;如ShiroFilterFactoryBean的filters属性;
  • init:初始化方法,Spring容器启动时会调用,首先其会自动给相应的拦截器设置如loginUrl、successUrl、unauthorizedUrl;其次根据filterChainDefinitionMap构建默认的拦截器链;
  • initFilter:此处我们忽略实现initFilter,因为交给spring管理了,所以Filter的相关配置会在Spring配置中完成;
  • proxy:组合多个拦截器链为一个生成一个新的FilterChain代理。

运行效果

Shiro 权限管理

本文源代码下载链接:http://pan.baidu.com/s/1kV8yGsf 密码:8t5v

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » Shiro 动态URL权限管理