第 15 章 预先认证

预先认证是指用户在进入系统给钱,就已经通过某种机制进行过身份认证,请求中已经附带了身份认证的信息,这时我们只需要从获得这些身份认证信息,并对用户进行授权即可。CAS, X509等都属于这种情况。
Spring Security中专门为这种系统外预先认证的情况提供了工具类,这一章我们来看一下如何使用Pre-Auth处理使用容器Realm认证的用户。

15.1. 为jetty配置Realm

首先在pom.xml中配置jetty所需的Realm。
  <userRealms>
    <userRealm implementation="org.mortbay.jetty.security.HashUserRealm">
      <name>Preauth Realm</name>
      <config>realm.properties</config>
    </userRealm>
  </userRealms>
        
用户,密码,以及权限信息都保存在realm.properties文件中。
admin: admin,ROLE_ADMIN,ROLE_USER
user: user,ROLE_USER
test: test
        
我们配置了三个用户,分别是admin, user和test,其中admin拥有ROLE_ADMIN和ROLE_USER权限,user拥有ROLE_USER权限,而test没有任何权限。
下一步在src/webapp/WEB-INF/web.xml中配置登录所需的安全权限。
<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>Preauth Realm</realm-name>
</login-config>
<security-role>
    <role-name>ROLE_USER</role-name>
</security-role>
<security-role>
    <role-name>ROLE_ADMIN</role-name>
</security-role>
<security-constraint>
    <web-resource-collection>
        <web-resource-name>All areas</web-resource-name>
        <url-pattern>/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>ROLE_USER</role-name>
    </auth-constraint>
</security-constraint>
        
这里我们将login-config中的realm-name配置为Preauth Realm,这与刚刚在pom.xml中配置的名称是相同的。而后我们配置了两个安全权限ROLE_USER和ROLE_ADMIN,最后我们现在访问所有资源都需要使用ROLE_USER这个权限。
自此,服务器与应用中的Realm配置完毕,下一步我们需要使用Spring Security与Realm对接。

15.2. 配置Spring Security

因为使用容器Realm的Pre-Auth并没有对应的命名空间,所以我们只能使用传统方式,一个一个bean的配置了。
首先要配置springSecurityFilterChain,告诉Spring Security我们会使用哪些过滤器。
<bean id="springSecurityFilterChain" class="org.springframework.security.util.FilterChainProxy">
    <sec:filter-chain-map path-type="ant">
        <sec:filter-chain pattern="/**" filters="hscif,j2eePreAuthFilter,etf,fsi"/>
    </sec:filter-chain-map>
</bean>
        
因为将要使用j2eePreAuthFilter,所有默认那些form-login, basic-login, rememberMe都没了用武之地,这里我们只保留HttpSessionContextIntegrationFilter, J2eePreAuthenticatedProcessingFilter, ExceptionTranslationFilter和FilterSecurityInterceptor。其中HttpSessionContextIntegrationFilter用来将session中的用户信息放到SecurityContext中。ExceptionTranslationFilter和FilterSecurityInterceptor负责验证用户权限,并处理验证过程中的异常。
而为了使用j2eePreAuthFilter,我们需要进行如下配置:
<bean id="preAuthenticatedAuthenticationProvider"
    class="org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationProvider">
    <sec:custom-authentication-provider />
    <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
</bean>
<bean id="preAuthenticatedUserDetailsService"
    class="org.springframework.security.providers.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>
<bean id="j2eeMappableRolesRetriever"
    class="org.springframework.security.ui.preauth.j2ee.WebXmlMappableAttributesRetriever">
    <property name="webXmlInputStream">
        <bean factory-bean="webXmlResource" factory-method="getInputStream"/>
    </property>
</bean>
<bean id="webXmlResource" class="org.springframework.web.context.support.ServletContextResource">
    <constructor-arg ref="servletContext"/>
    <constructor-arg value="/WEB-INF/web.xml"/>
</bean>
<bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>
<bean id="j2eePreAuthFilter"
    class="org.springframework.security.ui.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationDetailsSource">
        <bean class="org.springframework.security.ui.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
            <property name="mappableRolesRetriever" ref="j2eeMappableRolesRetriever"/>
            <property name="userRoles2GrantedAuthoritiesMapper">
                <bean class="org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper">
                    <property name="convertAttributeToUpperCase" value="true"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>
<bean id="preauthEntryPoint"
    class="org.springframework.security.ui.preauth.PreAuthenticatedProcessingFilterEntryPoint"/>
        
这里,我们要配置Pre-Auth所需的AuthenticationProvider, EntryPoint, AuthenticatedUserDetailsService并最终组装成一个j2eePreAuthFilter。其中j2eeMappableRolesRetriever会读取我们之前配置的web.xml,从中获得权限信息。
这样,当用户登录时,请求会先被Realm拦截,并要求用户进行登录:
Realm登录
图 15.1. Realm登录

登录成功后,Realm会将用户身份信息绑定到请求中,j2eePreAuthFilter就会从请求中读取身份信息,结合web.xml中定义的权限信息对用户进行授权,并将授权信息录入SecurityContext,之后对用户验证时与之前已没有了任何区别。
这里的preauthEntryPoint会在用户权限不足时起作用,它只会简单返回一个401的拒绝访问响应。
在此我们并不推荐实际中使用这项功能,因为需要对容器进行配置,影响应用的灵活性。