在当今的数字环境中,提供安全且用户友好的身份验证方法至关重要。一种越来越流行的方法是一次性令牌 (OTT) 身份验证,通常以通过电子邮件发送的“魔术链接”的形式实现。 Spring Security 6.4.0 为 OTT 身份验证提供强大的内置支持,包括即用型实现。在这份综合指南中,我们将探讨如何使用内置解决方案和自定义实现来实现安全的 OTT 身份验证。
了解一次性令牌与一次性密码
在深入实施之前,了解一次性令牌 (OTT) 与一次性密码 (OTP) 的不同非常重要。虽然 OTP 系统通常需要初始设置并依赖外部工具来生成密码,但从用户角度来看,OTT 系统更简单 - 它们会收到可用于身份验证的唯一令牌(通常通过电子邮件)。
主要区别包括:
- OTT 不需要初始用户设置
- 令牌由您的应用程序生成和交付
- 每个令牌通常只能使用一次,并在设定时间后过期
可用的内置实现
Spring Security 提供了 OneTimeTokenService 的两种实现:
-
InMemoryOneTimeTokenService:
- 将令牌存储在内存中
- 非常适合开发和测试
- 不适合生产或集群环境
- 应用程序重新启动时令牌会丢失
-
JdbcOneTimeTokenService:
- 将令牌存储在数据库中
- 适合生产使用
- 在集群环境中工作
- 具有自动清理功能的持久存储
使用 InMemoryOneTimeTokenService
以下是如何实现更简单的内存解决方案:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login/**", "/ott/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()) .oneTimeTokenLogin(Customizer.withDefaults()); // Uses InMemoryOneTimeTokenService by default return http.build(); } }
使用 JdbcOneTimeTokenService
对于生产环境,使用 JDBC 实现:
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired JdbcTemplate jdbcTemplate; @Bean public OneTimeTokenService oneTimeTokenService() { return new JdbcOneTimeTokenService(jdbcTemplate); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login/**", "/ott/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()) .oneTimeTokenLogin(Customizer.withDefaults()); return http.build(); } }
JdbcOneTimeTokenService 所需的表结构:
CREATE TABLE one_time_tokens ( token_value VARCHAR(255) PRIMARY KEY, username VARCHAR(255) NOT NULL, issued_at TIMESTAMP NOT NULL, expires_at TIMESTAMP NOT NULL, used BOOLEAN NOT NULL );
定制实施
为了更好地控制令牌生成和验证过程,您可以创建自定义实现:
1. Token实体和存储库
@Entity @Table(name = "one_time_tokens") public class OneTimeToken { @Id @GeneratedValue private Long id; private String tokenValue; private String username; private LocalDateTime createdAt; private LocalDateTime expiresAt; private boolean used; // Getters and setters omitted for brevity } @Repository public interface OneTimeTokenRepository extends JpaRepository<onetimetoken long> { Optional<onetimetoken> findByTokenValueAndUsedFalse(String tokenValue); void deleteByExpiresAtBefore(LocalDateTime dateTime); } </onetimetoken></onetimetoken>
2. 自定义令牌服务
@Service @Transactional public class PersistentOneTimeTokenService implements OneTimeTokenService { private static final int TOKEN_VALIDITY_MINUTES = 15; @Autowired private OneTimeTokenRepository tokenRepository; @Override public OneTimeToken generate(GenerateOneTimeTokenRequest request) { String tokenValue = UUID.randomUUID().toString(); LocalDateTime now = LocalDateTime.now(); OneTimeToken token = new OneTimeToken(); token.setTokenValue(tokenValue); token.setUsername(request.getUsername()); token.setCreatedAt(now); token.setExpiresAt(now.plusMinutes(TOKEN_VALIDITY_MINUTES)); token.setUsed(false); return return new DefaultOneTimeToken(token.getTokenValue(),token.getUsername(), Instant.now()); } @Override public Authentication consume(ConsumeOneTimeTokenRequest request) { OneTimeToken token = tokenRepository.findByTokenValueAndUsedFalse(request.getTokenValue()) .orElseThrow(() -> new BadCredentialsException("Invalid or expired token")); if (token.getExpiresAt().isBefore(LocalDateTime.now())) { throw new BadCredentialsException("Token has expired"); } token.setUsed(true); tokenRepository.save(token); UserDetails userDetails = loadUserByUsername(token.getUsername()); return new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); } }
实施令牌交付
Spring Security 不处理令牌传递,因此您需要实现它:
@Component public class EmailMagicLinkHandler implements OneTimeTokenGenerationSuccessHandler { @Autowired private JavaMailSender mailSender; private final OneTimeTokenGenerationSuccessHandler redirectHandler = new RedirectOneTimeTokenGenerationSuccessHandler("/ott/check-email"); @Override public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken token) throws IOException, ServletException { String magicLink = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)) .replacePath(request.getContextPath()) .replaceQuery(null) .fragment(null) .path("/login/ott") .queryParam("token", token.getTokenValue()) .toUriString(); SimpleMailMessage message = new SimpleMailMessage(); message.setTo(getUserEmail(token.getUsername())); message.setSubject("Your Sign-in Link"); message.setText("Click here to sign in: " + magicLink); mailSender.send(message); redirectHandler.handle(request, response, token); } }
自定义 URL 和页面
Spring Security 提供了多种自定义选项:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login/**", "/ott/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()) .oneTimeTokenLogin(Customizer.withDefaults()); // Uses InMemoryOneTimeTokenService by default return http.build(); } }
生产注意事项
在生产中部署 OTT 身份验证时:
-
选择正确的实施
- 使用 JdbcOneTimeTokenService 或自定义实现进行生产
- InMemoryOneTimeTokenService 只能用于开发/测试
配置电子邮件传送
@Configuration @EnableWebSecurity public class SecurityConfig { @Autowired JdbcTemplate jdbcTemplate; @Bean public OneTimeTokenService oneTimeTokenService() { return new JdbcOneTimeTokenService(jdbcTemplate); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login/**", "/ott/**").permitAll() .anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()) .oneTimeTokenLogin(Customizer.withDefaults()); return http.build(); } }
-
安全最佳实践
- 设置适当的令牌过期时间(建议 15 分钟)
- 对代币生成实施速率限制
- 对所有端点使用 HTTPS
- 监控失败的身份验证尝试
- 确保代币是一次性的,使用后立即失效
- 实现过期令牌的自动清理
- 使用安全随机令牌生成来防止猜测
它是如何运作的
- 用户通过提交电子邮件地址请求令牌
- 系统生成安全令牌并通过电子邮件发送魔法链接
- 用户点击链接并被重定向到令牌提交页面
- 系统验证令牌并验证用户(如果有效)
结论
Spring Security 的 OTT 支持为实现安全、用户友好的身份验证提供了坚实的基础。无论您选择内置实现还是创建自定义解决方案,您都可以为用户提供无密码登录选项,同时保持高安全标准。
实施 OTT 身份验证时,请记住:
- 选择适合您环境的实施
- 实施安全令牌交付
- 配置正确的令牌过期时间
- 遵循安全最佳实践
- 创建用户友好的错误处理和重定向
- 实施适当的电子邮件模板以获得专业外观
通过遵循本指南,您可以实现安全且用户友好的 OTT 身份验证系统,满足您的应用程序的需求,同时利用 Spring Security 强大的安全功能。
参考:https://docs.spring.io/spring-security/reference/servlet/authentication/onetimetoken.html
以上是使用 Spring Security 实现一次性令牌身份验证的详细内容。更多信息请关注PHP中文网其他相关文章!

新兴技术对Java的平台独立性既有威胁也有增强。1)云计算和容器化技术如Docker增强了Java的平台独立性,但需要优化以适应不同云环境。2)WebAssembly通过GraalVM编译Java代码,扩展了其平台独立性,但需与其他语言竞争性能。

不同JVM实现都能提供平台独立性,但表现略有不同。1.OracleHotSpot和OpenJDKJVM在平台独立性上表现相似,但OpenJDK可能需额外配置。2.IBMJ9JVM在特定操作系统上表现优化。3.GraalVM支持多语言,需额外配置。4.AzulZingJVM需特定平台调整。

平台独立性通过在多种操作系统上运行同一套代码,降低开发成本和缩短开发时间。具体表现为:1.减少开发时间,只需维护一套代码;2.降低维护成本,统一测试流程;3.快速迭代和团队协作,简化部署过程。

Java'splatformindependencefacilitatescodereusebyallowingbytecodetorunonanyplatformwithaJVM.1)Developerscanwritecodeonceforconsistentbehavioracrossplatforms.2)Maintenanceisreducedascodedoesn'tneedrewriting.3)Librariesandframeworkscanbesharedacrossproj

要解决Java应用程序中的平台特定问题,可以采取以下步骤:1.使用Java的System类查看系统属性以了解运行环境。2.利用File类或java.nio.file包处理文件路径。3.根据操作系统条件加载本地库。4.使用VisualVM或JProfiler优化跨平台性能。5.通过Docker容器化确保测试环境与生产环境一致。6.利用GitHubActions在多个平台上进行自动化测试。这些方法有助于有效地解决Java应用程序中的平台特定问题。

类加载器通过统一的类文件格式、动态加载、双亲委派模型和平台无关的字节码,确保Java程序在不同平台上的一致性和兼容性,实现平台独立性。

Java编译器生成的代码是平台无关的,但最终执行的代码是平台特定的。1.Java源代码编译成平台无关的字节码。2.JVM将字节码转换为特定平台的机器码,确保跨平台运行但性能可能不同。

多线程在现代编程中重要,因为它能提高程序的响应性和资源利用率,并处理复杂的并发任务。JVM通过线程映射、调度机制和同步锁机制,在不同操作系统上确保多线程的一致性和高效性。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

安全考试浏览器
Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

Atom编辑器mac版下载
最流行的的开源编辑器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

SublimeText3汉化版
中文版,非常好用

SecLists
SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。