Shiro过滤器原理简介(版本1.6.0)

1.拦截器配置

Shiro默认提供了13个拦截器

public enum DefaultFilter {

anon(AnonymousFilter.class),

    authc(FormAuthenticationFilter.class),

    authcBasic(BasicHttpAuthenticationFilter.class),

    authcBearer(BearerHttpAuthenticationFilter.class),

    logout(LogoutFilter.class),

    noSessionCreation(NoSessionCreationFilter.class),

    perms(PermissionsAuthorizationFilter.class),

    port(PortFilter.class),

    rest(HttpMethodPermissionFilter.class),

    roles(RolesAuthorizationFilter.class),

    ssl(SslFilter.class),

    user(UserFilter.class),

    invalidRequest(InvalidRequestFilter.class);

}

使用时,需要自己重写一个拦截器实现自己的拦截方式,本项目使用了Jwt进行拦截,所以重写一个JwtFilter,里面重写3个方法:

isAccessAllowed    登录验证

executeLogin    具体执行方法

preHandle    访问前处理


2.路径配置

然后是对拦截路径进行配置,Shiro拦截器初始化是在ShiroFilterFactoryBean这个工厂bean中实现,

* @see org.springframework.web.filter.DelegatingFilterProxy DelegatingFilterProxy

* @since 1.0

public class ShiroFilterFactoryBean implements FactoryBean, BeanPostProcessor

所以在项目中需编写一个ShiroConfig配置类,注入这个工厂bean,本项目中代码简写如下,

@Bean("shiroFilter")

public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {

    //构造一个工厂实例

    ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();

    //设置安全处理器

    shiroFilterFactoryBean.setSecurityManager(securityManager);

    // 拦截器

    Map filterChainDefinitionMap =new LinkedHashMap();

    //排除配置文件中不需要过滤的url

    if(oConvertUtils.isNotEmpty(excludeUrls)){

        String[] permissionUrl =excludeUrls.split(",");

        for(String url : permissionUrl){

            filterChainDefinitionMap.put(url,"anon");

        }

    }

// 配置不会被拦截的链接 顺序判断

    filterChainDefinitionMap.put("/sys/cas/client/validateLogin", "anon"); //cas验证登录

    filterChainDefinitionMap.put("/sys/randomImage/**", "anon"); //登录验证码接口排除

    filterChainDefinitionMap.put("/sys/checkCaptcha", "anon"); //登录验证码接口排除

    ......

    // 添加自己的过滤器并且取名为jwt

    Map filterMap =new HashMap(1);

    filterMap.put("jwt", new JwtFilter());

    shiroFilterFactoryBean.setFilters(filterMap);

    // <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边

    filterChainDefinitionMap.put("/**", "jwt");

    // 未授权界面返回JSON

    shiroFilterFactoryBean.setUnauthorizedUrl("/sys/common/403");

    shiroFilterFactoryBean.setLoginUrl("/sys/common/403");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

    return shiroFilterFactoryBean;

}

配置完之后,Spring在启动时会调用ShiroFilterFactoryBean的getObject()方法,

public Object getObject()throws Exception {

if (instance ==null) {

instance = createInstance();

    }

return instance;

}

然后调用创建实例方法

protected AbstractShiroFilter createInstance()throws Exception {

    SecurityManager securityManager = getSecurityManager();

    if (securityManager ==null) {

    String msg ="SecurityManager property must be set.";

        throw new BeanInitializationException(msg);

    }

    if (!(securityManagerinstanceof WebSecurityManager)) {

        String msg ="The security manager does not implement the WebSecurityManager interface.";

        throw new BeanInitializationException(msg);

    }

    FilterChainManager manager = createFilterChainManager();

    PathMatchingFilterChainResolver chainResolver =new PathMatchingFilterChainResolver();

    chainResolver.setFilterChainManager(manager);

    return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);

}

我们进入到加粗的createFilterChainManager()方法中,

protected FilterChainManager createFilterChainManager() {

    DefaultFilterChainManager manager =new DefaultFilterChainManager();

    //获取Shio自定义的那13个过滤器

    Map defaultFilters = manager.getFilters();

    for (Filter filter : defaultFilters.values()) {

        applyGlobalPropertiesIfNecessary(filter);

    }

    //获取自己定义的过滤器JwtFilter

    Map filters = getFilters();

    if (!CollectionUtils.isEmpty(filters)) {

        for (Map.Entry entry : filters.entrySet()) {

            String name = entry.getKey();

            Filter filter = entry.getValue();

            applyGlobalPropertiesIfNecessary(filter);

            if (filterinstanceof Nameable) {

                ((Nameable) filter).setName(name);

            }

            manager.addFilter(name, filter, false);

        }

}

    // set the global filters

    manager.setGlobalFilters(this.globalFilters);

    //这边是对定义的路径进行配置

    Map chains = getFilterChainDefinitionMap();

    if (!CollectionUtils.isEmpty(chains)) {

        for (Map.Entry entry : chains.entrySet()) {

            String url = entry.getKey();

            String chainDefinition = entry.getValue();

            manager.createChain(url, chainDefinition);

        }

}

    // create the default chain, to match anything the path matching would have missed

    manager.createDefaultChain("/**"); 

    return manager;

}

