Shiro的过滤链设计机制

本文详细介绍了如何在Shiro框架中配置JWT拦截器。首先,讲解了Shiro默认提供的13个拦截器,并说明如何自定义JWTFilter以实现登录验证。接着,阐述了路径配置过程,包括在ShiroConfig配置类中设置拦截路径,以及如何排除不需要过滤的URL。最后,分析了请求拦截的执行流程,展示了如何通过PathMatchingFilter实现请求路径的匹配,完成拦截功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;

            }

}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值