预认证场景
示例包括 X.509、Siteminder 以及应用程序运行所在的 Java EE 容器进行的认证。当使用预认证时,Spring Security 必须:
-
识别发出请求的用户。
-
获取用户的权限。
具体细节取决于外部认证机制。在X.509的情况下,用户可能通过其证书信息来识别;而在Siteminder的情况下,则可能通过HTTP请求头来识别。如果依赖容器认证,可以通过对传入的HTTP请求调用getUserPrincipal()
方法来识别用户。在某些情况下,外部机制可能会提供用户的角色和权限信息。然而,在其他情况下,必须从其他来源(如UserDetailsService
)获取权限。
预认证框架类
由于大多数预认证机制遵循相同的模式,Spring Security 提供了一组类,这些类为实现预认证身份验证提供程序提供了内部框架。这消除了重复,并允许以结构化的方式添加新的实现,而无需从头开始编写所有内容。如果您想使用类似X.509 身份验证的功能,则不需要了解这些类,因为它已经有一个命名空间配置选项,该选项更易于使用和入门。如果您需要使用显式的 bean 配置或计划编写自己的实现,则需要了解所提供的实现的工作原理。您可以在 org.springframework.security.web.authentication.preauth
下找到这些类。我们在此仅提供一个概要,因此请在适当的地方查阅 Javadoc 和源代码。
AbstractPreAuthenticatedProcessingFilter
这个类会检查当前的安全上下文内容,如果它是空的,则尝试从 HTTP 请求中提取用户信息,并将其提交给 AuthenticationManager
。子类通过重写以下方法来获取这些信息。
- Java
- Kotlin
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request);
protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
protected abstract fun getPreAuthenticatedPrincipal(request: HttpServletRequest): Any?
protected abstract fun getPreAuthenticatedCredentials(request: HttpServletRequest): Any?
调用这些方法后,过滤器会创建一个 PreAuthenticatedAuthenticationToken
,其中包含返回的数据并将其提交进行认证。这里所说的“认证”,实际上只是进一步处理以加载用户的权限,但遵循的是标准的 Spring Security 认证架构。
与其他 Spring Security 身份验证过滤器一样,预身份验证过滤器也有一个 authenticationDetailsSource
属性,默认情况下,该属性会创建一个 WebAuthenticationDetails
对象来存储附加信息,例如会话标识符和源 IP 地址在 Authentication
对象的 details
属性中。在可以从预身份验证机制中获取用户角色信息的情况下,数据也存储在此属性中,详细信息实现了 GrantedAuthoritiesContainer
接口。这使得身份验证提供者能够读取外部分配给用户的身份。我们接下来将看一个具体的例子。
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
如果过滤器配置了 authenticationDetailsSource
,即该类的一个实例,则会通过调用预定义的一组“可映射角色”的 isUserInRole(String role)
方法来获取权限信息。此类从配置的 MappableAttributesRetriever
中获取这些信息。可能的实现包括在应用程序上下文中硬编码一个列表,以及从 web.xml
文件中的 <security-role>
信息读取角色信息。预认证示例应用程序使用后一种方法。
还有一个额外的阶段,其中的角色(或属性)通过使用配置的 Attributes2GrantedAuthoritiesMapper
映射到 Spring Security 的 GrantedAuthority
对象。默认情况下,只是在名称前添加通常的 ROLE_
前缀,但它让你可以完全控制行为。
PreAuthenticatedAuthenticationProvider
预认证提供者除了加载用户的 UserDetails
对象之外,几乎没有其他工作要做。它通过委托给 AuthenticationUserDetailsService
来实现这一点。后者类似于标准的 UserDetailsService
,但它接受一个 Authentication
对象,而不仅仅是用户名:
public interface AuthenticationUserDetailsService {
UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException;
}
此接口可能还有其他用途,但通过预认证,它允许访问我们在上一节中看到的打包在 Authentication
对象中的权限。PreAuthenticatedGrantedAuthoritiesUserDetailsService
类实现了这一点。或者,它可以通过 UserDetailsByNameServiceWrapper
实现将任务委托给标准的 UserDetailsService
。
Http403ForbiddenEntryPoint
AuthenticationEntryPoint 负责为未认证的用户(当他们尝试访问受保护的资源时)启动认证过程。然而,在预先认证的情况下,这并不适用。只有在不将预先认证与其他认证机制结合使用时,才会用此类的实例配置 ExceptionTranslationFilter
。如果用户被 AbstractPreAuthenticatedProcessingFilter
拒绝,导致认证为 null 时,就会调用它。如果被调用,它总是返回一个 403
-禁止的响应代码。
具体实现
X.509 认证在单独的一章中介绍。在这里,我们来看一些为其他预认证场景提供支持的类。
请求头认证 (Siteminder)
外部认证系统可以通过在 HTTP 请求上设置特定的头部信息来向应用程序提供信息。一个著名的例子是 Siteminder,它通过名为 SM_USER
的头部传递用户名。此机制由 RequestHeaderAuthenticationFilter
类支持,该类仅从头部提取用户名。默认情况下,它使用 SM_USER
作为头部名称。有关更多详细信息,请参阅 Javadoc。
当使用这样的系统时,框架不会执行任何身份验证检查,因此极其重要的是正确配置外部系统并保护对应用程序的所有访问。如果攻击者能够在未被发现的情况下伪造原始请求中的头信息,他们可能会选择任何他们希望的用户名。
Siteminder 示例配置
以下示例显示了使用此过滤器的典型配置:
<security:http>
<!-- Additional http configuration omitted -->
<security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" />
</security:http>
<bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter">
<property name="principalRequestHeader" value="SM_USER"/>
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService">
<bean id="userDetailsServiceWrapper"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
</property>
</bean>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="preauthAuthProvider" />
</security:authentication-manager>
我们假设这里使用了security 命名空间进行配置。还假设您已经在配置中添加了一个 UserDetailsService
(称为 "userDetailsService")来加载用户的角色。
Java EE 容器认证
J2eePreAuthenticatedProcessingFilter
类从 HttpServletRequest
的 userPrincipal
属性中提取用户名。通常,此过滤器的使用会与 Java EE 角色的使用结合,如前面在 J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource 中所述。
有一个sample application在代码库中使用了这种方法,所以如果你感兴趣的话,可以从 Github 上获取代码并查看应用程序上下文文件。