shiro自定义过滤器对restful风格实现拦截的问题

前提

现在做一个前后端分离的应用,后台用shiro做权限控制,用jwt代替session的无状态web应用,自定义了一个shiro filter.url用restful风格.所有的接口url都以/api开头,我想让所有这种/api开头的url,如果需要认证就走我自定义的filter.

问题

现在我有四个rul:

  1. /api/user/1 --GET
  2. /api/user --POST
  3. /api/user --PATCH
  4. /api/user --PUT

我现在仅仅想对POST方法做权限控制,让他走我自定义的那个filter,shiro不知道如何控制.

我的自定义filter在shiro.xml中的配置如下:

<bean id="JWTFilter" class="com.test.web.shiro.stateless.JWTStatelessFilter"/>

<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="filters">
        <util:map>
            <entry key="jwt" value-ref="JWTFilter"/>
        </util:map>
    </property>
    <property name="filterChainDefinitions">
        <value>
            /api/**=jwt
            /**=anon
        </value>
    </property>
</bean>

我刚开始想过不配置/api/**=jwt,然后在方法上加@RequiresAuthentication注解,但是这样子,那个方法不走我的JWTFilter,如何让这个需要认证的url走我的自定义filter,该如何配置,求解.谢谢.

阅读 8.7k
3 个回答

按照tomsun28/bootshiro这里的提示,阅读了他的文章:基于shiro的改造集成真正支持restful请求

  1. 重写了PathMatchingFilterChainResolvergetChain方法:
public class RestPathMatchingFilterChainResolver extends PathMatchingFilterChainResolver {

    private static final Logger log = LoggerFactory.getLogger(RestPathMatchingFilterChainResolver.class);

    public RestPathMatchingFilterChainResolver() {
        super();
    }

    public RestPathMatchingFilterChainResolver(FilterConfig filterConfig) {
        super(filterConfig);
    }

    @Override
    public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request);
        String[] urls = null;
        for (String pathPattern : filterChainManager.getChainNames()) {
            urls = pathPattern.split("--");
            if (urls.length == 2) {
                // 分割出url+httpMethod,判断httpMethod和request请求的method是否一致,不一致直接false
                if (WebUtils.toHttp(request).getMethod().toUpperCase().equals(urls[1].toUpperCase())) {
                    pathPattern = urls[0];
                }
            }
            if (pathMatches(pathPattern, requestURI)) {
                if (log.isTraceEnabled()) {
                    log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "].  " +
                            "Utilizing corresponding filter chain...");
                }
                if (urls.length == 2) {
                    pathPattern = pathPattern.concat("--").concat(WebUtils.toHttp(request).getMethod().toUpperCase());
                }
                return filterChainManager.proxy(originalChain, pathPattern);
            }
        }
        return null;
    }

}
  1. 重写了ShiroFilterFactoryBeancreateInstance方法:
public class RestShiroFilterFactoryBean extends ShiroFilterFactoryBean {

    private static final Logger logger = LoggerFactory.getLogger(RestShiroFilterFactoryBean.class);

    public RestShiroFilterFactoryBean() {
        super();
    }

    @Override
    protected AbstractShiroFilter createInstance() throws Exception {
        logger.debug("Creating Shiro Filter instance.");
        SecurityManager securityManager = this.getSecurityManager();
        String msg;
        if (securityManager == null) {
            msg = "SecurityManager property must be set.";
            throw new BeanInitializationException(msg);
        } else if (!(securityManager instanceof WebSecurityManager)) {
            msg = "The security manager does not implement the WebSecurityManager interface.";
            throw new BeanInitializationException(msg);
        } else {
            FilterChainManager manager = this.createFilterChainManager();
            RestPathMatchingFilterChainResolver chainResolver = new RestPathMatchingFilterChainResolver();
            chainResolver.setFilterChainManager(manager);
            return new RestShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager)securityManager, chainResolver);
        }
    }

    private static final class SpringShiroFilter extends AbstractShiroFilter {

        protected SpringShiroFilter(WebSecurityManager webSecurityManager, FilterChainResolver resolver) {
            super();
            if (webSecurityManager == null) {
                throw new IllegalArgumentException("WebSecurityManager property cannot be null.");
            }
            setSecurityManager(webSecurityManager);
            if (resolver != null) {
                setFilterChainResolver(resolver);
            }
        }
    }

}

然后更改shiro配置文件使用自定义的RestShiroFilterFactoryBean:

    <bean id="shiroFilter" class="com.web.shiro.stateless.config.RestShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="filters">
            <util:map>
                <entry key="jwt" value-ref="JWTFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /api/user/test--POST=anon
                /api/**=jwt
                /**=anon
            </value>
        </property>
    </bean>

在这里由于我是写在配置文件中的,所以==的格式不行,换成了--的格式.测试了两个方法,/api/user/test分别有getpost请求,然后post添加不用认证,如上xml配置文件所示,测试通过,暂时只用到认证,以上可以解决认证的问题.
基于shiro的改造集成真正支持restful请求还重写了RestPathMatchingFilterpathsMatch方法,在他的文章中这个类主要是给BJwtFilter继承做授权使用,我现在暂时只用到认证,所以还没有深入了解.
以上已经能够解决同一个url不同的httpMethod请求时的授权问题,如有什么问题,还请大家指出.

我也遇到这个问题了,我是想着对外的api接口都要提供账号、密码来验证,这个怎么做?是在自定义的JWTFilter中,验证账号密码是否正确,是么?方便简单给两行代码看下么?
追问:自定义的JWTFilter你继承的是shiro的哪个filter??

推荐问题
宣传栏