{"id":52133,"date":"2025-05-14T00:00:00","date_gmt":"2025-05-14T07:00:00","guid":{"rendered":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/blog\/building-a-one-time-token-login-system-with-spring-security\/"},"modified":"2025-05-14T00:00:00","modified_gmt":"2025-05-14T07:00:00","slug":"building-a-one-time-token-login-system-with-spring-security","status":"publish","type":"post","link":"https:\/\/griddb.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/","title":{"rendered":"Building a One-Time Token Login System with Spring Security"},"content":{"rendered":"<p>Imagine trying to keep track of all your passwords. It&#8217;s a daunting task, isn&#8217;t it? With passwords required for social media, online shopping, e-wallet apps, and various computer tools, it&#8217;s easy to lose count. Forgetting a password can be frustrating, and having to create a new one every time can be a hassle.<\/p>\n<p>Fortunately, there&#8217;s a better and safer way to log in to websites: One-Time Tokens. Unlike traditional passwords, which are used repeatedly, One-Time Tokens provide a unique code that can only be used once. This token is designed to be used within a short timeframe, and once it&#8217;s used or expires, it becomes invalid. It&#8217;s like a secure, self-destructing message.<\/p>\n<p>So, why are One-Time Tokens a better option than traditional passwords? Here are a few key benefits:<\/p>\n<ul>\n<li>\n<p>Enhanced Security: By reducing our reliance on passwords, we minimize the risk of password-related vulnerabilities. This means no more weak passwords, no more password reuse across different sites, and even if someone intercepts a token, it&#8217;s likely to be expired and useless.<\/p>\n<\/li>\n<li>\n<p>Improve User Experience: Let&#8217;s face it, remembering passwords can be a pain. One-Time Tokens simplify the process, allowing users to click a link or enter a short code, making login a smoother experience.<\/p>\n<\/li>\n<li>\n<p>Fewer Password Reset: With One-Time Tokens, the need for password resets decreases significantly. Since users don&#8217;t have to constantly remember and re-enter passwords, there&#8217;s less to forget in the first place.<\/p>\n<\/li>\n<\/ul>\n<p>Let&#8217;s dive into the world of One-Time Tokens login with Spring Security! We will explore what they are and how we can actually implement them.<\/p>\n<h2>One-Time Token Login: The Flow<\/h2>\n<ol>\n<li>You navigate to the login page and enter your email address to start the process.<\/li>\n<li>The application generates a unique, one-time token. This token is a long, random string that&#8217;s impossible to guess.<\/li>\n<li>The system sends this token to you via email, SMS, or WhatsApp.<\/li>\n<li>You receive the token and enter it on the login page. Often, you&#8217;ll just click a magic link in the message that contains the token.<\/li>\n<li>The web application receives the token and checks:\n<ul>\n<li>Is the token valid? (Does it match one we generated?)<\/li>\n<li>Has it expired? (One-time tokens have a short lifespan for security)<\/li>\n<li>Has it already been used? (Remember, it&#8217;s one-time use only!)<\/li>\n<\/ul>\n<\/li>\n<li>If everything checks out, you&#8217;re logged in. The web application establishes a session for you.<\/li>\n<\/ol>\n<h2>  Let\u2019s Build This Thing!<\/h2>\n<h3>Maven<\/h3>\n<p>To enable the One-Time Token feature, we need to include the <code>spring-boot-starter-security<\/code> and <code>spring-boot-starter-web<\/code> dependencies.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-bash\">&lt;dependencies&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n            &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;\n        &lt;\/dependency&gt;\n        &lt;dependency&gt;\n            &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n            &lt;artifactId&gt;spring-boot-starter-security&lt;\/artifactId&gt;\n        &lt;\/dependency&gt;\n&lt;\/dependencies&gt;<\/code><\/pre>\n<\/div>\n<h3>Spring Security Configuration<\/h3>\n<p>Next, we configure the Spring Security and enable the form-login and One-Time Token Login:<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n  @Bean\n  public SecurityFilterChain securityFilterChain(\n      HttpSecurity http,\n      SendLinkOneTimeTokenGenerationSuccessHandler successHandler,\n      CustomOneTimeTokenService customOneTimeTokenService)\n      throws Exception {\n    AuthenticationSuccessHandler ottLoginsuccessHandler =\n        (request, response, authentication) -&gt; response.sendRedirect(\"\/\");\n\n    http.csrf(Customizer.withDefaults())\n        .authorizeHttpRequests(\n            authorize -&gt;\n                authorize\n                    .requestMatchers(\"\/error\", \"\/\", \"\/images\/**\", \"\/js\/*.js\", \"\/css\/*.css\")\n                    .permitAll()\n                    .requestMatchers(new AntPathRequestMatcher(\"\/authentication\/login\"))\n                    .permitAll()\n                    .requestMatchers(new AntPathRequestMatcher(\"\/logout\"))\n                    .permitAll()\n                    .requestMatchers(new AntPathRequestMatcher(\"\/webjars\/**\"))\n                    .permitAll()\n                    .requestMatchers(new AntPathRequestMatcher(\"\/ott\/sent\"))\n                    .permitAll()\n                    .requestMatchers(new AntPathRequestMatcher(\"\/ott\/submit\"))\n                    .permitAll()\n                    .anyRequest()\n                    .authenticated())\n        .formLogin(\n            form -&gt;\n                form.loginPage(\"\/authentication\/login\")\n                    .loginProcessingUrl(\"\/login\")\n                    .failureUrl(\"\/authentication\/login?failed\")\n                    .defaultSuccessUrl(\"\/\")\n                    .permitAll())\n        .headers(\n            httpSecurityHeaders -&gt; httpSecurityHeaders.frameOptions(FrameOptionsConfig::disable))\n        .logout(Customizer.withDefaults())\n        .oneTimeTokenLogin(\n            configurer -&gt;\n                configurer\n                    .tokenGenerationSuccessHandler(successHandler)\n                    .tokenService(customOneTimeTokenService)\n                    .showDefaultSubmitPage(false)\n                    .authenticationSuccessHandler(ottLoginsuccessHandler));\n\n    return http.build();\n  }\n}<\/code><\/pre>\n<\/div>\n<ul>\n<li><code>@EnableWebSecurity<\/code> annotation: enable Spring Security&#8217;s web security support and provide the Spring MVC integration.<\/li>\n<li><code>SecurityFilterChain<\/code> bean to add custom filter in Spring Security Context.<\/li>\n<li>Configures <code>authorizeHttpRequests<\/code> defines which URL path should be secured and which should not.<\/li>\n<li>Configures <code>formLogin()<\/code> to customize the form based authentication. Configures <code>loginPage()<\/code> for redirecting to <code>\/authentication\/login<\/code> if authentication is required. Configures <code>loginProcessingUrl<\/code> to validate the submitted credentials. <code>failureUrl<\/code> specify the URL to send users if authentication fails.<\/li>\n<li>Configures <code>headers()<\/code>. We enable all the default headers except the X-Frame-Options headers.<\/li>\n<li><code>.logout(Customizer.withDefaults())<\/code> provides logout support using default settings. The default is that accessing the URL &#8220;\/logout&#8221; will log the user out by invalidating the HTTP Session, cleaning up any <code>rememberMe<\/code> authentication that was configured, clearing the <code>SecurityContextHolder<\/code>, and then redirect to <code>\/login?success<\/code>.<\/li>\n<\/ul>\n<p>Enable One-Time Token Login support and customize it with <code>oneTimeTokenLogin()<\/code> method :<br \/>\n&#8211; <code>tokenGenerationSuccessHandler<\/code> : Specifies strategy to be used to handle generated one-time tokens. We will create a custom handler that implements <code>OneTimeTokenGenerationSuccessHandler<\/code>.<br \/>\n&#8211; <code>tokenService<\/code> : Configures the <code>OneTimeTokenService<\/code> used to generate and consume the <code>OneTimeToken<\/code>.<br \/>\n&#8211; <code>showDefaultSubmitPage(false)<\/code> : disable the default One-Time Token submit page.<br \/>\n&#8211; <code>authenticationSuccessHandler<\/code> : Specifies the <code>AuthenticationSuccessHandler<\/code> strategy used to handle a successful user authentication. For demo, we redirect user to the home page.<\/p>\n<h3>Custom OneTimeTokenGenerationSuccessHandler<\/h3>\n<p>Next, we need to implement a custom OneTimeTokenGenerationSuccessHandler to deliver the token to the end user.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Component\npublic class SendLinkOneTimeTokenGenerationSuccessHandler\n    implements OneTimeTokenGenerationSuccessHandler {\n\n  private final OttEmailService emailService;\n  private final FlashMapManager flashMapManager = new SessionFlashMapManager();\n\n  public SendLinkOneTimeTokenGenerationSuccessHandler(OttEmailService emailService) {\n    this.emailService = emailService;\n  }\n\n  @Override\n  @SneakyThrows\n  public void handle(\n      HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken)\n      throws IOException, ServletException {\n    UriComponentsBuilder builder =\n        UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))\n            .replacePath(request.getContextPath())\n            .replaceQuery(null)\n            .fragment(null)\n            .path(\"\/ott\/submit\")\n            .queryParam(\"token\", oneTimeToken.getTokenValue());\n    String link = builder.toUriString();\n    CompletableFuture.runAsync(() -&gt; emailService.sendEmail(oneTimeToken.getUsername(), link));\n\n    RedirectView redirectView = new RedirectView(\"\/ott\/sent\");\n    redirectView.setExposeModelAttributes(false);\n    FlashMap flashMap = new FlashMap();\n    flashMap.put(\"token\", oneTimeToken.getTokenValue());\n    flashMap.put(\"ottSubmitUrl\", link);\n    flashMapManager.saveOutputFlashMap(flashMap, request, response);\n    redirectView.render(flashMap, request, response);\n  }\n}<\/code><\/pre>\n<\/div>\n<p>This component will do a number of things:<\/p>\n<ul>\n<li>Generate the magic link containing the one-time token.<\/li>\n<li>Call <code>emailService.sendEmail()<\/code> to send the email to the user with magic link.<\/li>\n<li>For demo, we redirect user to the <code>\/ott\/sent<\/code> page.<\/li>\n<\/ul>\n<h3>Custom Success Page<\/h3>\n<p>Create a controller and an HTML template to handle this page. For demo purpose, we forward the token to the custom submit page.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Controller\n@RequestMapping(\"\/ott\")\npublic class OttController {\n  @GetMapping(\"\/sent\")\n  public String sent(Model model) {\n    return \"ott\/sent\";\n  }\n\n  @GetMapping(\"\/submit\")\n  public String submit(Model model, @RequestParam(\"token\") String token) {\n    model.addAttribute(\"token\", token);\n    return \"ott\/submit\";\n  }\n}<\/code><\/pre>\n<\/div>\n<h4><strong><code>sent.html<\/code><\/strong><\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-html\">&lt;html xmlns_th=\"http:\/\/www.thymeleaf.org\" xmlns_layout=\"http:\/\/www.ultraq.net.nz\/thymeleaf\/layout\"\n        layout_decorate=\"~{layout}\"&gt;\n    &lt;head&gt;\n        &lt;title&gt;OTT Sent&lt;\/title&gt;\n    &lt;\/head&gt;\n    &lt;body&gt;\n        &lt;div layout_fragment=\"content\"&gt;\n            &lt;p&gt;We just sent you an email. Please follow the provided link to log in.&lt;\/p&gt;\n            &lt;p&gt;For testing here is the &lt;a th_href=\"${ottSubmitUrl}\"&gt;submit link&lt;\/a&gt;&lt;\/p&gt;\n        &lt;\/div&gt;\n    &lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<\/div>\n<h4><strong><code>submit.html<\/code><\/strong><\/h4>\n<div class=\"clipboard\">\n<pre><code class=\"language-html\">&lt;body&gt;\n    &lt;div layout_fragment=\"content\"&gt;\n        &lt;div class=\"d-flex flex-wrap mb-4\"&gt;\n            &lt;h1 class=\"flex-grow-1\"&gt;Login OTT&lt;\/h1&gt;\n        &lt;\/div&gt;\n        &lt;form th_action=\"@{\/login\/ott}\" method=\"post\"&gt;\n            &lt;div class=\"row mb-3\"&gt;\n                &lt;label for=\"token\" class=\"form-check-label\"&gt;Token&lt;\/label&gt;\n                &lt;input type=\"text\" id=\"token\" name=\"token\" th_value=\"${token}\" placeholder=\"Token\" required=\"true\"\n                    autofocus=\"autofocus\" class=\"form-control\"\/&gt;\n            &lt;\/div&gt;\n            &lt;button class=\"btn btn-primary\" type=\"submit\"&gt;Sign in&lt;\/button&gt;\n        &lt;\/form&gt;\n    &lt;\/div&gt;\n&lt;\/body&gt;<\/code><\/pre>\n<\/div>\n<h3>Custom OneTimeTokenService<\/h3>\n<p>Next, create a custom implementation of <code>OneTimeTokenService<\/code> interface. By customizing it, we can have a custom expire time, adding more info into token, and implement custom token value.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\npublic class CustomOneTimeTokenService implements OneTimeTokenService {\n  private final Map&lt;String, OneTimeToken&gt; oneTimeTokens = new ConcurrentHashMap&lt;&gt;();\n\n  private Clock clock = Clock.systemUTC();\n\n  @Override\n  @NonNull\n  public OneTimeToken generate(GenerateOneTimeTokenRequest request) {\n    String token = UUID.randomUUID().toString();\n    Instant expiresAt = this.clock.instant().plus(5, ChronoUnit.MINUTES);\n\n    OneTimeToken oneTimeToken = new DefaultOneTimeToken(token, request.getUsername(), expiresAt);\n    oneTimeTokens.put(token, oneTimeToken);\n\n    return oneTimeToken;\n  }\n\n  @Override\n  @Nullable\n  public OneTimeToken consume(OneTimeTokenAuthenticationToken authenticationToken) {\n    log.info(\"Consume token: {}\", authenticationToken.getTokenValue());\n    OneTimeToken oneTimeToken = oneTimeTokens.remove(authenticationToken.getTokenValue());\n    if (oneTimeToken == null || isExpired(oneTimeToken)) {\n      return null;\n    }\n    return oneTimeToken;\n  }\n\n  private boolean isExpired(OneTimeToken oneTimeToken) {\n    return this.clock.instant().isAfter(oneTimeToken.getExpiresAt());\n  }\n}<\/code><\/pre>\n<\/div>\n<p><code>CustomOneTimeTokenService<\/code> class responsible for:<\/p>\n<ul>\n<li>Generating the token and storing it. For demo, we use in memory store.<\/li>\n<li>Consuming the token, we validate the token expiration and delete the token from the store.<\/li>\n<\/ul>\n<h3>Retrieving Users<\/h3>\n<p>Create custom <code>UserDetailsService<\/code> implementation to load user-specific data.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\n@AllArgsConstructor\npublic class CustomUserDetailService implements UserDetailsService {\n\n  private final UsersContainerClient usersContainerClient;\n\n  @Override\n  public UserDetails loadUserByUsername(String email) {\n    UserRecord user = usersContainerClient.getUserByEmail(email);\n    if (user == null) {\n      throw new UsernameNotFoundException(\"User not found\");\n    }\n\n    List&lt;SimpleGrantedAuthority&gt; authorities = new java.util.ArrayList&lt;&gt;();\n    authorities.add(new SimpleGrantedAuthority(\"ROLE_USER\"));\n\n    if (\"admin@example.com\".equals(user.email())) {\n      authorities.add(new SimpleGrantedAuthority(\"ROLE_ADMIN\"));\n    }\n\n    return new org.springframework.security.core.userdetails.User(\n        user.email(), user.password(), authorities);\n  }\n}<\/code><\/pre>\n<\/div>\n<p>The user object is stored and fetched using the <code>UsersContainerClient<\/code> class, which will handle the interaction with GridDB Cloud.<\/p>\n<div class=\"clipboard\">\n<pre><code class=\"language-java\">@Service\npublic class UsersContainerClient {\n  private static final String CONTAINER_NAME = \"Users\";\n  private final RestClient restClient;\n  private final String baseUrl;\n\n  public UsersContainerClient(\n      @Value(\"${griddb.base-url}\") String baseUrl,\n      @Value(\"${griddb.auth-token}\") String authToken) {\n    this.baseUrl = baseUrl;\n    this.restClient =\n        RestClient.builder()\n            .baseUrl(this.baseUrl)\n            .defaultHeader(\"Authorization\", \"Basic \" + authToken)\n            .defaultHeader(\"Content-Type\", MediaType.APPLICATION_JSON_VALUE)\n            .build();\n  }\n\n  private &lt;T&gt; T post(String uri, Object body, Class&lt;T&gt; responseType) {\n    try {\n      return restClient.post().uri(uri).body(body).retrieve().body(responseType);\n    } catch (GridDbException e) {\n      throw e;\n    } catch (Exception e) {\n      throw new GridDbException(\n          \"Failed to execute POST request\", HttpStatusCode.valueOf(500), e.getMessage(), e);\n    }\n  }\n\n  public UserRecord getUserByEmail(String email) {\n    String statement =\n        String.format(\"SELECT id, email, name, \"password\" FROM Users where email == '%s'\", email);\n    return getOneUser(statement);\n  }\n\n  private UserRecord getOneUser(String statement) {\n    String type = \"sql-select\";\n    GridDbCloudSQLSelectInput input = new GridDbCloudSQLSelectInput(type, statement);\n    var response = post(\"\/sql\", List.of(input), GridDbCloudSQLOutPut[].class);\n    log.info(\"Output: {}\", response[0]);\n    if (response[0].results().size() == 0) {\n      return null;\n    }\n    UserRecord foundUser = null;\n    for (List&lt;String&gt; row : response[0].results()) {\n      if (row.size() &lt; 4) {\n        break;\n      }\n      foundUser = new UserRecord(row.get(0), row.get(1), row.get(2), row.get(3));\n    }\n    log.info(\"Found user: {}\", foundUser);\n    return foundUser;\n  }\n}\n\nrecord GridDbCloudSQLSelectInput(String type, @JsonProperty(\"stmt\") String statement) {}\n\nrecord GridDbCloudSQLOutPut(\n    @JsonProperty(\"columns\") List&lt;GDCColumnInfo&gt; columns,\n    @JsonProperty(\"results\") List&lt;List&lt;String&gt;&gt; results,\n    @JsonProperty(\"responseSizeByte\") long responseSizeByte) {}<\/code><\/pre>\n<\/div>\n<ul>\n<li>To communicate with GridDB Cloud via HTTP requests, we create a Spring <code>RestClient<\/code> instance with HTTP basic authentication.<\/li>\n<li>We <code>POST<\/code> the <code>sql-select<\/code> query and convert the response into <code>UserRecord<\/code><\/li>\n<\/ul>\n<h3>Demo<\/h3>\n<p>For demo, we have added demo users (<code>admin@example.com, user@example.com<\/code>) on application startup. Full code can be access on <a href=\"https:\/\/github.com\/alifruliarso\/springsecurity-ott\/tree\/main\">Github<\/a>.<\/p>\n<p><a href=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/onetimetokendemo.gif\"><img fetchpriority=\"high\" decoding=\"async\" src=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/05\/onetimetokendemo.gif\" alt=\"\" width=\"1464\" height=\"664\" class=\"aligncenter size-full wp-image-31538\" \/><\/a><\/p>\n<h2>Conclusion<\/h2>\n<p>One-time tokens are a great leap forward in enhancing online security while keeping things user-friendly. Using frameworks like Spring Security can make it easier to implement these advanced security measures.<\/p>\n<p>When using one-time tokens in production, keep these key factors in mind:<br \/>\n&#8211; <strong>Token Validity<\/strong>: Decide how long each token should stay active.<br \/>\n&#8211; <strong>Delivery Reliability<\/strong>: Ensure your token delivery method is dependable.<br \/>\n&#8211; <strong>Security<\/strong>: Make sure the token generation process is cryptographically secure.<br \/>\n&#8211; <strong>Storage Safety<\/strong>: Store tokens securely to prevent unauthorized access.<\/p>\n<p>By addressing these aspects, you can create a robust and user-friendly security system.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Imagine trying to keep track of all your passwords. It&#8217;s a daunting task, isn&#8217;t it? With passwords required for social media, online shopping, e-wallet apps, and various computer tools, it&#8217;s easy to lose count. Forgetting a password can be frustrating, and having to create a new one every time can be a hassle. Fortunately, there&#8217;s [&hellip;]<\/p>\n","protected":false},"author":41,"featured_media":52134,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[121],"tags":[],"class_list":["post-52133","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.1.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Building a One-Time Token Login System with Spring Security | GridDB: Open Source Time Series Database for IoT<\/title>\n<meta name=\"description\" content=\"Imagine trying to keep track of all your passwords. It&#039;s a daunting task, isn&#039;t it? With passwords required for social media, online shopping, e-wallet\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a One-Time Token Login System with Spring Security | GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"og:description\" content=\"Imagine trying to keep track of all your passwords. It&#039;s a daunting task, isn&#039;t it? With passwords required for social media, online shopping, e-wallet\" \/>\n<meta property=\"og:url\" content=\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\" \/>\n<meta property=\"og:site_name\" content=\"GridDB: Open Source Time Series Database for IoT\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/griddbcommunity\/\" \/>\n<meta property=\"article:published_time\" content=\"2025-05-14T07:00:00+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/griddb.net\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1536\" \/>\n\t<meta property=\"og:image:height\" content=\"1024\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"griddb-admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:site\" content=\"@GridDBCommunity\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"griddb-admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\"},\"author\":{\"name\":\"griddb-admin\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\"},\"headline\":\"Building a One-Time Token Login System with Spring Security\",\"datePublished\":\"2025-05-14T07:00:00+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\"},\"wordCount\":935,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png\",\"articleSection\":[\"Blog\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\",\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\",\"name\":\"Building a One-Time Token Login System with Spring Security | GridDB: Open Source Time Series Database for IoT\",\"isPartOf\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage\"},\"thumbnailUrl\":\"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png\",\"datePublished\":\"2025-05-14T07:00:00+00:00\",\"description\":\"Imagine trying to keep track of all your passwords. It's a daunting task, isn't it? With passwords required for social media, online shopping, e-wallet\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage\",\"url\":\"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png\",\"contentUrl\":\"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png\",\"width\":1536,\"height\":1024},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website\",\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/\",\"name\":\"GridDB: Open Source Time Series Database for IoT\",\"description\":\"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL\",\"publisher\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization\",\"name\":\"Fixstars\",\"url\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"contentUrl\":\"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png\",\"width\":200,\"height\":83,\"caption\":\"Fixstars\"},\"image\":{\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/griddbcommunity\/\",\"https:\/\/x.com\/GridDBCommunity\",\"https:\/\/www.linkedin.com\/company\/griddb-by-toshiba\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233\",\"name\":\"griddb-admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g\",\"caption\":\"griddb-admin\"},\"url\":\"https:\/\/griddb.net\/en\/author\/griddb-admin\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Building a One-Time Token Login System with Spring Security | GridDB: Open Source Time Series Database for IoT","description":"Imagine trying to keep track of all your passwords. It's a daunting task, isn't it? With passwords required for social media, online shopping, e-wallet","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/","og_locale":"en_US","og_type":"article","og_title":"Building a One-Time Token Login System with Spring Security | GridDB: Open Source Time Series Database for IoT","og_description":"Imagine trying to keep track of all your passwords. It's a daunting task, isn't it? With passwords required for social media, online shopping, e-wallet","og_url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/","og_site_name":"GridDB: Open Source Time Series Database for IoT","article_publisher":"https:\/\/www.facebook.com\/griddbcommunity\/","article_published_time":"2025-05-14T07:00:00+00:00","og_image":[{"width":1536,"height":1024,"url":"https:\/\/griddb.net\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png","type":"image\/png"}],"author":"griddb-admin","twitter_card":"summary_large_image","twitter_creator":"@GridDBCommunity","twitter_site":"@GridDBCommunity","twitter_misc":{"Written by":"griddb-admin","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#article","isPartOf":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/"},"author":{"name":"griddb-admin","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233"},"headline":"Building a One-Time Token Login System with Spring Security","datePublished":"2025-05-14T07:00:00+00:00","mainEntityOfPage":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/"},"wordCount":935,"commentCount":0,"publisher":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization"},"image":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png","articleSection":["Blog"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/","url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/","name":"Building a One-Time Token Login System with Spring Security | GridDB: Open Source Time Series Database for IoT","isPartOf":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage"},"image":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage"},"thumbnailUrl":"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png","datePublished":"2025-05-14T07:00:00+00:00","description":"Imagine trying to keep track of all your passwords. It's a daunting task, isn't it? With passwords required for social media, online shopping, e-wallet","inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/blog\/building-a-one-time-token-login-system-with-spring-security\/#primaryimage","url":"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png","contentUrl":"\/wp-content\/uploads\/2025\/12\/cover-springsecurity-ott.png","width":1536,"height":1024},{"@type":"WebSite","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#website","url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/","name":"GridDB: Open Source Time Series Database for IoT","description":"GridDB is an open source time-series database with the performance of NoSQL and convenience of SQL","publisher":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#organization","name":"Fixstars","url":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/","url":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","contentUrl":"https:\/\/griddb.net\/wp-content\/uploads\/2019\/04\/fixstars_logo_web_tagline.png","width":200,"height":83,"caption":"Fixstars"},"image":{"@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/griddbcommunity\/","https:\/\/x.com\/GridDBCommunity","https:\/\/www.linkedin.com\/company\/griddb-by-toshiba"]},{"@type":"Person","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/4fe914ca9576878e82f5e8dd3ba52233","name":"griddb-admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/griddb-linux-hte8hndjf8cka8ht.westus-01.azurewebsites.net\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/5bceca1cafc06886a7ba873e2f0a28011a1176c4dea59709f735b63ae30d0342?s=96&d=mm&r=g","caption":"griddb-admin"},"url":"https:\/\/griddb.net\/en\/author\/griddb-admin\/"}]}},"_links":{"self":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/posts\/52133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/comments?post=52133"}],"version-history":[{"count":0,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/posts\/52133\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/media\/52134"}],"wp:attachment":[{"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/media?parent=52133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/categories?post=52133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/griddb.net\/en\/wp-json\/wp\/v2\/tags?post=52133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}