预认证场景
例如,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属性中。当用户角色信息可以通过预认证机制获取时,相关数据也会存储在此属性中,此时details会实现GrantedAuthoritiesContainer接口。这使得身份验证提供者能够读取外部分配给用户的权限。接下来我们看一个具体示例。
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
如果过滤器配置了authenticationDetailsSource(即该类的实例),则通过为预定义的一组“可映射角色”中的每个角色调用isUserInRole(String role)方法来获取权限信息。该类从已配置的MappableAttributesRetriever中获取这些角色。可能的实现方式包括:在应用上下文中硬编码角色列表,或从web.xml文件中的<security-role>信息读取角色信息。预认证示例应用采用了后一种方式。
此外还有一个额外的阶段,即通过配置的 Attributes2GrantedAuthoritiesMapper 将角色(或属性)映射为 Spring Security 的 GrantedAuthority 对象。默认实现仅会在名称前添加常见的 ROLE_ 前缀,但该机制为您提供了对映射行为的完全控制权。
预认证身份验证提供程序
预认证提供者除了加载用户的 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>
我们在此假设配置中使用了安全命名空间。同时假设您已在配置中添加了一个 UserDetailsService(命名为 "userDetailsService")来加载用户的角色。
Java EE 容器认证
J2eePreAuthenticatedProcessingFilter 类从 HttpServletRequest 的 userPrincipal 属性中提取用户名。使用此过滤器通常需要与 Java EE 角色结合使用,如先前在 J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource 中所述。
在代码库中有一个示例应用使用了这种方法,如果你感兴趣,可以从 Github 获取代码并查看应用上下文文件。