匿名身份验证

概述

通常认为,采取“默认拒绝”的策略是良好的安全实践,即显式指定允许的内容,并拒绝所有其他内容。为未经身份验证的用户定义可访问的内容也是类似的情况,尤其对于 Web 应用而言。许多网站要求用户必须经过身份验证才能访问除少数几个 URL 之外的任何内容(例如主页和登录页面)。在这种情况下,为这些特定 URL 定义访问配置属性比为每个受保护的资源定义访问配置属性更容易。换句话说,有时最好声明默认情况下需要 `ROLE_SOMETHING`,只允许对此规则的某些例外,例如应用的登录、注销和主页。您也可以完全从过滤器链中省略这些页面,从而绕过访问控制检查,但这可能由于其他原因而不理想,特别是如果这些页面对已认证用户的行为有所不同。

这就是我们所说的匿名身份验证。请注意, “匿名身份验证”的用户和未经身份验证的用户之间没有真正的概念差异。Spring Security 的匿名身份验证只是为您提供了一种更便捷的方式来配置访问控制属性。对 Servlet API 调用的调用,例如 `getCallerPrincipal`,仍然返回 null,即使 `SecurityContextHolder` 中实际上存在匿名身份验证对象。

匿名身份验证在其他情况下也很有用,例如当审计拦截器查询 `SecurityContextHolder` 以识别哪个主体负责给定操作时。如果类知道 `SecurityContextHolder` 始终包含 `Authentication` 对象并且从不包含 `null`,则可以更稳健地编写类。

配置

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

三个类协同工作以提供匿名身份验证功能。`AnonymousAuthenticationToken` 是 `Authentication` 的实现,并存储应用于匿名主体的 `GrantedAuthority` 实例。有一个对应的 `AnonymousAuthenticationProvider`,它被链接到 `ProviderManager` 中,以便接受 `AnonymousAuthenticationToken` 实例。最后,`AnonymousAuthenticationFilter` 链接在正常的身份验证机制之后,如果那里没有现有的 `Authentication` 对象,则自动将 `AnonymousAuthenticationToken` 添加到 `SecurityContextHolder`。过滤器和身份验证提供程序定义如下:

<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 调用)的场景中共享包含 `AnonymousAuthenticationProvider` 的 `ProviderManager`,则恶意客户端可以提交它自己创建的 `AnonymousAuthenticationToken`(使用选择的用户名和权限列表)。如果 `key` 是可猜测的或可以被发现的,则匿名提供程序将接受该令牌。这在正常使用中不是问题。但是,如果您使用 RMI,则应使用省略匿名提供程序的自定义 `ProviderManager`,而不是共享用于 HTTP 身份验证机制的提供程序。

`userAttribute` 的形式为 `usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]`。在 `InMemoryDaoImpl` 的 `userMap` 属性的等号后面也使用了相同的语法。

如前所述,匿名身份验证的好处是所有 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方法更强大,因为它允许您区分匿名用户、记住我的用户和完全认证的用户。但是,如果您不需要此功能,则可以使用ROLE_ANONYMOUS,它由Spring Security的标准RoleVoter处理。

使用Spring MVC获取匿名身份验证

Spring MVC 使用其自身的参数解析器来解析类型为Principal的参数

这意味着像这样的结构

  • 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"
    }
}

即使对于匿名请求,也将始终返回“非匿名”。原因是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