Announcement Announcement Module
Collapse
No announcement yet.
Spring MVC w/ Apache Shiro using JavaConfig Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Spring MVC w/ Apache Shiro using JavaConfig

    I've been attempting to user Shiro with SpringMVC and have run into some difficulty. If I use JavaConfig for Spring, I receive 500 errors stating "No WebApplicationContext found: no ContextLoaderListener registered?" If I change to the traditional xml based configuration, everything works fine. As far as I can tell the two configurations are the same.

    Here is the (broken) JavaConfig:

    web.xml
    Code:
    <servlet>
        <servlet-name>main</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
          <param-name>contextClass</param-name>
          <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>com.floyd.springconfig.WebConfig</param-value>
        </init-param>
      </servlet>
     
      <servlet-mapping>
        <servlet-name>main</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
      
      <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
      </filter>
      
      <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    WebConfig.java:
    Code:
    package com.floyd.springconfig;
    
    import org.apache.shiro.spring.LifecycleBeanPostProcessor;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
    import org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver;
    
    import com.floyd.security.DumbRealm;
    
    
    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages="com.floyd.controller")
    public class WebConfig {
    
    	@Bean
    	public FreeMarkerViewResolver viewResolver() {
    		FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
    		resolver.setCache(true);
    		resolver.setPrefix("");
    		resolver.setSuffix(".ftl");
    		return resolver;
    	}
    	
    	@Bean
    	public FreeMarkerConfigurer freeMarkerConfigurer(){
    		FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
    		configurer.setTemplateLoaderPath("/WEB-INF/view/");
    		return configurer;
    	}
    	
    	@Bean
    	public DefaultWebSecurityManager securityManager(){
    		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    		securityManager.setRealm(dumbRealm());
    		return securityManager;
    	}
    	
    	@Bean
    	public DumbRealm dumbRealm(){
    		return new DumbRealm();
    	}
    	
    	@Bean
    	public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
    		return new LifecycleBeanPostProcessor();
    	}
    	
    	@Bean(name="shiroFilter")
    	public ShiroFilterFactoryBean shiroFilter(){
    		ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
    		shiroFilter.setSecurityManager(securityManager());
    		shiroFilter.setLoginUrl("/login");
    		shiroFilter.setSuccessUrl("/");
    		shiroFilter.setUnauthorizedUrl("/unauthorized");
    		shiroFilter.setFilterChainDefinitions("/test = authc");
    		return shiroFilter;
    	}
    	
    	@Bean
    	@DependsOn("lifecycleBeanPostProcessor")
    	public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
    		return new DefaultAdvisorAutoProxyCreator();
    	}
    	
    	@Bean
    	public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
    		AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    		authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
    		return authorizationAttributeSourceAdvisor;
    	}
    }
    And here is the (working) xml configuration:

    web.xml
    Code:
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <servlet>
        <servlet-name>main</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>/WEB-INF/applicationContext.xml</param-value>
        </init-param>
      </servlet>
     
      <servlet-mapping>
        <servlet-name>main</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
      
      <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
      </filter>
      
      <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    And applicationContext.xml
    Code:
    <context:component-scan base-package="com.floyd.controller" /> 
      <mvc:annotation-driven />
     
      <bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
        <property name="cache" value="true"/>
        <property name="prefix" value=""/>
        <property name="suffix" value=".ftl"/>
      </bean>
      
      <bean id="freeMarkerConfigurer" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
      	<property name="templateLoaderPath" value="/WEB-INF/view/" />
      </bean>
      
      <bean id="dumbRealm" class="com.floyd.security.DumbRealm"/>
      
      <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
      	<property name="realm" ref="dumbRealm"/> 
      </bean>
      
      <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
      
      <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login"/>
        <property name="successUrl" value="/"/>
        <property name="unauthorizedUrl" value="/unauthorized"/>
        <property name="filterChainDefinitions">
            <value>
                /test = authc
            </value>
        </property>
      </bean>
      
      <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
      <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
      </bean>
    If I add a ContextLoaderListener to the web.xml of the JavaConfig version, the application fails trying to find applicationContext.xml even though I am not using the XML based configuration.

    Any thoughts? I can continue working using the XML configuration, but I really prefer the JavaConfig.

    Thanks!

    -Jason

  • #2
    You are missing the ContextLoaderListener

    In the java config web.xml you are using only DispatcherServlet to load your spring application config. In the web.xml where you load the xml-config you have defined both a DispatcherServlet and also the ContextLoaderListener.

    The ContextLoaderListener is special. It is used to create a "root" web application context. It will store this root context in the ServletContext as an attribute using the attribute name: org.springframework.web.context.WebApplicationCont ext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

    Since this name is well defined other frameworks (like Spring Security/Apache Shiro etc.) can easily get access to the root application context by looking up this attribute in the ServletContext.

    One bug/problem with your xml-config is that since you have defined the ContextLoaderListener - but not supplied any contextConfigLocation context-param for it to use - it will default to load a configuration file named /WEB-INF/applicationContext.xml. If the file does not exist it will throw an exception during startup.
    This default contextConfigLocation for ContextLoaderListener just happens to be the same value that your DispatcherServlet contextConfigLocation has been configured to load.

    So in your Spring xml-config web.xml you are actually loading everything twice. Once in the DispatcherServlet and once in the ContextLoaderListener.

    It is perfectly valid for Spring to have two application contexts in a web application. In a Spring web application there can be 0 or 1 'root' application context. It is always created by ContextLoaderListener. Apart from the root there can be any number of child application contexts - e.g. created by DispatcherServlet.
    A child context have access to beans in its own application context as well as those defined in the root application context, but not to beans in other child contexts. The root application context only have access to beans in its own context - it does not know anything about its child contexts.
    That way you can isolate parts of your application from each other - rather than having all beans in the same context.

    Typically you would define your "infrastructure" beans and other beans that may need to be made available to all child contexts - such as @Service and @Repository beans - in the root context. Then have your @Controllers e.g. in a child context loaded by a DispatcherServlet.

    If you are writing a small application and don't need separate web-contexts - then all you need to do is to use ContextLoaderListener instead of DispatcherServlet to load your java configuration and you should be able to proceed.

    Something along these lines:
    Code:
        <!-- Root application context -->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
        <context-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </context-param>
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.floyd.springconfig.WebConfig</param-value>
        </context-param>
    Last edited by joensson; Jul 10th, 2013, 03:05 AM. Reason: bad title

    Comment


    • #3
      Thanks joensson! I'll try this out.

      Comment


      • #4
        If anybody is interesting, here is my post about configuring Apache Shiro with Spring and Java config:
        http://beegor.blogspot.com/2013/10/a...work-java.html

        Comment

        Working...
        X