(springboot)shiro安全框架自定义过滤器出现的几个疑难杂症解决方案

问题一:多次重复重定向问题(匹配多个过滤器链重复调用其对应过滤器)

问题二:shiro认证时Realm会执行两次

在使用springboot框架整合shiro安全认证框架时踩了很多坑,每次出问题网上都找不到其中的解决方案,这里贴两个我遇到的坑,以及其解决方案给大家,希望大家可以少走弯路。

问题一场景:

1
2
3
4
5
6
7
8
9
10
11
12
		// 自定义拦截器
		Map<String, Filter> customisedFilter = new HashMap<>();
		customisedFilter.put("url", new CustomRolesAuthorizationFilter());
 
		// 配置映射关系
		filterChainDefinitionMap.put("/login", "anon");
		filterChainDefinitionMap.put("/index", "anon");
		filterChainDefinitionMap.put("/unauthorized", "anon");
		filterChainDefinitionMap.put("/doLogout", "logout");
		filterChainDefinitionMap.put("/**", "url");
		shiroFilterFactoryBean.setFilters(customisedFilter);
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

// 配置映射关系
filterChainDefinitionMap.put(“/login”, “anon”);
filterChainDefinitionMap.put(“/index”, “anon”);
filterChainDefinitionMap.put(“/unauthorized”, “anon”);
filterChainDefinitionMap.put(“/doLogout”, “logout”);
filterChainDefinitionMap.put(“/**”, “url”);
shiroFilterFactoryBean.setFilters(customisedFilter);
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

当我访问/login时,会进入anon过滤器 。但是问题出在,他还会额外继续执行url(自定义过滤器),在自定义过滤器中的逻辑是用户没有登录就重定向到/login这个url去,然后又进入anon过滤器,又执行url过滤器,又进行重定向,循环往复造成多次重定向。

问题一解决过程:

翻阅了一些shiro的资料,了解了一下其中过滤器的机制,shiro会对Servlet容器里的FilterChain进行代理,Shiro会通过一个代理类ProxiedFilterChain对Servlet对其进行代理,其中会先进行Shiro中用户自己配置的拦截器配置映射的关系,即先走映射关系中匹配到的url对应的过滤器,然后会去执行Servlet中的FilterChain进行Filter链的执行。

如果有看过底层源码,就会看到一个originalChain这个名词,它就是Servlet保存的FilterChain。也就是说,每次请求都将会先走Shiro的过滤器链,然后再走Servlet的过滤器链。

(springboot)shiro安全框架自定义过滤器出现的几个疑难杂症解决方案

这里我是SpringBoot框架,可以看到控制台在项目启动时自动配置characterEncodingFilter、requestContextFilter等等一些默认过滤器,把它加入一个叫FilterRegistrationBean中,作为一个过滤器链。可以看到红框中,这两个是我自己自定义的过滤器,我将其配置到Shiro中作为Shiro过滤器链使用,但没想到SpringBoot自动把这两个过滤器配置到了FilterRegistrationBean中,并且路径为/*,这也就能理解为什么上面会匹配到anon过滤器之后还会往下执行到我们的自定义过滤器了。

(springboot)shiro安全框架自定义过滤器出现的几个疑难杂症解决方案

这是我两个自定义过滤器的配置Bean,其中过滤器实现了PathMatchingFilter接口,我不知道是不是因为这个,还是什么奇怪的原因才会把它加载到FilterRegistrationBean中,如果有知道为什么的可以在底下评论告知我一下,至今还是对此摸不着头脑。

问题一解决方案:

最后我把这两个Bean注释掉了,在配置Shiro中我直接new出来,不作为Bean交给IOC管理了,这样就解决了问题,Shiro就不会调用额外的自定义过滤器了。

问题二解决过程:

当时因为我需要动态配置映射关系,会从数据库中读取需要映射的url与需要拦截的角色与权限,过滤器链有可能同一个URL匹配多个过滤器(例如permission过滤器和roles过滤器,角色与权限双验证效果),所以我就自定义了PathMatchingFilterChainResolver,并重写了getChain方法,这个方法是用来匹配返回即将调用的过滤器的,在里面我的逻辑是匹配所有匹配到的URL,把所有对应能匹配到的过滤器全部执行,但错误在,我在最底下写了一个匹配规则 /** 匹配authc,逻辑是除了需要权限验证、或是设置了anon、logout过滤器以外的url都要进行登录才可以访问,这就造成了在进行登录的时候,访问一次登录方法,又会去匹配那个/** 继续走一次身份验证,而走身份验证 subject.login 的底层是会走Realm,这就造成了走两次Realm。

问题二解决方案:

造成这种问题很大可能是PathMatchingFilterChainResolver中的getChain逻辑没有写好,ufl映射关系配置不科学造成。

下面贴一个我的getChian方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    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>();
 
        //获取全部的拦截url
        Set<String> chain = filterChainManager.getChainNames();
 
        for (String pathPattern : chain){
            // 匹配所有匹配到的url,装入chainNames作为即将要执行的过滤器集合
            if (pathMatches(pathPattern, requestURI)) {
                chainNames.add(pathPattern);
            }
        }
        //拦截器链的最后一个Url,因为它匹配全部的url,这里不与其他拦截器冲突,剔除/**url的匹配
        Object lastUrl = "/**";
        chainNames.remove(lastUrl);
        //没有匹配到url
        if(chainNames.size() == 0) {
            //为了不与其他拦截器冲突,在全部url都不匹配的情况下才匹配/**
            chainNames.add("/**");
        }
        return customDefaultFilterChainManager.proxy(originalChain, chainNames);
    }

String requestURI = getPathWithinApplication(request);

//要执行的过滤器集合
List<String> chainNames = new ArrayList<String>();

//获取全部的拦截url
Set<String> chain = filterChainManager.getChainNames();

for (String pathPattern : chain){
// 匹配所有匹配到的url,装入chainNames作为即将要执行的过滤器集合
if (pathMatches(pathPattern, requestURI)) {
chainNames.add(pathPattern);
}
}
//拦截器链的最后一个Url,因为它匹配全部的url,这里不与其他拦截器冲突,剔除/**url的匹配
Object lastUrl = “/**”;
chainNames.remove(lastUrl);
//没有匹配到url
if(chainNames.size() == 0) {
//为了不与其他拦截器冲突,在全部url都不匹配的情况下才匹配/**
chainNames.add(“/**”);
}
return customDefaultFilterChainManager.proxy(originalChain, chainNames);
}

基本思路就是匹配除了/**的所有过滤器路径,如果能匹配到,则执行匹配到的过滤器集合,如果一个都没匹配到,才走最后的身份验证。

要了解底层Shiro是怎么进行身份验证,这样出了问题就能通过Debug进行排错,如果项目中需要深度整合Shiro,改写很多Shiro的验证逻辑,需要了解Shiro底层原理,如FilterChainResolver(处理匹配规则)、FilterChainManager(处理匹配到的过滤器链)、PathMatchingFilter(自定义过滤器)、AuthorizingRealm(获取认证相关数据源的地方)、CredentialsMatcher(自定义登录认证逻辑)等等的原理,才能灵活运用Shiro框架。有空写一篇动态URL配置,动态双拦截角色权限的Shiro配置,和与SSO单点登录的WebService服务接口整合实现自定义的登录与权限的逻辑。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们