Spring 安全入门






4.75/5 (13投票s)
本文解释了 Spring Security 命名空间配置的核心概念,并说明了在 Web 应用程序中进行简单基于表单的身份验证所需的设置。
引言
Spring Security 是一个 Java/J2EE 框架,为企业应用程序提供高级安全功能。该框架最初是作为“Acegi Security Framework”开始的,后来被 Spring 采纳为其子项目“Spring Security”。Spring Security 针对两个领域:身份验证 (Authentication) 和授权 (Authorization)。本文解释了 Spring Security 的概念以及它在框架中的建模方式。本文仅关注 Spring 2.0 中引入的命名空间配置。本文假定读者了解 Java 和 Spring 的基础知识。(本文不涵盖支持类的所有实现,仅涵盖命名空间配置使用的默认实现。它也不涵盖 LDAP、CAS 等所有身份验证模型。仅涵盖 HTTP 表单身份验证。在授权方面,它不涵盖对 ACL 等单个域对象实例的访问授权。)
Spring Security 概念
Spring Security 围绕安全性的两个核心领域——身份验证和授权——进行工作。
“身份验证”是指用户实际上是他声称的那个人,例如,当用户登录任何应用程序并提供其凭据时,他就在进行身份验证。在身份验证级别,Spring 支持各种身份验证模型,例如 HTTP Basic 身份验证、基于表单的身份验证。
“授权”是指用户只被允许访问他被授权使用的资源。例如,在企业应用程序中,某些部分只有管理员可以访问,而某些部分所有员工都可以访问。这些访问规则由授予系统每个用户的访问权限决定。在授权级别,Spring 针对三个主要领域:授权 Web 请求、授权是否可以调用方法以及授权对单个域对象实例的访问。
入门
要开始实现,项目类路径中需要存在以下 jar 包。
- 核心 - spring-security-core.jar
- Web - spring-security-web.jar
- 配置 - spring-security-config.jar
命名空间配置
Spring 的命名空间配置提供了许多快捷方式,隐藏了框架的许多复杂性。要开始此配置,请在 web.xml 中定义一个安全过滤器,如下所示:
<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>
在上述配置中,DelegatingFilterProxy
将控制权委托给一个过滤器实现,该实现被定义为一个名为 springSecurityFilterChain
的 bean。此 bean 是处理命名空间配置的基础设施内部 bean。完成此配置后,所有传入的请求都会进入 Spring 框架进行安全检查。
安全配置
安全配置在 XML 文件中完成,可以有任何名称,例如 applicationContext-security.xml。需要从 web.xml 中显式加载此文件。这是通过添加 ContextLoadListener
来完成的。需要在 web.xml 中安全过滤器定义之前添加以下几行:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/applicationContext-security.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
applicationContext-security.xml
使用命名空间配置时,spring-config.jar 需要存在于类路径中。此 XML 文件中的第一行是模式定义,如下所示:
<beans xmlns="http://www.springframework.org/schema/security"
xmlns:bean="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.3.xsd">
...
</beans>
最小的命名空间配置如下:
<http auto-config='true'>
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="testadmin" password="testadminpassword"
authorities="ROLE_USER, ROLE_ADMIN" />
<user name="testuser" password="testuserpassword" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
上述配置声明应用程序中的所有 URL 都将被拦截以进行安全检查,并且只能由具有 ROLE-USER
角色的用户访问。属性 "auto-config=true
" 定义了三个元素 <form-login/>
、<http-basic/>
和 <logout>
。默认配置始终选择 http-basic 身份验证模型。如果需要将模型更改为 form-login 模型,则需要以下配置:
<http auto-config='true'>
<intercept-url pattern="/login.jsp*" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page='/login.jsp'/></http>
此配置用于启用 form-login 身份验证模型,其中登录页面是 login.jsp。请注意,在 intercept 标签中,为 login.jsp 提供了模式,并将访问规则定义为 IS_AUTHENTICATED_ANONYMOUSLY
。这意味着 login.jsp 不会进行安全检查,这是有道理的,因为 login.jsp 是用户进行身份验证的起点。
<authentication-manager>
标签处理身份验证信息;<authentication-provider>
定义每个用户的凭据信息和授予的角色(身份验证信息)。
上述配置定义了非常基础的安全方法。但是,当需要根据业务需求进行定制时,理解内部发生的事情非常重要。Spring Security 框架是一个过滤器链,每个过滤器都有特定的职责。下一节将深入探讨命名空间配置到 bean 配置,以理解每个过滤器的流程和职责。
命名空间配置到 bean 配置
命名空间配置中的 <http>
块会调用过滤器链。web.xml 中定义的 DelegatingFilterProxy
会调用 FilterChainProxy
类,该类进而调用为每个 URL 模式定义的过滤器链。
FilterChainProxy
调用过滤器链如下所示:
要在 applicationContext-security.xml 中定义 FilterChainProxy
,需要定义一个 bean。此 bean 可以有任何名称;要记住的唯一一件事是,它应该是 web.xml 中定义的 bean springSecurityFilterChain
的别名。更改模式定义以使 bean 成为默认。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="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.3.xsd">
...
</beans>
定义别名
<alias name="filterChainProxy" alias="springSecurityFilterChain"/>
过滤器链代理的定义
<bean id="filterChainProxy"
class="org.springframework.security.web.FilterChainProxy">
<security:filter-chain-map path-type="ant">
<security:filter-chain pattern="/login.jsp*" filters="none"/>
<security:filter-chain pattern="/**" filters="
securityContextFilter, logoutFilter, formLoginFilter, requestCacheFilter,
servletApiFilter, anonFilter, sessionMgmtFilter,
exceptionTranslator, filterSecurityInterceptor" />
</security:filter-chain-map> </bean>
上述配置声明,对于 URL 模式 login.jsp,不应应用任何过滤器;对于所有其他 URL 模式,应应用上述过滤器。所有这些 bean 都应在配置文件中定义为 bean。上述配置中定义的所有过滤器都按它们定义的顺序调用。
SecurityContextPersistenceFilter (org.springframework.security.web.context)
此过滤器每请求只执行一次。在身份验证过程开始之前,此过滤器会从 SecurityContextRepository
加载现有上下文;如果上下文不存在,它会创建一个新上下文。Context
是 SecurityContext
类的一个实例,它存储身份验证的详细信息,例如凭据信息、授予用户的权限。所有这些信息都封装在 Authentication
对象中。SecurityContextRepository
是一个持有上下文的类,这些信息可以根据需要存储在会话、数据库或任何其他地方。默认配置是使用 HttpSessionSecurityContextRepository
的 HTTP 会话。如果需要任何定制,可以通过实现 SecurityContextRepository
来编写自定义存储库。请求处理完成后,过滤器会将上下文信息再次保存到存储库。类图如下:
SecurityContextPersistenceFilter
的配置如下所示:
<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" />
LogoutFilter (org.springframework.security.web.authentication.logout)
此过滤器负责用户注销。默认情况下,它只调用一个 LogoutHandler
,即 SecurityContextLogoutHandler
,它会清除 SecurityContext
并使会话失效(如果允许此操作)。可以通过实现 LogoutHandler
接口来编写自定义处理程序。
类图如下:
LogoutFilter
的配置如下所示:
<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>
UsernamePasswordAuthenticationFilter (org.springframework.security.web.authentication)
此过滤器执行身份验证过程。在命名空间配置中,当配置了基于表单的身份验证时,默认会调用 UsernamePasswordAuthenticationFilter
。它所需的参数名称和它监听的 URL 都可以通过设置此类属性来配置。此过滤器还需要设置 AuthenticationManager
来执行身份验证过程。身份验证过程的可能结果是用户成功验证或未成功验证。
成功验证后,默认会调用 SavedRequestAwareAuthenticationSuccessHandler
。此处理程序首先检查存储在 RequestCache
中的客户端的原始请求,然后将请求重定向到该 URI。如果设置了 "alwaysUseDefaultTargetUrl
" 属性,则成功验证后会重定向到默认 URL。要设置自定义 URL,需要设置 "targetUrlParameter
" 属性。可以通过实现 AuthenticationSuccessHandler
接口来编写自定义成功处理程序。
身份验证失败时,默认会调用 SimpleUrlAuthenticationFailureHandler
。此处理程序会检查是否设置了 "defaultFailureUrl
" 属性,如果设置了,则会重定向到该 URL,否则会向客户端发送 401 响应。401 代表未经授权的请求。还有一个名为 ExceptionMappingAuthenticationFailureHandler
的该处理程序的子类,通过使用它,可以将异常类型映射到应重定向到的 URL。为此,类路径中应存在 exceptionMapping
属性文件。
类图如下:
UsernamePasswordAuthenticationFilter
的配置如下所示:
<bean id="formLoginFilter"
class="com.tcs.integrated.internals.security.UsernamePasswordAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="usernameParameter" value="username "/>
<property name="passwordParameter" value="password"/>
<property name="authenticationSuccessHandler">
<bean class="
org.springframework.security.web.authentication.
SavedRequestAwareAuthenticationSuccessHandler ">
<property name="alwaysUseDefaultTargetUrl" value="true"/>
<property name="defaultTargetUrl" value="/success.jsp"/>
</bean>
</property>
<property name="authenticationFailureHandler">
<bean class=" org.springframework.security.web.
authentication.SimpleUrlAuthenticationFailureHandler "/>
</property>
</bean>
在上述配置中,使用了 UsernamePasswordAuthenticationFilter
。此过滤器将监听 j_spring_security_check
进行身份验证。成功验证后,将调用 SavedRequestAwareAuthenticationSuccessHandler
,它会将用户重定向到 success.jsp。
AuthenticationManager
AuthenticationManager
处理身份验证请求。它有各种实现,默认是 ProviderManager
。ProviderManager
遍历 AuthenticationProvider
列表。如果其中任何一个提供程序返回非 null
响应,则用户成功验证。如果没有任何提供程序返回非 null
响应,则会抛出 ProviderNotFoundException
。如果所有提供程序或最后一个提供程序都抛出 AuthenticationException
,则用户未成功验证,并获得 401 HTTP 状态码。AuthenticationProvider
有各种实现,例如 DAOAuthenticationProvider
。DAOAuthenticationProvider
利用 UserDetailsService
从 Authentication
请求中查找给定用户名的用户名、密码和 GrantedAuthority
。UserDetailsService
的一些实现是 JDBCDaoImpl
和内存身份验证。内存身份验证是最简单的配置,其中凭据信息直接在 config 文件中提供,如下所示。在 JDBCDaoImpl
中,信息从数据库中获取。
<bean id="authenticationManager"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonProvider" />
</list>
</property>
</bean>
<bean id="daoAuthenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="inMemoryDaoImpl"/>
</bean>
<bean id="inMemoryDaoImpl"
class=”org.springframework.security.core.userdetails.memory.InMemoryDaoImpl”>
<property name="userMap">
<value>test=testpassword,enabled,ROLE_USER</value>
</property>
</bean>
<bean id="anonProvider"
class="org.springframework.security.providers.anonymous.
AnonymousAuthenticationProvider">
<property name="key" value="myapp" />
</bean>
上述配置定义了一个 AuthenticationManager
实现 ProviderManager
,它使用两个提供程序 DaoAuthenticationProvider
和 AnonymousProvider
。DaoAuthenticationProvider
使用 InMemoryDaoImpl
从给定的 Authentication
请求中获取用户凭据信息。
RequestCacheAwareFilter (org.springframework.security.web.savedrequest)
此过滤器从 RequestCache
中检索请求,如果当前请求在缓存中找到,则会将包装后的请求转发给下一个过滤器,否则会将原始请求转发给下一个过滤器。
类图如下:
RequestCacheAwareFilter
的配置如下所示:
<bean id="requestCacheFilter" class="org.springframework.security.
web.savedrequest.RequestCacheAwareFilter" />
SecurityContextHolderAwareRequestFilter(org.springframework.security.web.servletapi)
此过滤器用实现了安全特定方法的包装器来包装 ServletRequest
。这对于从请求对象访问身份验证详细信息是必需的。当请求从安全框架转发到 Spring MVC 框架时,需要此包装器请求对象来检查授权详细信息。此包装器实现了以下方法:
getRemoteUser()
:获取主体名称getUserPrincipal()
:获取Authentication
对象isUserInRole()
:如果用户被授予了某些角色,则返回true
,否则返回false
类图如下:
SecurityContextHolderAwareRequestFilter
的配置如下所示:
<bean id="servletApiFilter" class="org.springframework.security.web.
servletapi.SecurityContextHolderAwareRequestFilter"/>
AnonymousAuthenticationFilter(org.springframework.security.web.authentication)
如果安全上下文中的身份验证对象为 null
,则此过滤器会填充它。当某些 URL 不需要用户进行身份验证时,就会出现这种情况,例如 login.jsp。对于此类 URI,会定义一个匿名用户,并为其授予匿名角色。定义如下:
<bean id="anonFilter"
class="org.springframework.security.web.authentication.
AnonymousAuthenticationFilter" >
<property name="key" value="SomeUniqueKeyForThisApplication" />
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS" />
</bean>
上述配置声明了 anonFilter
bean,它有两个参数,key
可以是应用程序特定的任何值,并且 AnonymousAuthenticationProvider
在处理身份验证时将使用此 key
。在 userAttributes
中定义了主体以及授予该主体的权限。AnonymousAuthenticationProvider
的配置:
<bean id="anonymousAuthenticationProvider"
class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>
SessionManagementFilter(org.springframework.security.web.session)
此过滤器每请求只应用一次。借助此过滤器,可以执行各种与会话相关的活动。例如,为了防范会话固定攻击,可以在每次成功身份验证后更改与请求关联的 sessionID
。SessionFixationProtectionStrategy
是执行此更改的类。或者,如果应用程序支持每个会话并发用户,则 ConcurrentSessionControlStrategy
是执行与并发用户相关检查的类。如果允许的每个会话用户数超出限制,它将抛出 SessionAuthenticationException
,否则允许用户继续。它需要 SecurityContextRepository
来检查用户是否已验证。bean 的定义如下:
类图如下:
SessionManagementFilter
的配置如下所示:
<bean id="sessionMgmtFilter"
class="org.springframework.security.web.session.SessionManagementFilter">
<constructor-arg ref="customSecurityContextRepository"/>
</bean>
ExceptionTranslationFilter (org.springframework.security.web.access)
此过滤器处理从过滤器链中抛出的 AuthenticationException
和 AccessDeniedException
。如果抛出 AuthenticationException
,它会启动其 "authenticationEntryPoint
" 属性定义的 entryPoint
。如果抛出 AccessDeniedException
且用户是匿名用户,则会启动 authenticationEntryPoint
。否则,AccessDeniedHandlerImpl
将返回 403(访问被拒绝)状态码。
类图如下:
ExceptionTranslationFilter
的配置如下所示:
<bean id="exceptionTranslator"
class="org.springframework.security.web.access.ExceptionTranslationFilter">
<property name="requestCache" ref="myRequestCache"/>
<property name="authenticationEntryPoint">
<bean class="org.springframework.security.web.authentication.
LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>
</property>
</bean>
<bean id ="myRequestCache"
class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
</bean>
FilterSecurityInterceptor(org.springframework.security.web.access.intercept)
此拦截器根据配置的参数执行对请求的安全授权检查。此拦截器中设置的元数据(如下所示)声明了 URL 模式以及访问资源所需的授权角色。
类图如下:
FilterSecurityInterceptor
的配置如下所示:
<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="securityMetadataSource">
<security:filter-security-metadata-source>
<security:intercept-url pattern="/user/**" access="ROLE_USER,ROLE_ADMIN"/>
<security:intercept-url pattern="/login.jsp*" access="ROLE_ANONYMOUS" />
</security:filter-security-metadata-source>
</property>
<property name="accessDecisionManager" ref="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>
上述配置声明,匹配 /user/**
模式的请求需要 ROLE_USER
或 ROLE_ADMIN
角色进行身份验证。任何其他 URL 都可以访问资源,而无需经过安全检查。请注意,在 filterChainProxy
中,我们定义了 login.jsp 的过滤器完全绕过。为此 login.jsp 设置任何访问值都不会有任何效果,因为此拦截器永远不会被调用。授权是通过各种 AccessDecisionManager
实现来完成的。默认是 AffirmativeBased
,它在任何 AccessDecisionVoter
返回肯定响应时授予访问权限。AccessDecisionVoter
有各种实现,负责对授权决策进行投票。默认是 RoleVoter
。RoleVoter
查找 FilterSecurityInterceptor
中定义的元数据。它首先检查定义的访问权限是否以 ROLE_
前缀开头。如果不是,则返回 ACCESS_ABSTAIN
。否则,它会检查定义的角色是否与 Authentication
对象中存在的角色相同。如果匹配,则返回 ACCESS_GRANTED
,否则返回 ACCESS_DENIED
。请注意,所有比较都区分大小写。
结论
Spring Security 框架是一个健壮且定义良好的框架,用于实现安全需求。由于其基于接口的设计,它具有高度的可定制性。本文仅触及了安全框架的默认配置。默认配置有助于理解对定制此框架以满足特定需求至关重要的基础知识。
历史
- 2011 年 9 月 12 日:初始版本