Spring专题

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是如何完成以下功能:

  1. 验证: HttpBasicAuthentication 和表单登录验证
  2. 通过ACL实现授权控制
  3. 退出
  4. 匿名登录Anonymous Login support
  5. Remember-me 验证
  6. 并发会话管理。

验证

  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>

下页

spring security安全登陆源码下载

基于容器的用户安全认证授权系统

JavaEE 7的Servlet高级应用

使用Shiro实现基于服务的多域身份验证和授权

.权限专题