领先一步
VMware 提供培训和认证,助您加速进步。
了解更多随着 Spring Security 2 中安全模式的引入,搭建一个简单的安全应用程序变得容易了许多。在旧版本中,用户必须单独声明和连接所有实现 Bean,导致 Spring 应用程序上下文文件庞大而复杂,难以理解和维护。学习曲线相当陡峭,我至今仍记得,当我于 2004 年开始从事该项目(当时的 Acegi Security)时,花了一段时间才弄明白这一切。从积极的一面来看,这种对框架基本构建块的接触意味着,一旦你成功地组装了一个可用的配置,就几乎不可能不了解重要的类以及它们如何协同工作。反过来,这些知识让你能够充分利用定制的机会,而这正是使用 Spring Security 的最大好处之一。
我们现在有许多 Spring Security 用户,他们从使用命名空间开始,并受益于其提供的简单性和快速开发机会,但当您想超越命名空间提供的功能时,事情就变得更加困难了。这时,您必须开始了解框架架构以及您的自定义类将如何融入其中。您必须知道要扩展哪些类,要实现哪些策略接口以及如何将它们插入。学习曲线仍然存在,它只是转移了。命名空间有意地提供了 Spring Security 所解决问题领域的高级视图,因此它实际上隐藏了实现细节,使得难以了解实际发生的情况。它确实提供了许多扩展点,但无论出于何种原因,您可能觉得需要深入挖掘。
在本文中,我们将探讨一个用于 Web 应用程序的简单命名空间配置,以及它作为一个完整的 Spring Bean 配置会是什么样子。我们不会深入探讨 Bean 的所有细节,但您可以在参考手册和 Javadoc 中找到有关特定类和接口的更多信息。这里的材料主要面向已经熟悉基础知识的现有用户,因此如果您以前没有使用过 Spring Security,则至少应该阅读参考手册中的命名空间章节,并花一些时间查看示例应用程序。
首先,让我们看看我们要替换的命名空间配置。
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<http>
<intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR" />
<intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
<intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/images/*" filters="none" />
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.htm" default-target-url="/home.htm" />
<logout logout-success-url="/logged_out.htm" />
</http>
<authentication-manager>
<authentication-provider>
<password-encoder hash="md5"/>
<user-service>
<user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />
<user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
这是一个相当简单的示例,类似于您在在线示例和项目附带的示例应用程序中看到的。它定义了一个内存中的用户帐户列表用于身份验证,每个用户都有一个权限列表(在本例中是简单的角色)。它还配置了 Web 应用程序中的一组受保护 URL 模式、基于表单的身份验证机制以及对基本注销 URL 的支持。
我们如何用老式的 Bean 配置来重现这一点?对于那些在 Acegi Security 时代的人来说,是时候回忆一下了。
我们首先看
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="authenticationProvider" />
<ref bean="anonymousProvider" />
</list>
</property>
</bean>
<bean id="authenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="passwordEncoder">
<bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
</property>
<property name="userDetailsService" ref="userService" />
</bean>
<bean id="anonymousProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="SomeUniqueKeyForThisApplication" />
</bean>
<sec:user-service id="userService">
<sec:user name="bob" password="12b141f35d58b8b3a46eea65e6ac179e" authorities="ROLE_SUPERVISOR, ROLE_USER" />
<sec:user name="sam" password="d1a5e26d0558c455d386085fad77d427" authorities="ROLE_USER" />
</sec:user-service>
</beans>
在此阶段,我们保留了 `
<bean id="userService" class="org.springframework.security.core.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
bob=12b141f35d58b8b3a46eea65e6ac179e,ROLE_SUPERVISOR,ROLE_USER
sam=d1a5e26d0558c455d386085fad77d427,ROLE_USER
</value>
</property>
</bean>
在这种情况下,命名空间语法更清晰,但您可能希望使用自己的UserDetailsService实现。Spring Security 也有基于标准 JDBC 和 LDAP 的版本。我们还添加了一个AnonymousAuthenticationProvider,它纯粹是为了支持下面 Web 配置中出现的AnonymousAuthenticationFiter。
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<property name="matcher">
<bean class="org.springframework.security.web.util.AntUrlPathMatcher"/>
</property>
<property name="filterChainMap">
<map>
<entry key="/somepath/**">
<list>
<ref local="filter1"/>
</list>
</entry>
<entry key="/images/*">
<list/>
</entry>
<entry key="/**">
<list>
<ref local="filter1"/>
<ref local="filter2"/>
<ref local="filter3"/>
</list>
</entry>
</map>
</property>
</bean>
其中 filter1、filter2 等是应用程序上下文中实现javax.servlet.Filter接口的其他 Bean 的名称。
所以FilterChainProxy将传入请求与过滤器列表匹配,并通过找到的第一个匹配链传递请求。请注意,除了 "/images/*" 模式(它映射到一个空的过滤器链)之外,这些与
由于上述配置有点冗长,因此可以使用更紧凑的命名空间语法来配置一个FilterChainProxy映射,而不会丢失任何功能。上面的等价物将是:
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/somepath/**" filters="filter1"/>
<sec:filter-chain pattern="/images/*" filters="none"/>
<sec:filter-chain pattern="/**" filters="filter1, filter2, filter3"/>
</sec:filter-chain-map>
</bean>
现在,过滤器链被指定为 Bean 名称的有序列表,按过滤器应用的顺序排列。那么,我们最初的命名空间配置会创建哪些过滤器呢?在这种情况下,FilterChainProxy将是
<alias name="filterChainProxy" alias="springSecurityFilterChain"/>
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/images/*" filters="none"/>
<sec:filter-chain pattern="/**" filters="securityContextFilter, logoutFilter, formLoginFilter, requestCacheFilter,
servletApiFilter, anonFilter, sessionMgmtFilter, exceptionTranslator, filterSecurityInterceptor" />
</sec:filter-chain-map>
</bean>
所以里面有九个过滤器,其中一些是可选的,一些是必不可少的。此时,您可以看到您现在暴露在许多命名空间所保护的细节中。您控制着所使用的过滤器及其调用顺序,这两者都是至关重要的。
我们还为 Bean 添加了别名,以匹配之前在web.xml中使用的名称。或者,您也可以直接使用 "filterChainProxy"。
现在我们来看看这九个过滤器 Bean 以及支持它们所需的其他 Bean。
<bean id="securityContextFilter" class="org.springframework.security.web.context.SecurityContextPersistenceFilter" >
<property name="securityContextRepository" ref="securityContextRepository" />
</bean>
<bean id="securityContextRepository"
class="org.springframework.security.web.context.HttpSessionSecurityContextRepository" />
<bean id="logoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter">
<constructor-arg value="/logged_out.htm" />
<constructor-arg>
<list><bean class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler" /></list>
</constructor-arg>
</bean>
<bean id="formLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationSuccessHandler">
<bean class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/index.jsp" />
</bean>
</property>
<property name="sessionAuthenticationStrategy">
<bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy" />
</property>
</bean>
<bean id="requestCacheFilter" class="org.springframework.security.web.savedrequest.RequestCacheAwareFilter" />
<bean id="servletApiFilter" class="org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter" />
<bean id="anonFilter" class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter" >
<property name="key" value="SomeUniqueKeyForThisApplication" />
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
</bean>
<bean id="sessionMgmtFilter" class="org.springframework.security.web.session.SessionManagementFilter" >
<constructor-arg ref="securityContextRepository" />
</bean>
<bean id="exceptionTranslator" class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.htm"/>
</bean>
</property>
</bean>
<bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="securityMetadataSource">
<sec:filter-security-metadata-source>
<sec:intercept-url pattern="/secure/extreme/*" access="ROLE_SUPERVISOR"/>
<sec:intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_FULLY" />
<sec:intercept-url pattern="/login.htm" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<sec:intercept-url pattern="/**" access="ROLE_USER" />
</sec:filter-security-metadata-source>
</property>
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
</bean>
我们再次使用了方便的命名空间元素,filter-security-metadata-source,来创建SecurityMetadataSource实例,该实例由FilterSecurityInterceptor使用,但您可以在此处插入自己的 Bean(请参阅此常见问题以获取示例)。filter-security-metadata-source元素创建了一个DefaultFilterInvocationSecurityMetadataSource.
这个过滤器必须包含在任何过滤器链中。它负责在请求之间存储认证信息(一个SecurityContext实例)。它还负责在请求期间设置存储它的线程局部变量,并在请求完成后清除它。它的默认策略是存储SecurityContext在 HTTP 会话中,因此使用了HttpSessionSecurityContextRepositorybean。
LogoutFilter仅负责处理注销链接(默认为/j_spring_security_logout),清除安全上下文并使会话无效。AnonymousAuthenticationFilter负责为匿名用户填充安全上下文,从而更容易应用默认安全限制,这些限制在某些 URL 上会放宽。例如,在上述配置中,IS_AUTHENTICATED_ANONYMOUSLY属性意味着匿名用户可以访问登录页面(但不能访问其他任何内容)。请查阅手册中关于此主题的章节以获取更多信息。它的使用是可选的,如果您不使用它,可以删除额外的AnonymousAuthenticationProvider。
SecurityContextHolderAwareRequestFilter提供标准的 Servlet API 安全方法,使用一个访问 SecurityContext 的请求包装器。如果您不需要这些方法,可以省略此过滤器。SessionManagementFilter负责在用户*在当前请求期间*认证时(例如,通过“记住我”认证)应用与会话相关的策略。在其默认配置中,它将创建一个新会话(复制现有会话的属性),旨在更改会话标识符,并提供针对会话固定攻击的防御。它还在使用 Spring Security 的并发会话控制时使用。在此配置中,UsernamePasswordAuthenticationFilter是唯一的身份验证机制,并且还注入了SessionFixationProtectionStrategy。这意味着我们可以安全地删除会话管理过滤器。
如果您一直密切关注,您会注意到上述配置中仍然缺少一个 bean 引用。安全拦截器需要配置一个AccessDecisionManager。如果您使用的是命名空间,那么内部会创建一个,尽管您也可以插入一个自定义 bean。没有命名空间,我们需要显式地提供一个。命名空间内部版本的等效项将如下所示:
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.springframework.security.access.vote.RoleVoter"/>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>
这是命名空间注册的另一个 Bean,尽管它不是直接必需的(它可能在某些 JSP 标签中使用)。它允许您查询当前用户是否允许调用特定的 URL。在您的控制器 Bean 中,它可能很有用,用于确定在呈现视图中应提供哪些信息或导航链接。
<bean id="webPrivilegeEvaluator" class="org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator">
<constructor-arg ref="filterSecurityInterceptor" />
</bean>
再次强调,本文的目的不是详细解释所有这些 Bean 的工作原理,而是主要提供一个参考,帮助您从基本的命名空间配置中继续前进,并了解其底层原理。如您所见,它相当复杂!但请记住,可以将相当多的这些 Bean 插入到命名空间配置本身中,现在您可以看到它们实际上将去往何处。既然您知道涉及哪些类,您就知道可以在 Spring Security 参考手册、Javadoc 以及源代码本身中查找更多信息。