跳到主要内容
版本:7.0.2

匿名认证

DeepSeek V3 中英对照 Anonymous Anonymous Authentication

概述

通常认为,采用"默认拒绝"的安全策略是一种良好实践,即明确指定允许的内容并禁止其他所有内容。对于未认证用户可访问内容的定义也属于类似情况,尤其是在Web应用中。许多网站要求用户除了少数URL(例如首页和登录页)外,必须通过认证才能访问。在这种情况下,为这些特定URL定义访问配置属性比逐个配置每个受保护资源更为简便。换言之,有时适合设定默认要求ROLE_SOMETHING权限,仅允许特定例外情况(例如应用程序的登录、注销和首页)。您也可以将这些页面完全排除在过滤器链之外,从而绕过访问控制检查,但出于其他原因(特别是当这些页面对已认证用户有不同行为时),这可能并不理想。

这就是我们所说的匿名认证。请注意,"匿名认证"的用户与未认证用户在概念上并无实质区别。Spring Security 的匿名认证只是为您提供了一种更便捷的方式来配置访问控制属性。即使 SecurityContextHolder 中确实存在匿名认证对象,调用 Servlet API(例如 getCallerPrincipal)仍会返回 null。

在其他场景中,匿名认证同样有用武之地。例如,当审计拦截器查询 SecurityContextHolder 以确定特定操作由哪个主体执行时,如果代码能够确信 SecurityContextHolder 始终包含一个 Authentication 对象且永远不会为 null,那么类的编写将更加健壮可靠。

配置

当您使用HTTP配置时(Spring Security 3.0中引入),系统会自动提供匿名身份验证支持。您可以通过使用<anonymous>元素来自定义(或禁用)此功能。除非您正在使用传统的Bean配置,否则无需配置此处描述的Bean。

三个类协同工作以提供匿名认证功能。AnonymousAuthenticationTokenAuthentication 接口的实现,用于存储适用于匿名主体的 GrantedAuthority 实例。与之对应的是 AnonymousAuthenticationProvider,它被链接到 ProviderManager 中,以便接受 AnonymousAuthenticationToken 实例。最后,AnonymousAuthenticationFilter 被链接在常规认证机制之后,如果 SecurityContextHolder 中当前没有已存在的 Authentication 对象,该过滤器会自动向其中添加一个 AnonymousAuthenticationToken。该过滤器与认证提供者的定义如下:

<bean id="anonymousAuthFilter"
class="org.springframework.security.web.authentication.AnonymousAuthenticationFilter">
<property name="key" value="foobar"/>
<property name="userAttribute" value="anonymousUser,ROLE_ANONYMOUS"/>
</bean>

<bean id="anonymousAuthenticationProvider"
class="org.springframework.security.authentication.AnonymousAuthenticationProvider">
<property name="key" value="foobar"/>
</bean>

key 在过滤器和身份验证提供者之间共享,以便前者创建的令牌能够被后者接受。

备注

此处使用 key 属性不应被视为提供任何实际的安全性。它仅仅是一种簿记操作。如果在可能允许认证客户端构造 Authentication 对象的场景中(例如通过 RMI 调用)共享一个包含 AnonymousAuthenticationProviderProviderManager,那么恶意客户端可能会提交一个它自己创建的 AnonymousAuthenticationToken(包含其选择的用户名和权限列表)。如果 key 是可猜测的或可以被发现,该令牌将被匿名提供者接受。这在正常使用中不是问题。但是,如果你使用 RMI,你应该使用一个定制的、省略了匿名提供者的 ProviderManager,而不是共享你用于 HTTP 认证机制的那个。

userAttributeusernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority] 的形式表示。在 InMemoryDaoImpluserMap 属性等号后面也使用相同的语法。

如前所述,匿名认证的优势在于所有URI模式都可以应用安全配置,如下例所示:

<bean id="filterSecurityInterceptor"
class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
<property name="securityMetadata">
<security:filter-security-metadata-source>
<security:intercept-url pattern='/index.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
<security:intercept-url pattern='/hello.htm' access='ROLE_ANONYMOUS,ROLE_USER'/>
<security:intercept-url pattern='/logoff.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
<security:intercept-url pattern='/login.jsp' access='ROLE_ANONYMOUS,ROLE_USER'/>
<security:intercept-url pattern='/**' access='ROLE_USER'/>
</security:filter-security-metadata-source>" +
</property>
</bean>

AuthenticationTrustResolver

匿名身份验证讨论的收尾部分是 AuthenticationTrustResolver 接口及其对应的 AuthenticationTrustResolverImpl 实现。该接口提供了一个 isAnonymous(Authentication) 方法,允许相关类考虑这种特殊类型的身份验证状态。ExceptionTranslationFilter 在处理 AccessDeniedException 实例时使用了此接口。如果抛出了 AccessDeniedException 且身份验证属于匿名类型,过滤器不会抛出 403(禁止)响应,而是启动 AuthenticationEntryPoint,以便主体能够正确进行身份验证。这是一个必要的区分。否则,主体将始终被视为“已认证”,并且永远没有机会通过表单、基本认证、摘要认证或其他常规身份验证机制进行登录。

在早期的拦截器配置中,我们经常看到 ROLE_ANONYMOUS 属性被替换为 IS_AUTHENTICATED_ANONYMOUSLY,这两者在定义访问控制时实际上是等效的。这是使用 AuthenticatedVoter 的一个例子,我们在授权章节中对此进行了介绍。它使用一个 AuthenticationTrustResolver 来处理这个特定的配置属性,并授予匿名用户访问权限。AuthenticatedVoter 方法更加强大,因为它允许你区分匿名用户、记住我(remember-me)用户和完全认证的用户。不过,如果你不需要这个功能,可以继续使用 ROLE_ANONYMOUS,它由 Spring Security 的标准 RoleVoter 处理。

使用 Spring MVC 获取匿名认证

这意味着,像这样的结构:

@GetMapping("/")
public String method(Authentication authentication) {
if (authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
} else {
return "not anonymous";
}
}

将始终返回“not anonymous”,即使是匿名请求。原因在于Spring MVC通过HttpServletRequest#getPrincipal解析参数,而该值在请求为匿名时会返回null

如果您希望在匿名请求中获取 Authentication 对象,请改用 @CurrentSecurityContext 注解:

@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
if (context.getAuthentication() instanceOf AnonymousAuthenticationToken) {
return "anonymous"
} else {
return "not anonymous"
}
}