Implementing SMS One-Time Password Verification in Java with Redis Integration
Folllowing the establishment of the Redis connection pool and population of preliminary test records, the development cycle shifts toward automating credential management. An initialization routine automatically retrieves stored usernames and passwords to pre-fill authentication fields, eliminating repetitive manual input during debugging. The subsequent priority is constructing a secure SMS-based one-time password (OTP) verification pipeline. This system generates transient codes, dispatches them to registered mobile numbers, and temporarily persists them in Redis to enable consistent backend validation.
Integrating with an external messaging provider requires provisioning an application programming interface (API) key and submitting a message template for platform approval. Although commercial gateways typically allocate limited trial quotas, development environments often route payloads through local Redis storage to bypass rate limits and network latency. To encapsulate this workflow within the primary project entrance, a dedicated package structure manages all telephony verification operations independently of the core authentication layer.
The solution separates concerns into two distinct components: a cryptographically secure code factory and an optimized HTTP dispatch handler. The legacy Apache HttpClient dependency has been replaced with the native Java networking stack to eliminate transitive bloat and improve throughput. All payload encoding strictly adheres to UTF-8 standards to prevent parameter corruption during transmission. Connection pooling and timeout boundaries are explicitly configured to handle transient network failures gracefully.
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
/**
* Generates cryptographically safe numeric tokens for authentication flows.
*/
public class OtpTokenFactory {
private static final SecureRandom CRYPTO_RANDOM = new SecureRandom();
private static final int CODE_LENGTH = 6;
/**
* Produces a random numeric string suitable for time-limited authentication.
*
* @return Six-digit alphanumeric verification token
*/
public static String produceVerificationToken() {
StringBuilder tokenBuffer = new StringBuilder(CODE_LENGTH);
for (int index = 0; index < CODE_LENGTH; index++) {
tokenBuffer.append(CRYPTO_RANDOM.nextInt(10));
}
return tokenBuffer.toString();
}
}
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
/**
* Routes encoded authentication messages to external SMS gateways.
*/
public class MessageRoutingService {
private static final HttpClient TRANSPORT_CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
private static final String GATEWAY_ENDPOINT = "http://api.dingdongcloud.com/v1/sms/sendyzm";
private static final String API_CREDENTIAL = System.getenv("SMS_GATEWAY_KEY");
/**
* Dispatches a URL-encoded SMS payload to the specified recipient.
*
* @param recipientNumber Target mobile identifier
* @param encodedContent Pre-formatted message body
* @return Raw response from the gateway indicating delivery status
* @throws IOException If network unavailability or protocol mismatch occurs
*/
public static String deliverAuthenticationSms(String recipientNumber, String encodedContent) throws IOException {
String queryParams = "apikey=" + API_CREDENTIAL +
"&mobile=" + URLEncoder.encode(recipientNumber, StandardCharsets.UTF_8) +
"&content=" + encodedContent;
URI requestUri = URI.create(GATEWAY_ENDPOINT + "?" + queryParams);
HttpRequest dispatchRequest = HttpRequest.newBuilder()
.uri(requestUri)
.header("Accept", "application/json")
.GET()
.timeout(Duration.ofSeconds(15))
.build();
HttpResponse<String> transportResponse = TRANSPORT_CLIENT.send(dispatchRequest, HttpResponse.BodyHandlers.ofString());
if (transportResponse.statusCode() >= 200 && transportResponse.statusCode() < 300) {
return transportResponse.body();
}
throw new IOException("Gateway returned non-success status: " + transportResponse.statusCode());
}
}
Orchestrating the verification sequence begins by invoking the routing service with a validated contact identifier. The token factory produces a secure six-digit integer, which is embedded into a standardized notification string before being passed to the HTTP handler. Upon successful payload submission, the ganerated token is immediately serialized into Redis alongside the corresponding user identifier, accompanied by a short-lived expiration policy (typically ten minutes).
During the authentication handshake, the client transmits the received code back to the authorization endpoint. The backend queries the Redis cache for the matching key, performs a strict equality check, and nullifies the cached record to enforce single-use consumption. Once the token matches and expires naturally outside the allowed window, the system proceeds to issue a persistent session artifact, completing the dual-factor validation cycle without exposing sensitive credentials over unencrypted channels.