第 19 章 预先认证

预先认证是指用户在进入系统之前,就已经通过某种机制进行过身份认证,请求中已经附带了身份认证的信息,这时我们只需要从获得这些身份认证信息,并对用户进行授权即可。CAS, X509等都属于这种情况。

Spring Security中专门为这种系统外预先认证的情况提供了工具类,这一章我们来看一下如何使用Pre-Auth处理使用容器Realm认证的用户。

19.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对接。

19.2. 配置Spring Security

因为使用容器Realm的Pre-Auth并没有对应的命名空间,所以需要去掉auto-config="true"并引用对应的安全入口。

<sec:http entry-point-ref="preauthEntryPoint">
    <sec:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
    <sec:intercept-url pattern="/**" access="ROLE_USER" />
</sec:http>
        

这次我们会使用j2eePreAuthFilter执行用户认证,所有默认那些form-login, basic-login, rememberMe都没了用武之地。

而为了使用j2eePreAuthFilter,我们需要进行如下配置:

<sec:http entry-point-ref="preauthEntryPoint">
    <sec:intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
    <sec:intercept-url pattern="/**" access="ROLE_USER" />
    <sec:custom-filter position="BASIC_AUTH_FILTER" ref="j2eePreAuthFilter" />
</sec:http>

<sec:authentication-manager alias="authenticationManager">
    <sec:authentication-provider ref="preAuthenticatedAuthenticationProvider" />
</sec:authentication-manager>

<bean id="preAuthenticatedAuthenticationProvider"
    class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
    <property name="preAuthenticatedUserDetailsService" ref="preAuthenticatedUserDetailsService"/>
</bean>

<bean id="preAuthenticatedUserDetailsService"
    class="org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesUserDetailsService"/>

<bean id="j2eeMappableRolesRetriever"
    class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever"/>

<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.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="authenticationDetailsSource">
        <bean class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
            <property name="mappableRolesRetriever" ref="j2eeMappableRolesRetriever"/>
            <property name="userRoles2GrantedAuthoritiesMapper">
                <bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
                    <property name="convertAttributeToUpperCase" value="true"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>

<bean id="preauthEntryPoint"
    class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
        

这里,我们要配置Pre-Auth所需的AuthenticationProvider, EntryPoint, AuthenticatedUserDetailsService并最终组装成一个j2eePreAuthFilter。其中j2eeMappableRolesRetriever会读取我们之前配置的web.xml,从中获得权限信息。

这样,当用户登录时,请求会先被Realm拦截,并要求用户进行登录:

Realm登录

图 19.1. Realm登录


登录成功后,Realm会将用户身份信息绑定到请求中,j2eePreAuthFilter就会从请求中读取身份信息,结合web.xml中定义的权限信息对用户进行授权,并将授权信息录入SecurityContext,之后对用户验证时与之前已没有了任何区别。

这里的preauthEntryPoint会在用户权限不足时起作用,它只会简单返回一个401的拒绝访问响应。

在此我们并不推荐实际中使用这项功能,因为需要对容器进行配置,影响应用的灵活性。

实例在ch109。