是否可以在一个弹簧容器中运行具有弹簧安全性的弹簧webmvc webapp?

时间:2013-09-05 22:13:46

标签: java spring security spring-mvc spring-security

这是我的场景:我有一个spring webapp(使用webmvc),我刚刚添加了spring security。我有一个CRUD用户管理器页面,允许具有足够权限的用户添加用户。在服务器上,这由com.myapp.UserController类处理。我也有自己的org.springframework.security.core.userdetails.UserDetailsS​​ervice实现,名为com.myapp.UserDetailsS​​erviceImpl。 UserController和我的UserDetailsS​​ervice impl都使用com.myapp.UserService来跟踪java.util.List中的用户。

我发现当我通过我的网络界面创建新用户,注销,然后尝试使用该新用户重新登录时,找不到该用户。经过仔细检查,这是因为有两个不同的com.myapp.UserService实例。 UserController使用的用户具有新用户,但UserDetailsS​​erviceImpl使用的用户没有。

我跟踪原因归结为当我启动此webapp时两个不同的弹簧容器运行。一个是webmvc(myapp-servlet.xml),一个是安全性(myapp-security.xml)。每个都有自己的UserService实例。我的理解是,这是配置spring webmvc的安全性非常常见的场景:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">

    <servlet>
        <servlet-name>myapp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>myapp</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

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

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/myapp-servlet.xml, /WEB-INF/myapp-security.xml</param-value>
    </context-param>

    <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>/*</url-pattern>
    </filter-mapping>

</web-app>

在我的myapp-servlet.xml中,我正在进行组件扫描,因为它使用@Service进行注释,所以它会选择UserService:

    <context:component-scan base-package="com.myapp" />

但myapp-security.xml也会选择UserService,因为它是我的身份验证管理器配置的一部分:

    <authentication-manager>
        <authentication-provider user-service-ref="userDetailsService" />
    </authentication-manager>

以下是com.myapp.UserDetailsS​​erviceImpl的完整性的相关部分:

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    @Qualifier("testUserService")
    private UserService userService;

    ...
}

我的UserController:

@Controller
@RequestMapping("/admin/users")
public class UserController {

    @Autowired
    @Qualifier("testUserService")
    private UserService userService;

    ...
}

我的问题是:有没有办法将webmvc和安全性结合到一个弹簧容器中,这样我就没有创建UserService的重复实例了?

2 个答案:

答案 0 :(得分:1)

简短回答

从contextConfigLocation上下文参数中删除 /WEB-INF/myapp-servlet.xml

长答案

ContextLoaderListener 根据contextConfigLocation中定义的配置文件创建根应用程序上下文,并在初始化任何Servlet之前将其加载到ServletContext中。

DispatcherServlet同时将使用指定的配置创建子应用程序上下文。您没有明确指定任何bean定义文件,因此按照惯例,它将采用/WEB-INF/appName-servlet.xml(在您的情况下为/WEB-INF/myapp-servlet.xml,偶然存在)。您的根应用程序上下文和子应用程序上下文都会有一些常见的bean(重复,因为它们加载相同的配置文件)。

您有两种选择:

1)从contextConfigLocation中删除/WEB-INF/myapp-servlet.xml。此外,如果您想更明确,请将配置添加到ServletDispatcher声明:

<servlet>
    <servlet-name>myapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/myapp-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>myapp</servlet-name>
    <url-pattern>*.html</url-pattern>
</servlet-mapping>

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

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/myapp-security.xml</param-value>
</context-param>

注意:myapp-servlet.xml中配置的bean只有子应用程序上下文可见,如果您想从根应用程序上下文访问它们,请使用选项2或重新组织配置文件。

2)在根应用程序上下文(当前配置)中加载所有bean,并在调度程序servlet中添加en empty配置参数。

<servlet>
    <servlet-name>myapp</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

答案 1 :(得分:0)

Sergi的回答是正确的。为了完整起见,这是我的工作web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">

    <servlet>
        <servlet-name>myapp</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>myapp</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

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

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/myapp-servlet.xml,/WEB-INF/myapp-security.xml</param-value>
    </context-param>

    <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>/*</url-pattern>
</filter-mapping>