匿名身份验证
概述
通常认为采用“默认拒绝”的立场是一种良好的安全实践,即明确指定允许的内容并禁止其他所有内容。定义未经身份验证的用户可以访问的内容是一个类似的情况,特别是对于 Web 应用程序。许多网站要求用户必须经过身份验证才能访问除少数 URL(例如主页和登录页面)之外的任何内容。在这种情况下,最简单的方法是为这些特定 URL 定义访问配置属性,而不是为每个受保护的资源定义属性。换句话说,有时最好说默认情况下需要ROLE_SOMETHING
,并且只允许对该规则进行某些例外,例如应用程序的登录、注销和主页。你还可以完全从过滤器链中省略这些页面,从而绕过访问控制检查,但这可能由于其他原因而不可取,特别是如果这些页面对经过身份验证的用户表现不同。
这就是我们所说的匿名身份验证。请注意,“匿名身份验证”的用户与未经身份验证的用户之间没有真正的概念差异。Spring Security 的匿名身份验证只是为您提供了一种更方便的方式来配置您的访问控制属性。对 servlet API 调用(例如 getCallerPrincipal
)的调用仍然返回 null,即使 SecurityContextHolder
中实际上存在一个匿名身份验证对象。
匿名身份验证还有其他一些有用的情况,例如当审计拦截器查询 SecurityContextHolder
以识别哪个主体对给定操作负责时。如果知道 SecurityContextHolder
始终包含一个 Authentication
对象且从不包含 null
,则可以更稳健地编写类。
配置
当您使用 HTTP 配置(在 Spring Security 3.0 中引入)时,将自动提供匿名身份验证支持。您可以使用 <anonymous>
元素对其进行自定义(或禁用)。除非您使用传统 Bean 配置,否则无需配置此处描述的 Bean。
三个类协同工作以提供匿名身份验证功能。AnonymousAuthenticationToken
是 Authentication
的一个实现,并存储适用于匿名主体的 GrantedAuthority
实例。有一个相应的 AnonymousAuthenticationProvider
,它被链接到 ProviderManager
中,以便接受 AnonymousAuthenticationToken
实例。最后,一个 AnonymousAuthenticationFilter
在正常身份验证机制之后链接,如果 SecurityContextHolder
中没有现有的 Authentication
,则自动向其中添加一个 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
在过滤器和身份验证提供程序之间共享,以便前者创建的令牌被后者接受
|
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>
AuthenticationTrustResolver
匿名身份验证讨论的最后是 AuthenticationTrustResolver
接口,及其对应的 AuthenticationTrustResolverImpl
实现。此接口提供了一个 isAnonymous(Authentication)
方法,它允许感兴趣的类考虑这种特殊类型的身份验证状态。ExceptionTranslationFilter
在处理 AccessDeniedException
实例时使用此接口。如果抛出 AccessDeniedException
并且身份验证为匿名类型,则过滤器不会抛出 403(禁止)响应,而是启动 AuthenticationEntryPoint
,以便主体可以正确地进行身份验证。这是一个必要的区别。否则,主体将始终被视为“已验证”,并且永远不会有机会通过表单、基本、摘要或其他一些正常身份验证机制登录。
我们经常看到早期拦截器配置中的 ROLE_ANONYMOUS
属性被替换为 IS_AUTHENTICATED_ANONYMOUSLY
,在定义访问控制时,它们实际上是同一回事。这是 AuthenticatedVoter
使用的一个示例,我们在 授权章节 中介绍了它。它使用 AuthenticationTrustResolver
来处理此特定配置属性并授予匿名用户访问权限。AuthenticatedVoter
方法更强大,因为它可以让你区分匿名用户、记住我用户和完全经过身份验证的用户。但是,如果你不需要此功能,则可以使用 Spring Security 的标准 RoleVoter
处理的 ROLE_ANONYMOUS
。
使用 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"
}
}
将始终返回“非匿名”,即使对于匿名请求也是如此。原因是 Spring MVC 使用 HttpServletRequest#getPrincipal
解析参数,当请求为匿名时,该参数为 null
。
如果你想在匿名请求中获取 Authentication
,请改用 @CurrentSecurityContext
-
Java
-
Kotlin
@GetMapping("/")
public String method(@CurrentSecurityContext SecurityContext context) {
return context.getAuthentication().getName();
}
@GetMapping("/")
fun method(@CurrentSecurityContext context : SecurityContext) : String =
context!!.authentication!!.name