Anonymous Authentication

Overview

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

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

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

Configuration

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

三个类共同协作以提供匿名身份验证功能。AnonymousAuthenticationTokenAuthentication 的实现,它存储适用于匿名主体的 GrantedAuthority 实例。有一个相应的 AnonymousAuthenticationProvider,它链接到 ProviderManager,这样就不会接受 AnonymousAuthenticationToken 实例。最后,一个 AnonymousAuthenticationFilter 在正常身份验证机制之后链接,并且如果 SecurityContextHolder 中不存在 Authentication,它会自动向 SecurityContextHolder 中添加 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 属性的使用视为提供任何真正的安全性。这仅仅是一项记账练习。如果您共享包含 AnonymousAuthenticationProviderProviderManager,并且身份验证客户端有可能构建 Authentication 对象(例如使用 RMI 调用),则恶意客户端可以提交它自己创建的 AnonymousAuthenticationToken(具有所选用户名和权限列表)。如果能猜到 key 或可以找到 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,以便主体可以正确地进行身份验证。这是一个必要的区别。否则,主体将始终被视为 “authenticated”,并且永远不会有机会通过表单、基本、摘要或其他一些正常身份验证机制登录。

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

Getting Anonymous Authentications with Spring MVC

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

这意味着像这样的结构:

  • 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

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

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