匿名认证

概述

普遍认为,采用“默认拒绝”姿态是良好的安全实践,即明确指定允许的内容,并拒绝其他所有内容。特别是对于 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 认证机制的管理器。

userAttribute 的形式为 usernameInTheAuthenticationToken,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)和完全认证用户。但是,如果您不需要此功能,则可以继续使用由 Spring Security 的标准 RoleVoter 处理的 ROLE_ANONYMOUS

在 Spring MVC 中获取匿名认证

这意味着像这样的构造

  • Java

  • Kotlin

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

总是会返回 "not anonymous",即使对于匿名请求也是如此。原因是 Spring MVC 使用 HttpServletRequest#getPrincipal 解析参数,当请求是匿名时,它返回 null

如果您想在匿名请求中获取 Authentication 对象,请改用 @CurrentSecurityContext

对匿名请求使用 @CurrentSecurityContext
  • Java

  • Kotlin

@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
	return context.getAuthentication().getName();
}
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String =
		context!!.authentication!!.name