匿名认证

概述

通常认为,采用“默认拒绝”(deny-by-default)的安全策略是一种良好的安全实践,即明确指定允许的内容,而禁止其他所有内容。 定义未认证用户可访问的内容也属于类似情况,尤其对于 Web 应用程序而言。 许多网站要求用户必须经过身份认证才能访问除少数 URL(例如主页和登录页面)之外的所有内容。 在这种情况下,为这些特定的 URL 定义访问配置属性,要比为每个受保护资源逐一配置更为简便。 换句话说,有时我们希望默认要求具备 ROLE_SOMETHING 角色,并仅为此规则设置少数例外,例如应用程序的登录页、登出页和主页。 你也可以完全将这些页面从过滤器链中排除,从而绕过访问控制检查,但出于其他原因(尤其是当这些页面对已认证用户表现出不同行为时),这种做法可能是不可取的。spring-doc.cadn.net.cn

这就是我们所说的匿名认证。 请注意,“匿名认证”的用户与未经认证的用户在概念上并没有实质性的区别。 Spring Security 的匿名认证只是为您提供了一种更便捷的方式来配置访问控制属性。 即使 getCallerPrincipal 中实际上存在一个匿名认证对象,对 Servlet API(例如 SecurityContextHolder)的调用仍然返回 null。spring-doc.cadn.net.cn

还有其他一些场景下匿名认证也很有用,例如当审计拦截器查询SecurityContextHolder以确定哪个主体(principal)负责了某项操作时。 如果类知道SecurityContextHolder始终包含一个Authentication对象而永远不会为null,那么这些类的编写就会更加健壮。spring-doc.cadn.net.cn

配置

当你使用 HTTP 配置(Spring Security 3.0 中引入)时,匿名认证支持会自动提供。 你可以通过使用 <anonymous> 元素来自定义(或禁用)该功能。 除非你使用传统的 bean 配置方式,否则无需配置此处描述的 bean。spring-doc.cadn.net.cn

三个类协同工作以提供匿名认证功能。 AnonymousAuthenticationTokenAuthentication 的一个实现,用于存储适用于匿名主体的 GrantedAuthority 实例。 有一个对应的 AnonymousAuthenticationProvider,它被链接到 ProviderManager 中,以便接受 AnonymousAuthenticationToken 实例。 最后,一个 AnonymousAuthenticationFilter 在常规认证机制之后被链接,并在没有现有 Authentication 存在时自动向 SecurityContextHolder 添加一个 AnonymousAuthenticationToken。 过滤器和认证提供程序定义如下:spring-doc.cadn.net.cn

<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 在过滤器和身份验证提供者之间共享,以便前者创建的Tokens能被后者接受。spring-doc.cadn.net.cn

此处使用 key 属性不应被视为提供了任何实质性的安全保障。 它仅仅是一种记录管理措施。 如果你在某种场景下共享了一个包含 ProviderManagerAnonymousAuthenticationProvider,而该场景中认证客户端有可能自行构造 Authentication 对象(例如通过 RMI 调用),那么恶意客户端就可能提交一个由其自己创建的 AnonymousAuthenticationToken(包含其所选的用户名和权限列表)。 如果该 key 可被猜测或被探知到,匿名提供者就会接受该Tokens。 在常规使用中这并不是一个问题。然而,如果你使用 RMI,则应使用一个自定义的 ProviderManager,其中省略了匿名提供者,而不是与用于 HTTP 认证机制的 7 共享同一个实例。spring-doc.cadn.net.cn

userAttribute 的格式为 usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]。 在 userMapInMemoryDaoImpl 属性中,等号后面的值也使用相同的语法。spring-doc.cadn.net.cn

如前所述,匿名认证的好处在于可以对所有 URI 模式应用安全控制,如下例所示:spring-doc.cadn.net.cn

<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 接口及其对应的 AuthenticationTrustResolverImpl 实现。 该接口提供了一个 isAnonymous(Authentication) 方法,允许相关类考虑到这种特殊的认证状态。 ExceptionTranslationFilter 在处理 AccessDeniedException 实例时会使用此接口。 如果抛出了 AccessDeniedException,且当前认证属于匿名类型,那么过滤器不会返回 403(禁止访问)响应,而是启动 AuthenticationEntryPoint,以便主体(principal)能够正确地进行身份认证。 这一区分是必要的。否则,主体将始终被视为“已认证”,从而永远无法通过表单、基本认证、摘要认证或其他常规认证机制进行登录。spring-doc.cadn.net.cn

我们经常看到早期拦截器配置中的 ROLE_ANONYMOUS 属性被替换为 IS_AUTHENTICATED_ANONYMOUSLY,这在定义访问控制时实际上是相同的。 这是使用 AuthenticatedVoter 的一个示例,我们在 授权章节中进行了介绍。 它使用一个 AuthenticationTrustResolver 来处理此特定配置属性,并授予匿名用户访问权限。 AuthenticatedVoter 方法更为强大,因为它允许您区分匿名用户、记住我用户和完全认证用户。 不过,如果您不需要此功能,可以继续使用 ROLE_ANONYMOUS,它由 Spring Security 的标准 RoleVoter 处理。spring-doc.cadn.net.cn

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

这意味着像下面这样的结构:spring-doc.cadn.net.cn

@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 来解析该参数,而在请求为匿名时,该方法返回 nullspring-doc.cadn.net.cn

如果您希望在匿名请求中获取 Authentication,请使用 @CurrentSecurityContext 替代:spring-doc.cadn.net.cn

对匿名请求使用 CurrentSecurityContext
@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
	if (context.getAuthentication() instanceOf AnonymousAuthenticationToken) {
		return "anonymous"
	} else {
		return "not anonymous"
	}
}
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String {
   return if (context!!.authentication is AnonymousAuthenticationToken) {
	"anonymous"
   } else {
	"not anonymous"
   }
}