今日のデジタル環境では、安全でユーザーフレンドリーな認証方法を提供することが非常に重要です。このような方法の 1 つとして人気が高まっているのが、ワンタイム トークン (OTT) 認証であり、多くの場合、電子メールで送信される「マジック リンク」として実装されます。 Spring Security 6.4.0 は、すぐに使用できる実装を含む、OTT 認証の強力な組み込みサポートを提供します。この包括的なガイドでは、組み込みソリューションとカスタム実装の両方を使用して安全な OTT 認証を実装する方法を説明します。
実装に入る前に、ワンタイム トークン (OTT) がワンタイム パスワード (OTP) とは異なることを理解することが重要です。 OTP システムは通常、初期設定が必要で、パスワード生成には外部ツールに依存しますが、OTT システムはユーザーの観点から見るとよりシンプルです。つまり、認証に使用できる一意のトークン (通常は電子メール経由) を受け取ります。
主な違いは次のとおりです:
Spring Security は、OneTimeTokenService の 2 つの実装を提供します。
InMemoryOneTimeTokenService:
JdbcOneTimeTokenService:
よりシンプルなインメモリ ソリューションを実装する方法は次のとおりです。
@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(); } }
実稼働環境の場合は、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 );
トークンの生成と検証プロセスをさらに制御するには、カスタム実装を作成できます。
@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); }
@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); } }
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 認証を導入する場合:
適切な実装を選択してください
メール配信の設定
@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(); } }
Spring Security の OTT サポートは、安全でユーザーフレンドリーな認証を実装するための堅牢な基盤を提供します。組み込みの実装を選択するか、カスタム ソリューションを作成するかに関係なく、高いセキュリティ基準を維持しながら、ユーザーにパスワードなしのログイン オプションを提供できます。
OTT 認証を実装する場合は、次の点に注意してください。
このガイドに従うことで、Spring Security の堅牢なセキュリティ機能を活用しながら、アプリケーションのニーズを満たす安全でユーザーフレンドリーな OTT 認証システムを実装できます。
参考: https://docs.spring.io/spring-security/reference/servlet/authentication/onetimetoken.html
以上がSpring Security を使用したワンタイムトークン認証の実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。