2
votes

I am trying to refactor my Spring MVC xml based project configurations to Spring Boot java based configurations. While setting up shiro configurations as follows:

@Configuration
public class ShiroConfig {

    @Bean
    public Realm realm() {
        JdbcRealm myRealm = new JdbcRealm();
        myRealm.setCredentialsMatcher(sha256Matcher());
        myRealm.setPermissionsLookupEnabled(true);
        myRealm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
        return myRealm;
    }

    @Bean
    public HashedCredentialsMatcher sha256Matcher() {
        HashedCredentialsMatcher sha256Matcher = new HashedCredentialsMatcher();
        sha256Matcher.setHashAlgorithmName("SHA-256");
        return sha256Matcher;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public CacheManager cacheManager() {
        return new MemoryConstrainedCacheManager();
    }       

    @Bean
    public Filter jwtv() {
        return new JWTVerifyingFilter();
    }

    @Bean
    public Filter ljwtv() {
        return new LimitedAccessJWTVerifyingFilter();
    }

    @Bean
    public Filter logout() {
        LogoutFilter logoutFilter = new LogoutFilter();
        logoutFilter.setRedirectUrl("/login.jsp");
        return logoutFilter;
    }

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

        chainDefinition.addPathDefinition("/login", "authc");
        chainDefinition.addPathDefinition("/logout", "logout");
        chainDefinition.addPathDefinition("/my/test/**", "anon");
        chainDefinition.addPathDefinition("/my/xyz/**/abc", "ljwtv");            chainDefinition.addPathDefinition("/my/xyz/**/mno", "ljwtv");
        chainDefinition.addPathDefinition("/my/**", "jwtv");

        return chainDefinition;
    }

}

I am encountering the following error:

Error starting Tomcat context. Exception: org.springframework.beans.factory.BeanCreationException. Message: Error creating bean with name 'filterShiroFilterRegistrationBean' defined in class path resource [org/apache/shiro/spring/config/web/autoconfigure/ShiroWebFilterConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.FilterRegistrationBean]: Factory method 'filterShiroFilterRegistrationBean' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shiroFilterFactoryBean': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: There is no filter with name 'jwtv' to apply to chain [/my/**] in the pool of available Filters. Ensure a filter with that name/path has first been registered with the addFilter method(s).

The custom filer JWTVerifyingFilter is a @Component that extends org.apache.shiro.web.filter.AccessControlFilter. Don't know what I am missing, it all works with the xml configuration. Please Help.

1
Can you put together a sample project on GitHub (or somewhere) and i'll try to take a look - Brian Demers
@BrianDemers github.com/bibekhadka/demo here it is. Thanks for your time. - Bibek Khadka

1 Answers

0
votes

Looks like the Boot integration doesn't add the Filters from the Spring Context. (Pre Boot / Java config, this was typically defined in XML, and wasn't an issue).

The workaround is pretty simple though, just add this bean def:

@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Value("#{ @environment['shiro.loginUrl'] ?: '/login.jsp' }") String loginUrl,
                                                     @Value("#{ @environment['shiro.successUrl'] ?: '/' }") String successUrl,
                                                     @Value("#{ @environment['shiro.unauthorizedUrl'] ?: null }") String unauthorizedUrl,
                                                     SecurityManager securityManager,
                                                     ShiroFilterChainDefinition shiroFilterChainDefinition,
                                                     Map<String, Filter> filterMap) {

    ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

    filterFactoryBean.setLoginUrl(loginUrl);
    filterFactoryBean.setSuccessUrl(successUrl);
    filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

    filterFactoryBean.setSecurityManager(securityManager);
    filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());
    filterFactoryBean.setFilters(filterMap);

    return filterFactoryBean;
}

I also created a PR to add this back into Shiro (once we add tests).

Update:

I don't think your JSP processing is configured correctly, but the redirect problem is caused by your definition of the logout filter. That filter is getting processed on every request (by Spring). You would need to use a FitlerRegistrationBean mapped to a path instead:

@Bean
public FilterRegistrationBean logout() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    LogoutFilter logoutFilter = new LogoutFilter();
    logoutFilter.setRedirectUrl("/login.jsp");
    filterRegistrationBean.setFilter(logoutFilter);
    filterRegistrationBean.addUrlPatterns("/logout.htm");
    return filterRegistrationBean;
}

UPDATE: More fun with automatic filter registration

I think the root of both of these issues is the filters are getting added automatically by Spring, and then also used by Shiro.

If you define the filter bean, and disable it using a FilterRegistrationBean Spring does NOT automatically add the filter (IIRC). This means Shiro can manage the filter chain (which is what we want to happen)

Using the following snippet: ``` @Bean public Filter jwtv() { return new JWTVerifyingFilter(); }

@Bean
public FilterRegistrationBean jwtvFilterRegBean() {
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(jwtv());
    filterRegistrationBean.setEnabled(false);
    return filterRegistrationBean;
}

```

I was able to trigger your filter when hitting /:

HTTP/1.1 403 Content-Length: 0 Date: Thu, 27 Sep 2018 20:01:26 GMT message: NO TOKEN!

But NOT when hitting /test.

For anyone who is trying to implement custom filters like JWT tokenized authorization: Sample Project