|
对于最新的稳定版本,请使用 Spring Security 7.0.4! |
匿名认证
概述
通常认为,采用“默认拒绝”(deny-by-default)的安全策略是一种良好的安全实践,即明确指定允许的内容,而禁止其他所有内容。
定义未认证用户可访问的内容也属于类似情况,尤其对于 Web 应用程序而言。
许多网站要求用户必须经过身份认证才能访问除少数 URL(例如主页和登录页面)之外的所有内容。
在这种情况下,为这些特定的 URL 定义访问配置属性,要比为每个受保护资源逐一配置更为简便。
换句话说,有时我们希望默认要求具备 ROLE_SOMETHING 角色,并仅为此规则设置少数例外,例如应用程序的登录页、登出页和主页。
你也可以完全将这些页面从过滤器链中排除,从而绕过访问控制检查,但出于其他原因(尤其是当这些页面对已认证用户表现出不同行为时),这种做法可能是不可取的。
这就是我们所说的匿名认证。
请注意,“匿名认证”的用户与未经认证的用户在概念上并没有实质性的区别。
Spring Security 的匿名认证只是为您提供了一种更便捷的方式来配置访问控制属性。
即使 getCallerPrincipal 中实际上存在一个匿名认证对象,对 Servlet API(例如 SecurityContextHolder)的调用仍然返回 null。
还有其他一些场景下匿名认证也很有用,例如当审计拦截器查询SecurityContextHolder以确定哪个主体(principal)负责了某项操作时。
如果类知道SecurityContextHolder始终包含一个Authentication对象而永远不会为null,那么这些类的编写就会更加健壮。
配置
当你使用 HTTP 配置(Spring Security 3.0 中引入)时,匿名认证支持会自动提供。
你可以通过使用 <anonymous> 元素来自定义(或禁用)该功能。
除非你使用传统的 bean 配置方式,否则无需配置此处描述的 bean。
三个类协同工作以提供匿名认证功能。
AnonymousAuthenticationToken 是 Authentication 的一个实现,用于存储适用于匿名主体的 GrantedAuthority 实例。
有一个对应的 AnonymousAuthenticationProvider,它被链接到 ProviderManager 中,以便接受 AnonymousAuthenticationToken 实例。
最后,一个 AnonymousAuthenticationFilter 在常规认证机制之后被链接,并在没有现有 Authentication 存在时自动向 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 在过滤器和身份验证提供者之间共享,以便前者创建的Tokens能被后者接受。
|
此处使用 |
userAttribute 的格式为 usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]。
在 userMap 的 InMemoryDaoImpl 属性中,等号后面的值也使用相同的语法。
如前所述,匿名认证的好处在于可以对所有 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 接口及其对应的 AuthenticationTrustResolverImpl 实现。
该接口提供了一个 isAnonymous(Authentication) 方法,允许相关类考虑到这种特殊的认证状态。
ExceptionTranslationFilter 在处理 AccessDeniedException 实例时会使用此接口。
如果抛出了 AccessDeniedException,且当前认证属于匿名类型,那么过滤器不会返回 403(禁止访问)响应,而是启动 AuthenticationEntryPoint,以便主体(principal)能够正确地进行身份认证。
这一区分是必要的。否则,主体将始终被视为“已认证”,从而永远无法通过表单、基本认证、摘要认证或其他常规认证机制进行登录。
我们经常看到早期拦截器配置中的 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"
}
}
即使对于匿名请求,也始终会返回“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"
}
}