# 匿名身份验证
# 概述
通常认为,采用“默认拒绝”(deny-by-default)是一种良好的安全实践,在这种情况下,你可以明确指定允许的内容,而不允许其他任何内容。定义未经身份验证的用户可以访问的内容也是类似的情况,特别是对于 Web 应用程序。许多网站要求用户必须对除几个 URL(例如主页和登录页面)以外的任何内容进行身份验证。在这种情况下,最容易为这些特定的 URL 定义访问配置属性,而不是为每个安全资源定义访问配置属性。换句话说,有时说ROLE_SOMETHING
是缺省情况下需要的,并且只允许此规则的某些例外情况,例如应用程序的登录、注销和主页,这是很好的。你还可以从筛选链中完全省略这些页面,从而绕过访问控制检查,但是由于其他原因,这可能是不希望的,特别是如果对于经过身份验证的用户,页面的行为不同。
这就是我们所说的匿名身份验证。请注意,“匿名身份验证”的用户和未经身份验证的用户之间没有真正的概念上的区别。 Spring 安全性的匿名身份验证为你提供了一种更方便的方式来配置访问控制属性。例如,对 Servlet API 的调用(例如getCallerPrincipal
)仍将返回 null,即使在SecurityContextHolder
中实际上存在匿名身份验证对象。
在其他情况下,匿名身份验证是有用的,例如,当审计拦截器查询SecurityContextHolder
以确定哪个主体负责给定的操作时。如果类知道SecurityContextHolder
总是包含一个Authentication
对象,而不是null
,则可以更可靠地编写类。
# 配置
匿名身份验证支持在使用 HTTP 配置 Spring Security3.0 时自动提供,并且可以使用<anonymous>
元素进行定制(或禁用)。你不需要配置这里描述的 bean,除非你使用的是传统的 Bean 配置。
三个类一起提供匿名身份验证功能。AnonymousAuthenticationToken
是Authentication
的一个实现,并存储适用于匿名主体的GrantedAuthority
s。有一个对应的AnonymousAuthenticationProvider
,它被链接到ProviderManager
中,使得AnonymousAuthenticationToken
s 被接受。最后,还有一个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
在过滤器和身份验证提供程序之间共享,因此前者创建的令牌被后者接受[1]。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>
# 身份验证 TrustResolver
匿名身份验证讨论的一个补充是AuthenticationTrustResolver
接口,其对应的AuthenticationTrustResolverImpl
实现。这个接口提供了isAnonymous(Authentication)
方法,它允许感兴趣的类考虑到这种特殊类型的身份验证状态。ExceptionTranslationFilter
在处理AccessDeniedException
s 时使用该接口。如果抛出了AccessDeniedException
,并且身份验证是匿名类型的,那么过滤器将启动AuthenticationEntryPoint
,以使主体能够正确地进行身份验证,而不是抛出 403(禁用)响应。这是一个必要的区别,否则主体将始终被视为“身份验证”,并且永远不会有机会通过 Form、Basic、Digest 或其他一些正常的身份验证机制登录。
在上面的拦截器配置中,你经常会看到ROLE_ANONYMOUS
属性被替换为IS_AUTHENTICATED_ANONYMOUSLY
,这在定义访问控制时实际上是相同的。这是AuthenticatedVoter
的使用示例,我们将在授权章中看到它。它使用AuthenticationTrustResolver
来处理这个特定的配置属性,并授予匿名用户访问权限。AuthenticatedVoter
方法更强大,因为它允许你区分匿名、rememe-me 和完全验证的用户。但是,如果你不需要此功能,那么你可以坚持使用ROLE_ANONYMOUS
,它将由 Spring Security 的标准RoleVoter
处理。
# 利用 Spring MVC 获得匿名身份验证
[ Spring MVC 解析类型Principal
]的参数(https://DOCS. Spring.io/ Spring-framework/DOCS/current/reference/html/web.html#mvc-ann-arguments)使用自己的参数解析器。
这意味着像这样的构造:
爪哇
@GetMapping("/")
public String method(Authentication authentication) {
if (authentication instanceof AnonymousAuthenticationToken) {
return "anonymous";
} else {
return "not anonymous";
}
}
Kotlin
@GetMapping("/")
fun method(authentication: Authentication?): String {
return if (authentication is AnonymousAuthenticationToken) {
"anonymous"
} else {
"not anonymous"
}
}
将始终返回“不匿名”,即使是匿名请求。原因是 Spring MVC 使用HttpServletRequest#getPrincipal
解析参数,当请求是匿名的时,这是null
。
如果你想在匿名请求中获得Authentication
,请使用@CurrentSecurityContext
:
例 1。对匿名请求使用 CurrentSecurityContext
爪哇
@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
return context.getAuthentication().getName();
}
Kotlin
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String =
context!!.authentication!!.name
1。在这里,使用key
属性不应被视为提供了任何真正的安全性。这只是一次记账练习。如果你正在共享一个ProviderManager
,其中包含一个AnonymousAuthenticationProvider
,在这样的场景中,验证客户端可以构造Authentication
对象(例如通过 RMI 调用),然后,恶意客户端可以提交它自己创建的AnonymousAuthenticationToken
(使用所选的用户名和权限列表)。如果key
是可以猜测的,或者可以找到,那么该令牌将被匿名提供者接受。这不是正常使用的问题,但是如果你正在使用 RMI,你最好使用定制的ProviderManager
,它省略了匿名提供者,而不是共享你用于 HTTP 身份验证机制的提供者。