我们重点看加粗的createChain方法,

public void createChain(String chainName, String chainDefinition) {

    if (!StringUtils.hasText(chainName)) {

        throw new NullPointerException("chainName cannot be null or empty.");

    }else if (!StringUtils.hasText(chainDefinition)) {

        throw new NullPointerException("chainDefinition cannot be null or empty.");

    }else {

        if (log.isDebugEnabled()) {

            log.debug("Creating chain [" + chainName +"] with global filters " +this.globalFilterNames +" and from String definition [" + chainDefinition +"]");

        }

    if (!CollectionUtils.isEmpty(this.globalFilterNames)) {

        this.globalFilterNames.stream().forEach((filterName) -> {

                this.addToChain(chainName, filterName);

          });

        }

        String[] filterTokens =this.splitChainDefinition(chainDefinition);

        String[] var4 = filterTokens;

        int var5 = filterTokens.length;

        for(int var6 =0; var6 < var5; ++var6) {

            String token = var4[var6];

            String[] nameConfigPair =this.toNameConfigPair(token);

            this.addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);

        }

}

}

上面那几个报错判断我们忽略,我们看重点的addToChain方法,这里有2个这个方法,上面一个是全局过滤器对过滤路径进行处理,下面那个是自定义过滤器对过滤路径进行的处理,自定义的会覆盖全局的,我们进入这个方法中,

public void addToChain(String chainName, String filterName) {

    this.addToChain(chainName, filterName, (String)null);

}

public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {

    if (!StringUtils.hasText(chainName)) {

        throw new IllegalArgumentException("chainName cannot be null or empty.");

    }else {

    Filter filter =this.getFilter(filterName);

        if (filter ==null) {

            throw new IllegalArgumentException("There is no filter with name '" + filterName +"' to apply to chain [" + chainName +"] in the pool of available Filters.  Ensure a filter with that name/path has first been registered with the addFilter method(s).");

        }else {

            this.applyChainConfig(chainName, filter, chainSpecificFilterConfig);

            NamedFilterList chain =this.ensureChain(chainName);

            chain.add(filter);

        }

}

}

这里最关键的是applyChainConfig这个方法,继续进入这个方法,

protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {

    if (log.isDebugEnabled()) {

        log.debug("Attempting to apply path [" + chainName +"] to filter [" + filter +"] with config [" + chainSpecificFilterConfig +"]");

    }

    if (filterinstanceof PathConfigProcessor) {

        ((PathConfigProcessor)filter).processPathConfig(chainName, chainSpecificFilterConfig);

    }else if (StringUtils.hasText(chainSpecificFilterConfig)) {

        String msg ="chainSpecificFilterConfig was specified, but the underlying Filter instance is not an 'instanceof' " +        PathConfigProcessor.class.getName() +".  This is required if the filter is to accept chain-specific configuration.";

        throw new ConfigurationException(msg);

    }

}

继续进入processPathConfig这个方法,

public abstract class PathMatchingFilterextends AdviceFilterimplements PathConfigProcessor {

    private static final String DEFAULT_PATH_SEPARATOR ="/";

    protected PatternMatcher pathMatcher =new AntPathMatcher();

    protected Map appliedPaths =new LinkedHashMap();

    public PathMatchingFilter() {}

    public Filter processPathConfig(String path, String config) {

        String[] values =null;

        if (config !=null) {

            values = StringUtils.split(config);

        }

        this.appliedPaths.put(path, values);

                return this;

        }

我们发现Shiro最终是把路径全部放入PathMatchingFilter这个类里面的appliedPaths这个Map变量里

3.请求路径拦截

当请求进入系统时,会首先进入我们自定义的JwtFilter的preHandle方法中,我们交给父类去执行,super.preHandle(request, response),可以看到他的父类就是PathMatchingFilter,

protected boolean preHandle(ServletRequest request, ServletResponse response)throws Exception {

    if (this.appliedPaths !=null && !this.appliedPaths.isEmpty()) {

        Iterator var3 =this.appliedPaths.keySet().iterator();

        String path;

        do {

            if (!var3.hasNext()) {

                    return true;

            }

            path = (String)var3.next();

            }while(!this.pathsMatch(path, request));

          Object config =this.appliedPaths.get(path);

          return this.isFilterChainContinued(request, response, path, config);

            }else {

                if (log.isTraceEnabled()) {

                        log.trace("appliedPaths property is null or empty.  This Filter will passthrough immediately.");

                }

                return true;

            }

}

我们发现在这个方法里将之前保存的那些路径和当前请求的路径进行了一一的比对,从而实现了拦截效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,719评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,337评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,887评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,488评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,313评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,284评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,672评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,346评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,644评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,700评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,457评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,316评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,706评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,261评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,648评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,859评论 2 335

推荐阅读更多精彩内容