Anonymous Authentication

Overview

一般来说,采用 “deny-by-default” 立场被认为是良好的安全实践,在这种立场上,你明确指定允许的内容并禁止其他所有内容。定义未经身份验证的用户可以访问的内容是类似的情况,特别是对于网络应用程序而言。许多网站要求用户必须对除少数几个 URL(例如主页和登录页面)之外的所有内容进行身份验证。在这种情况下,最简单的办法是为这些特定的 URL 而不是为每个受保护资源定义访问配置属性。换句话说,有时需要明确表示登录的应用程序的主页和注销页面会 ROLE_SOMETHING 要求进行身份验证,并且仅允许对该规则进行某些例外,例如登录页面、注销页面和主页。你也可以从筛选链中完全省略这些页面,从而绕过访问控制检查,但这对于其他原因,特别是如果页面对经过身份验证的用户表现不同,可能是不合适的。

It is generally considered good security practice to adopt a “deny-by-default” stance, where you explicitly specify what is allowed and disallow everything else. Defining what is accessible to unauthenticated users is a similar situation, particularly for web applications. Many sites require that users must be authenticated for anything other than a few URLs (for example the home and login pages). In that case, it is easiest to define access configuration attributes for these specific URLs rather than for every secured resource. Put differently, sometimes it is nice to say ROLE_SOMETHING is required by default and allow only certain exceptions to this rule, such as for login, logout, and home pages of an application. You could also omit these pages from the filter chain entirely, thus bypassing the access control checks, but this may be undesirable for other reasons, particularly if the pages behave differently for authenticated users.

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

This is what we mean by anonymous authentication. Note that there is no real conceptual difference between a user who is “anonymously authenticated” and an unauthenticated user. Spring Security’s anonymous authentication just gives you a more convenient way to configure your access-control attributes. Calls to servlet API calls, such as getCallerPrincipal, still return null, even though there is actually an anonymous authentication object in the SecurityContextHolder.

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

There are other situations where anonymous authentication is useful, such as when an auditing interceptor queries the SecurityContextHolder to identify which principal was responsible for a given operation. Classes can be authored more robustly if they know the SecurityContextHolder always contains an Authentication object and never contains null.

Configuration

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

Anonymous authentication support is provided automatically when you use the HTTP configuration (introduced in Spring Security 3.0). You can customize (or disable) it by using the <anonymous> element. You need not configure the beans described here unless you are using traditional bean configuration.

三个类共同协作以提供匿名身份验证功能。AnonymousAuthenticationTokenAuthentication 的实现,它存储适用于匿名主体的 GrantedAuthority 实例。有一个相应的 AnonymousAuthenticationProvider,它链接到 ProviderManager,这样就不会接受 AnonymousAuthenticationToken 实例。最后,一个 AnonymousAuthenticationFilter 在正常身份验证机制之后链接,并且如果 SecurityContextHolder 中不存在 Authentication,它会自动向 SecurityContextHolder 中添加 AnonymousAuthenticationToken。过滤器和身份验证提供程序定义如下:

Three classes work together to provide the anonymous authentication feature. AnonymousAuthenticationToken is an implementation of Authentication and stores the GrantedAuthority instances that apply to the anonymous principal. There is a corresponding AnonymousAuthenticationProvider, which is chained into the ProviderManager so that AnonymousAuthenticationToken instances are accepted. Finally, an AnonymousAuthenticationFilter is chained after the normal authentication mechanisms and automatically adds an AnonymousAuthenticationToken to the SecurityContextHolder if there is no existing Authentication held there. The filter and authentication provider is defined as follows:

<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 在过滤器和身份验证提供程序之间共享,以便前者创建的令牌被后者接受

The key is shared between the filter and authentication provider, so that tokens created by the former are accepted by the latter

此处不应将 key 属性的使用视为提供任何真正的安全性。这仅仅是一项记账练习。如果您共享包含 AnonymousAuthenticationProviderProviderManager,并且身份验证客户端有可能构建 Authentication 对象(例如使用 RMI 调用),则恶意客户端可以提交它自己创建的 AnonymousAuthenticationToken(具有所选用户名和权限列表)。如果能猜到 key 或可以找到 key,匿名提供程序会接受令牌。这不是正常用法中的问题。但是,如果您使用 RMI,则应使用一个自定义的 ProviderManager,它省略匿名提供程序,而不是共享您用于 HTTP 身份验证机制的那个。

