领先一步
VMware 提供培训和认证,助您加速进步。
了解更多Spring Security 6.4.1 是你处理身份验证和授权的“一站式商店”,而这个版本简直是重磅炸弹!发行说明充满了可能性!
发行说明是骗人的!
我的意思是,它们并非全然是谎言。只是它们没有很好地捕捉和传达这个版本有多么出色。这个版本用户可见的新功能比以往许多版本都要多。这可能是我最喜欢的 Spring Security 版本,至少自从它开始拥有 Java 配置 DSL 以来是这样!
看看那些发布说明。看到了那些关于无密码认证和一次性令牌登录的简短章节了吗?是的。那是谎言。这些东西应该有它们自己的篇章!我们会回过头来谈论它们。我保证。我们先快速看一下其他部分。太多的不同部分了。
@AuthorizeReturnObject功能正确配合,并且可以在@PreAuthorize和@PostAuthorize生命周期回调中引用Bean。SecurityAnnotationScanners API提供了一种扫描安全注解的方式,可以将Spring Security的选择和模板功能添加到自定义注解中。oauth2Login()方法现在接受OAurth2AuthorizationRequestResolver作为@Bean。ClientRegistrations现在支持外部获取的配置。login page()logout+jwt类型的退出令牌OAuth2ClientHttpRequestInterceptor配置Spring RestClient来发出受保护的资源请求。在发出HTTP请求时,您可以让它向下游服务提供您的令牌。registrationId已简化。application/samlmetadata+xml MIME类型。Remember Me cookieServerHttpSecurity现在将ServerWebExchangeFirewall对象作为Bean进行拾取。AclAuthorizationStrategyImpl支持RoleHierarchy类型,这也是一个相当新的类型!现在,让我们回到关于无密码认证和一次性令牌的讨论。
让我们来看一个简单的应用程序。
我们将有一个需要锁定的HTTP控制器
package com.example.bootiful_34.security;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.security.Principal;
import java.util.Map;
@Controller
@ResponseBody
class SecuredController {
@GetMapping("/admin")
Map<String, String> admin(Principal principal) {
return Map.of("admin", principal.getName());
}
@GetMapping("/")
Map<String, String> hello(Principal principal) {
return Map.of("user", principal.getName());
}
}
要锁定它,我们需要定义一个SecurityFilterChain,其中包含一些常规项:HTTP表单登录、一些授权规则等。
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(requests -> requests
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers("/error").permitAll()
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
// ...
.build();
}
我们需要让Spring Security了解我们系统中的用户,所以让我们提供一个UserDetailsService
package com.example.bootiful_34.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.http.MediaType;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@ImportRuntimeHints(UiResourcesRuntimeHintsRegistrar.class)
class SecurityConfiguration {
@Bean
UserDetailsService userDetailsService() {
var josh = User.withUsername("josh").password("pw").roles("USER").build();
var rob = User.withUsername("rob").password("pw").roles("USER", "ADMIN").build();
return new InMemoryUserDetailsManager(josh, rob);
}
// ...
}
这个应用程序有两个用户:josh和rob。josh只有一个角色USER,而rob同时拥有ADMIN和USER。
在非平凡的应用程序中,您应该使用一个指向身份提供者的替代`UserDetailsService`实现。或者,至少,一个不存储纯文本密码的SQL数据库。我在这里注册用户和他们的密码,但一个设计良好的系统将尽可能减少密码的使用。您关于密码的常识性智慧可能错了。美国国家标准与技术研究所(美国商务部的一个组织)去年(2024年)发布了其更新的密码定义指南。
NIST特别出版物800-63B提供了关于创建、处理、续订和存储密码(称为“记忆秘密”)的具体指导,并概述了对一次性令牌和基于硬件的身份验证器(例如,YubiKeys/WebAuthn)等替代方案的建议。让我们来看看他们的一些建议。
如果您的系统需要密码, there’s a lot to know. 幸运的是,Spring Security 可以为您完成以上大部分(甚至全部?)的指导。
用户选择的密码必须至少八个字符长,由CSP(凭证服务提供商)随机生成的密码必须至少六个字符长。不鼓励复杂性规则(例如,要求大小写混合、数字和符号)。密码不得被任意限制(例如,限制最大长度或排除某些字符)。密码必须与常用、泄露或预期密码的列表进行比对(例如,字典单词、重复模式、服务特定术语)。如果密码被标记为弱密码,用户必须选择一个更健壮的替代方案。建议使用密码强度计或反馈来帮助用户创建健壮的密码。该指南建议允许复制粘贴功能,以鼓励使用密码管理器,并提供输入的密码的可选显示,以最大程度地减少输入错误。该文档建议将连续登录失败次数限制在不超过100次,并实施验证码、增加延迟时间或其他自适应措施,以防止因滥用导致账户被锁定。有趣的是,它建议重新验证策略应根据保证级别而有所不同(例如,高保证级别需要每12小时或30分钟无活动后重新验证)。另一方面,密码更改不应被任意或定期要求。只有在有证据表明存在泄露的情况下,才应强制更改。它建议使用单向密钥派生函数(例如,PBKDF2、BCrypt或Argon2)对密码进行哈希和加盐处理。Spring Security默认使用BCrypt。
NIST建议使用一次性令牌(OTP)等密码替代方案。单因素OTP设备生成基于时间或基于计数器的OTP。OTP必须具有密码学安全性,并具有至少20位的熵。多因素OTP设备在生成OTP之前需要第二个身份验证因素(例如,PIN或生物特征)。带外身份验证利用辅助通信渠道(例如,移动设备)进行OTP传递或确认,这需要安全的渠道并限制弱方法。还鼓励使用基于硬件的身份验证器(例如,YubiKeys、WebAuthn)。WebAuthn是FIDO联盟的一部分,并被鼓励作为一种健壮且抗网络钓鱼的身份验证方法。像YubiKeys这样的硬件身份验证器使用密码学协议来确保安全。多因素密码学设备必须结合“你拥有的东西”(例如,YubiKey)与“你知道的东西”(例如,PIN)或“你是什么”(例如,生物特征)。密钥应保留在防篡改硬件内,并且无法提取。
通过优先考虑这些方法,NIST强调了向多因素身份验证(MFA)和密码学解决方案过渡,作为比传统密码更安全的选择。
因此,在Spring Security应用程序的上下文中,一次性令牌通过依赖于只有用户自己才拥有的带外因素来验证用户——也许是用户接收短信的能力、访问其电子邮件等。您可能以前在其他地方使用过此功能。您访问一个网站,输入您的用户名,然后该网站会向您发送一封包含您可以点击登录的链接的电子邮件。这些有时被称为“魔法链接”。
Spring Security不提供与您喜欢的电子邮件提供商或消息应用程序的集成。您可以使用Sendgrid、Twilio或数百万个其他服务中的任何一个来实现。但Spring Security确实提供了创建和验证链接的基础架构。
这是我们在Spring Security配置中必须添加的一小段代码,以使其能够打印
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// ..
.oneTimeTokenLogin(configurer -> configurer.tokenGenerationSuccessHandler(
(request, response, oneTimeToken) -> {
var msg = "go to https://:8080/login/ott?token=" + oneTimeToken.getTokenValue();
System.out.println(msg);
response.setContentType(MediaType.TEXT_PLAIN_VALUE);
response.getWriter().print("you've got console mail!");
}))
// ..
.build();
}
看到了吗?这只是一个单独的lambda表达式,您可以在其中提供足够的信息来创建并向用户发送一个链接,一旦用户点击该链接,就可以让他们登录。简单!在这个例子中,我只是通过点击控制台中的链接来登录。同样,您可能发送电子邮件或其他更实际的方式。
这非常方便,并让用户不必担心或——更糟糕的是——共享两个密码。希望他们已经锁定了他们的电子邮件密码!我宁愿他们有一个不跨网站共享的良好密码,而不是十几个糟糕且共享的密码。
另一种方法——另一层安全性——可能比文本链接更复杂,以阻止潜在的黑客接触到。例如指纹、面部ID扫描或独立的硬件加密狗。问题是:如何在需要的地方集成这些东西?为了让它们正常工作,我们需要修改浏览器、服务器端处理逻辑、操作系统等,以便它们都能说同一种标准协议。
您会很高兴地知道,这几乎是每个人都在做的事情。该协议名为WebAuthn,其背后的组织名为FIDO Alliance。FIDO Alliance得到了普遍支持!以下是其一些最杰出的成员。名单包括DELL、Apple、Google、Intuit、NTT DOCOMO、Microsoft、meta、LastPass、DashLane、Bank Of America、1Password、Intel、CISCO、CVS Health、American Express、VISA,以及无数其他公司。关键是,那些在乎金钱的人都在支持这种方法。浏览器也是如此!所有主流浏览器——Chrome、Safari、Edge、Firefox、iOS上的Safari、Android上的Chrome、iOS上的Chrome以及iOS上的Edge——都支持它。它无处不在!现在,多亏了Spring Security的这个版本,它也可以轻松地集成到您的应用程序中。
以下是我们Spring Security过滤器链示例中的相关配置
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
// ...
.webAuthn(c -> c
.rpId("localhost")
.rpName("bootiful passkeys")
.allowedOrigins("https://:8080")
)
// ...
.build();
}
重启应用程序,然后登录到localhost:8080/webauthn/register。注册您的无密码认证。我在Apple生态系统中,所以它提示我在iPhone上进行FaceID,或者在我的macOS Apple Silicon笔记本电脑上使用TouchID。然后,浏览器将无密码认证存储在操作系统钥匙串中。它现在已通过iCloud进行联合。所以,我不仅有了一种有效的登录方式,而且它还与我的iCloud帐户绑定,因此我可以在一台设备上使用Face ID,在另一台设备上使用Touch ID登录。无需额外工作!
要查看效果,请注销:localhost:8080/logout。
安全性不仅非常出色,而且对用户来说也更轻松!这不是很酷吗?