spring web, security + web.xml + mvc dispatcher +

2019-01-07 14:11发布

问题:

I have the Web.xml as below:

 <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/api/secure/*</url-pattern>
</filter-mapping>

[Edit]

After I added the spring security, then I get the error!

java.lang.IllegalStateException: No WebApplicationContext found: no ContextLoaderListener registered?

then I added

 <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/mvc-dispatcher-servlet.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

then it seems working fine, but then 1) The problem is the bean are created twice!

if I only remove that:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/mvc-dispatcher-servlet.xml
    </param-value>
</context-param>

but leave the <listener> then the web application doesn't run at all

[Extra]

The full Web.xml is below:

<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <display-name>Spring MVC Application</display-name>

    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/api/secure/*</url-pattern>
    </filter-mapping>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/mvc-dispatcher-servlet.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

here is my mvc-dispatcher-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                            http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
                            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
                            http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <mvc:annotation-driven/>
    <context:annotation-config/>
    <context:component-scan base-package="com.ge.wtracker"/>
    <context:property-placeholder location="classpath*:META-INF/spring/*.properties"/>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!--Java Persistence API config-->
    <jpa:repositories base-package="com.ge.wtracker.repository"/>

    <!--JPA and Database Config-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="defaultPersistenceUnit"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <bean class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" id="dataSource">
        <property name="driverClassName" value="${database.driverClassName}"/>
        <property name="url" value="${database.url}"/>
        <property name="username" value="${database.username}"/>
        <property name="password" value="${database.password}"/>
        <property name="testOnBorrow" value="true"/>
        <property name="testOnReturn" value="true"/>
        <property name="testWhileIdle" value="true"/>
        <property name="timeBetweenEvictionRunsMillis" value="1800000"/>
        <property name="numTestsPerEvictionRun" value="3"/>
        <property name="minEvictableIdleTimeMillis" value="1800000"/>
        <property name="validationQuery" value="SELECT 1"/>
    </bean>

    <!--Spring Security-->
    <security:http create-session="stateless" entry-point-ref="restAuthenticationEntryPoint" authentication-manager-ref="authenticationManager">
        <security:intercept-url pattern="/api/secure/**" access="ROLE_USER" />
        <security:custom-filter ref="customRestFilter" position="BASIC_AUTH_FILTER" />
    </security:http>
    <!-- Configures the authentication entry point that returns HTTP status code 401 -->
    <bean id="restAuthenticationEntryPoint" class="com.ge.wtracker.web.security.RestAuthenticationEntryPoint">
        <property name="realmName" value="Not Authorized" />
    </bean>
    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref="restAuthenticationProvider" />
    </security:authentication-manager>

    <!--The customRestFilter responsibles for retrieving and manipulating any request data to pass to the authentication
        provider to authenticate.-->
    <bean id="customRestFilter" class="com.ge.wtracker.web.security.RestSecurityFilter">
        <constructor-arg name="authenticationManager" ref="authenticationManager" />
        <constructor-arg name="authenticationEntryPoint" ref="restAuthenticationEntryPoint" />
    </bean>
    <bean id="restAuthenticationProvider" class="com.ge.wtracker.web.security.RestAuthenticationProvider" />

</beans>

回答1:

During the servlet container lifecycle, the container first initializes the ServletContextListener, then the Filter and Servlet instances.

A Spring Web application typically loads two contexts: the root context and the dispatcher servlet context. The ContextLoaderListener class is a ServletContextListener which loads the application (or root) context. It identifies the file to load either through the context-param with the name contextConfigLocation as given in the web.xml like below

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/mvc-dispatcher-servlet.xml
    </param-value>
</context-param>

or, by default, by looking for a file at /WEB-INF/applicationContext.xml. Since you've specified /WEB-INF/mvc-dispatcher-servlet.xml as the contextConfigLocation, that context will be loaded.

Once this is done, the container initializes the DispatcherServlet, which also loads a context. It identifies the file load either through an init-param element with the name contextConfigLocation as given in the web.xml below

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/appServlet/some-random-location.xml</param-value>
    </init-param>
</servlet>

or, by default, by looking for a file at /WEB-INF/name-of-your-servlet-servlet.xml. In other words, it takes the value of the <servlet-name> element and appends -servlet.xml to it and looks for it in WEB-INF.

Since you haven't specified an init-param with name contextConfigLocation, the DispatcherServlet loads the context file at /WEB-INF/mvc-dispatcher-servlet.xml, since its name is mvc-dispatcher. The context loaded by the DispatcherServlet has access to the beans loaded by the ContextLoaderListener, that's why we call that the root context (and the others children).

All this to say that both your ContextLoaderListener and your DispatcherServlet are creating their own copy of an ApplicationContext by each loading a XmlWebApplicationContext from the same file at /WEB-INF/mvc-dispatcher-servlet.xml.

Identify what beans or configuration you think should be available to the whole application and put them in the file that will be loaded by the ContextLoaderListener. Identify the beans or configuration you think should be available to the DispatcherServlet and put them in its context file.



回答2:

http://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch16s02.html

The framework will, on initialization of a DispatcherServlet, look for a file named [servlet-name]-servlet.xml in the WEB-INF directory of your web application and create the beans defined there (overriding the definitions of any beans defined with the same name in the global scope).

So you can remove the context-param:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
        /WEB-INF/mvc-dispatcher-servlet.xml
    </param-value>
</context-param>


回答3:

Spring MVC then create a new any-name.xml and place context specific beans like Spring-security.xml and for your security bean to load.

New xml will be like this

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/beans     
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-3.1.xsd">

 <mvc:annotation-driven/>
  <context:component-scan base-package="com"/>
<task:annotation-driven/> 
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.
config.PropertyPlaceholderConfigurer">
 <property name="location" value=""/>
    <property name="locations">
           <list>
               <value>/WEB-INF/jdbc.properties</value>
           </list>
        </property>
</bean>
<bean id="dataSource"
    class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.databaseurl}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>            
</bean>
</beans>

and now include this in web.xml

  <context-param>
   <param-name>contextConfigLocation</param-name>
    <param-value>  
      /WEB-INF/login-security.xml,
      /WEB-INF/application-context.xml
   </param-value>
 </context-param>

hope this will help you.