匿名认证 Authentication

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

概述

一般来说,采取 “deny-by-default” (默认拒绝)的立场被认为是良好的安全实践,即明确规定允许什么,不允许其他一切。定义未经认证的用户可以访问的内容是一个类似的情况,特别是对于Web应用程序。许多网站要求,除了几个URL(例如主页和登录页面)之外,用户必须经过认证。在这种情况下,为这些特定的URL定义访问配置属性是最容易的,而不是为每一个安全的资源定义。换句话说,有时说 ROLE_SOMETHING 是默认需要的,只允许这个规则的某些例外,例如一个应用程序的登录、注销和主页。你也可以从过滤器链中完全省略这些页面,从而绕过访问控制检查,但由于其他原因这可能是不可取的,特别是如果这些页面对认证用户有不同的行为。

这就是我们所说的匿名认证。请注意,"匿名认证" 的用户和未认证的用户之间没有真正的概念上的区别。Spring Security 的匿名认证只是给你提供了一种更方便的方式来配置你的访问控制属性。对servlet API的调用,如 getCallerPrincipal,仍然返回 null,即使在 SecurityContextHolder 中实际上有一个匿名认证对象。

在其他情况下,匿名认证也很有用,比如当审计拦截器查询 SecurityContextHolder 以确定哪个委托人(principal)负责某个特定操作时。如果知道 SecurityContextHolder 总是包含一个 Authentication 对象,并且从不包含 null,那么就可以更稳健地编写类了。

配置

当你使用HTTP配置(在Spring Security 3.0中引入)时,会自动提供匿名认证支持。你可以通过使用 <anonymous> 元素来定制(或禁用)它。除非你使用传统的Bean配置,否则你不需要配置这里描述的Bean。

三个类一起工作以提供匿名认证功能。AnonymousAuthenticationTokenAuthentication 的一个实现,它存储适用于匿名委托人(principal)的 GrantedAuthority 实例。有一个相应的 AnonymousAuthenticationProvider,它被链入 ProviderManager,以便 AnonymousAuthenticationToken 实例被接受。最后,AnonymousAuthenticationFilter 被锁在正常认证机制之后,如果 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 是可以猜到的,或者可以被发现,这个令牌就会被匿名提供者接受。 这对正常使用来说不是问题。然而,如果你使用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(forbidden)响应,而是开始进行 AuthenticationEntryPoint,这样委托人就可以正常认证了。这是一个必要的区别。否则,委托人将总是被视为 “authenticated” (已认证),而永远不会有机会通过表单、基本、摘要或其他正常认证机制登录。

我们经常看到早期拦截器配置中的 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"
    }
}

将总是返回 "not anonymous",即使是匿名请求。原因是 Spring MVC 使用 HttpServletRequest#getPrincipal 来解析参数,当请求为匿名时,该参数为 null

如果你想在匿名请求中获得 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