Search code examples
javaspringspring-mvcspring-bootshiro

Spring Boot with Apache Shiro


I'm currently trying to integrate Apache Shiro to my Spring Boot restful API, but am experiencing some issues and was wondering if anyone could help.

My Application.class:

@Configuration
@EnableTransactionManagement
@EnableAutoConfiguration
@ComponentScan(basePackages = "org.xelamitchell.sophia.server")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

My WebConfig.class:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public DispatcherServlet dispatcherServlet() {

        DispatcherServlet servlet = new DispatcherServlet();
        servlet.setDispatchOptionsRequest(true);

        return servlet;
    }

    @Bean
    public ServletRegistrationBean dispatcherRegistration(DispatcherServlet dispatcherServlet) {

        ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet);
        registration.addUrlMappings("/sophia/*");

        return registration;
    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {

        Map<String, MediaType> types = new HashMap<>();
        types.put("json", APPLICATION_JSON);
        types.put("xml", APPLICATION_XML);

        configurer
            .defaultContentType(APPLICATION_JSON)
            .mediaTypes(types);

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        converters.add(jackson());
        converters.add(jaxb());

        super.configureMessageConverters(converters);
    }

    @Bean
    public MappingJackson2HttpMessageConverter jackson() {

        final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.getObjectMapper()
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
            .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        return converter;
    }

    @Bean
    public Jaxb2RootElementHttpMessageConverter jaxb() {

        final Jaxb2RootElementHttpMessageConverter converter = new Jaxb2RootElementHttpMessageConverter();

        return converter;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter() {

        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setLoginUrl("/sophia/*");
        shiroFilter.setSecurityManager(securityManager());

        Map<String, Filter> filters = new HashMap<>();
        filters.put("anon", new FormAuthenticationFilter());
        filters.put("authc", new FormAuthenticationFilter());
        shiroFilter.setFilters(filters);

        return shiroFilter;
    }

    @Bean
    public org.apache.shiro.mgt.SecurityManager securityManager() {

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(sophiaRealm());

        return securityManager;
    }

    @Bean(name = "sophiaRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public SophiaRealm sophiaRealm() {
        return new SophiaRealm();
    }

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

}

The application start fine, and the logs do show that shiroFilter is being set:

INFO 12:44:44:271 org.springframework.boot.context.embedded.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/sophia/*] INFO 12:44:44:277 org.springframework.boot.context.embedded.FilterRegistrationBean - Mapping filter: 'shiroFilter' to: [/*]

But when I attempt to access /sophia/users I do not get asked to authenticate, the server simply gives me the response.

I was basing my shiroFilter configuration on this question: How to configure Shiro with Spring Boot


Solution

  • Minor changes to the shiroFilter fixed the problem:

    1. Remove shiroFilter.setLoginUrl(String)
    2. Use the following filter chain definition mapping:

    Map<String, Filter> filters = new HashMap<>();
    filters.put("/**", "authcBasic");
    shiroFilter.setFilters(filters);
    

    And magically the whole API is secured with a Basic HTTP authentication. :)

    Uses Shiro's Default Web Filters