安全任务(例如,用户身份验证和用户查看应用程序资源的授权)通常由应用程序服务器处理。 可以将这些任务委托给Spring安全性流程,以减轻应用程序服务器处理这些任务的负担。 Spring安全性基本上通过实现标准javax.servlet.Filter来处理这些任务。 为了在应用程序中初始化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>
现在,这个过滤器(springSecurityFilterChain)仅将请求委托给Spring安全框架,在此框架中定义的安全任务将由在应用程序上下文中定义的安全过滤器处理。 那么这是怎么发生的呢?
在DelegatingFilterProxy(javax.servlet.Filter的实现)的doFilter方法内,将检查spring应用程序上下文中是否有名为“ springSecurityFilterChain”的bean。 这个'springSecurityFilterChain'bean实际上是为spring过滤器链定义的别名。
<alias name="filterChainProxy" alias="springSecurityFilterChain"/>
因此,当在应用程序上下文中完成检查时,它将返回filterChainProxy bean。 此过滤器链与javax.servlet.FilterChain的链不同,web.xml中定义的Java过滤器使用javax.servlet.FilterChain来调用下一个可能的过滤器(如果存在的话)或将请求传递给servlet / jsp。 bean filterChainProxy包含在Spring应用程序上下文中定义的安全过滤器的有序列表。 所以这是下一组问题:
1.谁初始化/定义这个filterChainProxy?
2.在Spring应用程序上下文中定义了哪些安全过滤器?
3.这些安全过滤器与web.xml中定义的普通过滤器有何不同?
现在是第一个问题,当在应用程序上下文中定义了安全名称空间的‹http›元素时,将初始化filterChainProxy。 这是‹http›元素的基本结构:
<sec:http auto-config="true">
<sec:intercept-url pattern="/**" access="ROLE_USER" />
</sec:http>
<sec:authentication-manager id="authenticationManager">
<sec:authentication-provider>
<sec:user-service>
<sec:user name="admin" password="password" authorities="ROLE_USER, ROLE_ADMIN" />
<sec:user name="user" password="password" authorities="ROLE_USER" />
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
现在,来自Spring框架的HttpSecurityBeanDefinitionParser读取了这个‹http›元素,以在应用程序上下文中注册filterChainProxy。 将auto-config设置为true的http元素实际上是以下内容的简写形式:
<sec:http>
<sec:form-login />
<sec:http-basic />
<sec:logout />
</sec:http>
稍后我们将讨论‹http›的子元素。 因此,现在提到第二个问题,默认情况下,所有过滤器都在过滤器链中注册了什么? 这是Spring文档的答案:
‹http›名称空间块始终创建一个SecurityContextPersistenceFilter , ExceptionTranslationFilter和FilterSecurityInterceptor 。 这些是固定的,不能用替代方法替代。
因此,默认情况下,当我们添加‹http›元素时,将添加以上三个过滤器。 并且由于将auto-config设置为true,BasicAuthenticationFilter,LogoutFilter和UsernamePasswordAuthenticationFilter也被添加到过滤器链中。 现在,如果您查看任何这些过滤器的源代码,它们也是标准的javax.servlet.Filter实现。 但是,通过在应用程序上下文而不是在web.xml中定义这些过滤器,应用程序服务器会将控件转移到Spring以处理与安全性相关的任务。 Spring的filterChainProxy将负责链接将应用于请求的安全过滤器。 这回答了第三个问题。
为了更好地控制要应用于请求的安全筛选器,我们可以定义自己的FilterChainProxy实现。
<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, servletApiFilter, anonFilter, exceptionTranslator,
filterSecurityInterceptor, customFilter1, customeFilter2" />
</sec:filter-chain-map>
</bean>
从上面的xml中,我们看到我们不希望对图像应用任何过滤器,而对于其余的请求,则指定了必须应用的一系列过滤器。 因此,通常,我们按照从最小约束到最大约束的顺序指定过滤器链。 但是通常不需要这种注册我们自己的过滤器链的方法。 Spring通过‹http›元素提供了多个挂钩,通过这些挂钩我们可以更好地控制安全性的应用方式。 因此,我们将详细介绍所有可以通过‹http›元素配置的内容。
1.身份验证:HttpBasicAuthentication和基于表单登录的身份验证
2.通过ACL(访问控制列表)的授权支持
3.注销支持 4.匿名登录支持 5.记住我身份验证 6.并发会话管理
(1)身份验证:身份验证可以通过两种方式处理-HttpBasicAuthentication和基于表单登录的身份验证。 我们将在短期内简要讨论这两个。 在理解这些内容之前,最好对AuthenticationManager有基本的了解,它是通过Spring安全性实现身份验证的核心。 在身份验证管理器元素内,我们定义了可用于该应用程序的所有身份验证提供程序。 身份验证提供程序包含UserDetailsService的实现。 Spring将用户信息加载到UserDetailsService中,并将用户名/密码组合与登录时提供的凭据进行比较。 这是UserDetailsService接口:
package org.springframework.security.core.userdetails;
import org.springframework.dao.DataAccessException;
/**
* Core interface which loads user-specific data.
* It is used throughout the framework as a user DAO and is the strategy used by the
* {@link org.springframework.security.authentication.dao.DaoAuthenticationProvider DaoAuthenticationProvider}.
* The interface requires only one read-only method, which simplifies support for new data-access strategies.
* @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
* @see UserDetails
* @author Ben Alex
*/
public interface UserDetailsService {
/**
* Locates the user based on the username. In the actual implementation, the search may possibly be case
* insensitive, or case insensitive depending on how the implementation instance is configured. In this case, the
* <code>UserDetails</code> object that comes back may have a username that is of a different case than what was
* actually requested..
*
* @param username the username identifying the user whose data is required.
*
* @return a fully populated user record (never <code>null</code>)
*
* @throws UsernameNotFoundException if the user could not be found or the user has no GrantedAuthority
* @throws DataAccessException if user could not be found for a repository-specific reason
*/
UserDetails loadUserByUsername(String username)
throws UsernameNotFo