The use of the key property should not be regarded as providing any real security here. It is merely a book-keeping exercise. If you share a ProviderManager that contains an AnonymousAuthenticationProvider in a scenario where it is possible for an authenticating client to construct the Authentication object (such as with RMI invocations), then a malicious client could submit an AnonymousAuthenticationToken that it had created itself (with the chosen username and authority list). If the key is guessable or can be found out, the token would be accepted by the anonymous provider. This is not a problem with normal usage. However, if you use RMI, you should use a customized ProviderManager that omits the anonymous provider rather than sharing the one you use for your HTTP authentication mechanisms.

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

The userAttribute is expressed in the form of usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]. The same syntax is used after the equals sign for the userMap property of InMemoryDaoImpl.

如前所述,匿名身份验证的好处是所有 URI 模式都可以对其应用安全性,如下例所示:

As explained earlier, the benefit of anonymous authentication is that all URI patterns can have security applied to them, as the following example shows:

<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,以便主体可以正确地进行身份验证。这是一个必要的区别。否则,主体将始终被视为 “authenticated”,并且永远不会有机会通过表单、基本、摘要或其他一些正常身份验证机制登录。

Rounding out the anonymous authentication discussion is the AuthenticationTrustResolver interface, with its corresponding AuthenticationTrustResolverImpl implementation. This interface provides an isAnonymous(Authentication) method, which allows interested classes to take into account this special type of authentication status. The ExceptionTranslationFilter uses this interface in processing AccessDeniedException instances. If an AccessDeniedException is thrown and the authentication is of an anonymous type, instead of throwing a 403 (forbidden) response, the filter, instead, commences the AuthenticationEntryPoint so that the principal can authenticate properly. This is a necessary distinction. Otherwise, principals would always be deemed “authenticated” and never be given an opportunity to login through form, basic, digest, or some other normal authentication mechanism.

我们经常看到早先拦截器配置中的 ROLE_ANONYMOUS 属性被替换为 IS_AUTHENTICATED_ANONYMOUSLY,这在定义访问控制时实际上是同一回事。这是 AuthenticatedVoter 使用的一个示例,我们在 authorization chapter 中涵盖了它。它使用 AuthenticationTrustResolver 处理此特定配置属性并授予匿名用户访问权限。AuthenticatedVoter 方法更强大,因为它可以区分匿名用户、记住我的用户和完全经过身份验证的用户。但是,如果您不需要此功能,您可以坚持使用 ROLE_ANONYMOUS,它是由 Spring Security 的标准 RoleVoter 处理的。

We often see the ROLE_ANONYMOUS attribute in the earlier interceptor configuration replaced with IS_AUTHENTICATED_ANONYMOUSLY, which is effectively the same thing when defining access controls. This is an example of the use of the AuthenticatedVoter, which we cover in the authorization chapter. It uses an AuthenticationTrustResolver to process this particular configuration attribute and grant access to anonymous users. The AuthenticatedVoter approach is more powerful, since it lets you differentiate between anonymous, remember-me, and fully authenticated users. If you do not need this functionality, though, you can stick with ROLE_ANONYMOUS, which is processed by Spring Security’s standard RoleVoter.

Getting Anonymous Authentications with Spring MVC

@ {s1} 使用自己的参数解析程序。

Spring MVC resolves parameters of type Principal using its own argument resolver.

这意味着像这样的结构:

This means that a construct like this one:

  • 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 来解析参数,当请求是匿名的时,HttpServletRequest#getPrincipalnull

will always return "not anonymous", even for anonymous requests. The reason is that Spring MVC resolves the parameter using HttpServletRequest#getPrincipal, which is null when the request is anonymous.

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

If you’d like to obtain the Authentication in anonymous requests, use @CurrentSecurityContext instead:

Use CurrentSecurityContext for Anonymous requests
  • Java

  • Kotlin

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