匿名认证
概述
通常认为,采用“默认拒绝”立场是一种良好的安全实践,即明确指定允许的内容并拒绝所有其他内容。定义未认证用户可访问的内容也是类似的情况,特别是对于 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 添加到 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 在过滤器和认证提供者之间共享,因此前者创建的令牌会被后者接受。
|
不应将 |
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 方法更强大,因为它允许您区分匿名、记住我以及完全认证的用户。但是,如果您不需要此功能,则可以继续使用 ROLE_ANONYMOUS,它由 Spring Security 的标准 RoleVoter 处理。
使用 Spring MVC 获取匿名认证
这意味着这样的构造
-
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) {
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"
}
}