Spring security深入使用
通常,安全任务是由应用服务器完成用户认证和对资源的授权,这些任务也可以委托给Spring security处理这些任务从而减轻应用服务器负担,Spring安全基本上通过实施标准的javax.servlet.Filter来处理这些任务,您需要声明下面的过滤器在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'进行检查。
'springSecurityFilterChain'有一个别名filterChainProxy:<alias name="filterChainProxy" alias="springSecurityFilterChain"/>
下面的问题是:
- 谁初始化/定义此filterChainProxy?
- 哪些安全过滤器在Spring应用程序上下文中定义?
- 这些安全过滤器如何不同于在web.xml中定义的正常过滤器?
filterChainProxy是在应用程序上下文的安全命名空间<HTTP>元素定义时初始化的。这里是<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到Spring的应用上下文. http元素的auto-config设置为真,实际就是处理如下简写:
<sec:http>
<sec:form-login />
<sec:http-basic />
<sec:logout />
</sec:http>
下面回答第二个问题:哪些安全过滤器在Spring应用程序上下文中定义?
该<HTTP>命名空间块总是创建一个SecurityContextPersistenceFilter和一个ExceptionTranslationFilter和FilterSecurityInterceptor。这些都是固定的,不能被替换的替代品。
在默认情况下,当我们添加<HTTP>元素时,上面的三个过滤器将被添加。正如我们已经设置自动配置为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>元素进行配置细节。
下面我们看看Spring是如何完成以下功能:
- 验证: HttpBasicAuthentication 和表单登录验证
- 通过ACL实现授权控制
- 退出
- 匿名登录Anonymous Login support
- Remember-me 验证
- 并发会话管理。
验证
HttpBasicAuthentication和基于表单的身份验证登录 - 身份验证可以通过两种方式来处理。我们要定义适用所有的应用程序的身份验证程序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 UsernameNotFoundException, DataAccessException;
}
Spring提供这个接口的两个实现:
(1)存储用户的 login/password 细节在应用上下文
<sec:authentication-manager id="authenticationManager">
<sec:authentication-provider>
<sec:user-service>
<sec:user name="admin" password="password" authorities="ROLE_ADMIN,ROLE_USER"/>
<sec:user name="user" password="password" authorities="ROLE_USER"/>
</sec:user-service>
</sec:authentication-provider>
</sec:authentication-manager>
‹authentication-provider›实际是配置DaoAuthenticationProvider类,这个Dao实际是UserDetailsService 的实现. 我们提供用户名和密码在XML中,当用户很多时,应该存储在数据库中,相应实现是org.springframework.security.core.userdetails.memory.InMemoryDaoImpl
(2)存储用户的 login/password 细节在数据库
<sec:authentication-manager id="authenticationManager">
<sec:authentication-provider>
<sec:jdbc-user-service data-source-ref="dataSource" />
</sec:authentication-provider>
</sec:authentication-manager>
这对应org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl. 类,如果你看看这个类,你可以发现,用户名和密码都存储在用户表,并且可以分配给用户的角色存储在授权表。下面是这个类从数据库查询获取用户凭据和权限:
-- Fetch user credentials:
select username,password,enabled from users where username = ?
-- Fetch user authorities:
select username,authority from authorities where username = ?
假设你有您的用户详细信息存储在其他一些表的遗留数据库,那么我们可以配置Spring获取用户凭据和权限。说我有一个成员表里面有ID,用户名,密码和角色表有角色。下面是我们如何需要配置:
<sec:authentication-manager id="authenticationManager">
<sec:authentication-provider>
<!-- TBD <password-encoder hash="md5"/> -->
<sec:jdbc-user-service id="userDetailsService" data-source-ref="dataSource"
users-by-username-query=
"SELECT username, password, true as enabled
FROM MEMBER
WHERE username=?"
authorities-by-username-query=
"SELECT member.username, role.role as authorities
FROM ROLE role, MEMBER member
WHERE role.member_id=member.id and member.username=?"/>
</sec:authentication-provider>
</sec:authentication-manager>
下面开始进行验证。
HttpBasicAuthentication是如下配置
<sec:http auto-config="true">
<sec:http-basic />
</sec:http>
默认情况下,启用此选项,浏览器通常会显示用户登录的登录对话框。而不是我们可以配置的登录页面。它最大的问题,大多数浏览器都有倾向于缓存的会话,不同的用户无法在通过刷新浏览器重新登录。
<http-basic>实际上定义了一个BasicAuthenticationFilter过滤器。一旦认证成功,认证对象将被投入Spring的SecurityContext。安全上下文可以通过类SecurityContextHolder进行访问。下面是优化BasicAuthenticationFilter bean声明:
<sec:custom-filter position="BASIC_AUTH_FILTER" ref="basicAuthenticationFilter" />
<bean id="basicAuthenticationFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</bean>
<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>
基于表单的验证
<sec:form-login login-page="/login.jsp"/>激活表单认证。
Spring提供了多个挂钩。目标URL指定用户进行身份验证后应该去页面,如果和身份验证失败,url定义认证失败的页面。
<sec:form-login login-page="/login.jsp" default-target-url="/app/messagePost"
authentication-failure-url="/login.jsp?error=true"/>
更多属性有: always-use-default-target, authentication-success-handler-ref and authentication-failure-handler-ref.authentication-success-handler-ref gets called on successful authentication and authentication-failure-handler-ref
这里是AuthenticationSuccessHandler 和AuthenticationFailureHandler.接口:
public interface AuthenticationSuccessHandler {
void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException;
}
public interface AuthenticationFailureHandler {
void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException;
}
Spring内建了两个实现SimpleUrlAuthenticationSuccessHandler 和SavedRequestAwareAuthenticationSuccessHandler分别实现这两个接口。
SavedRequestAwareAuthenticationSuccessHandler的目的是将用户带回定向到登录页面的之前的那个页面, ‹form-login› 缺省是定义回到之前的那个页面,我们可以优化定制登录成功后重定向到我们指定的那个页面。
SimpleUrlAuthenticationFailureHandler和ExceptionMappingAuthenticationFailureHandler是进行失败处理的,后者继承前者。
只是在SimpleUrlAuthenticationFailureHandler 时规定一个出错页面的URL,ExceptionMappingAuthenticationFailureHandler( org.springframework的子类。 security.core.AuthenticationException )我们指定的身份验证类型的异常,用户将被重定向到多个URL 。
当我们定义我们的自定义登录页面,我们分别标记的用户名和密码字段为j_username和j_password和提交操作将默认为j_spring_security_check 。我们也可以配置这些字段名,并通过指定的属性提交行动:分别为username-parameter, password-parameter 和 login-processing-url 。
完整的filter如下:
<sec:custom-filter position="FORM_LOGIN_FILTER" ref="formLoginFilter" />
<bean id="formLoginFilter" class="org.springframework.security.web.authentication.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 "/-->
<bean id="authenticationFailureHandler" class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.security.authentication.BadCredentialsException">/login/badCredentials</prop>
<prop key="org.springframework.security.authentication.CredentialsExpiredException">/login/credentialsExpired</prop>
<prop key="org.springframework.security.authentication.LockedException">/login/accountLocked</prop>
<prop key="org.springframework.security.authentication.DisabledException">/login/accountDisabled</prop>
</props>
</property>
</bean>
</property>
</bean>
问题是用户名和密码是明文。这可以通过使用加密技术进行编码的密码处理。 Spring提供了一个内置该使用身份验证提供程序使用<password-encoder>元素的支持。
<sec:authentication-manager id="authenticationManager">
<sec:authentication-provider>
<sec:password-encoder hash="md5"/>
<sec:jdbc-user-service data-source-ref="dataSource" />
</sec:authentication-provider>
</sec:authentication-manager>
.权限专题