
Teilen Sie:
Senior Frontend-Ingenieur mit Sitz in Mumbai, Indien. Ich arbeite an der Fullstack-Entwicklung mit React und Spring Boot für Indiens meistgeschätztes Fintech. Auch der Gründer von learnersbucket.com, wo ich über JavaScript Interview Vorbereitung Führer schreiben.
OTP-basierte (JWT) Authentifizierung in Spring Boot mit Vonage Verify API
Lesedauer: 7 Minuten
Dieser Artikel wurde im März 2025 aktualisiert.
Übersicht
Eine nahtlose Benutzererfahrung (UX) ist ein wichtiger Faktor für das Produktwachstum. Die Authentifizierung ist ein wesentlicher Bestandteil einer guten UX, insbesondere bei Bankanwendungen oder FinTech.
Stellen Sie sich vor, Sie müssten eine Anmeldeseite für eine Bank erstellen, auf der sich der Kunde mit einer Telefonnummer und einem Einmalpasswort (OTP) authentifiziert. Der Wegfall der Notwendigkeit, sich ein Passwort zu merken, und die Sicherheit würden die Benutzerfreundlichkeit verbessern.
Wir wollen sehen, wie wir eine OTP-basierte JWT-Authentifizierung (JSON Web Token) in Java erstellen können. Wir verwenden das Spring Boot Framework und die Vonage Verify API.
Erfahren Sie mehr über JWT hier.
Vonage API-Konto
Bevor wir mit der Nutzung der API beginnen, benötigen wir ein Vonage API-Konto.

Sobald wir unseren API-Schlüssel und API-Geheimnishaben, werden wir sie für die Verify API.
Einrichtung
Wir erstellen eine neue Spring-Anwendung und importieren das Vonage Java Server SDK, damit wir die Vonage APIs in unserer Anwendung verwenden können. Wir fügen den folgenden Code entweder in unsere build.gradle oder der POM-Datei hinzu.
Gradle
Fügen Sie Folgendes zur Datei build.gradle Datei hinzu.
repositories {
mavenCentral()
}
dependencies {
implementation 'com.vonage:server-sdk:8.20.1'
} Maven
Fügen Sie der POM-Datei unseres Projekts Folgendes hinzu.
<dependency>
<groupid>com.vonage</groupid>
<artifactid>server-sdk</artifactid>
<version>8.20.1</version>
</dependency> Überblick über die Umsetzung
Jetzt, wo wir mit der Einrichtung fertig sind, können wir mit der Entwicklung beginnen. Diese OTP-Anmeldeauthentifizierung mit JWT kann in drei Schritten durchgeführt werden:
JWT-Erstellung
Benutzeranfragen filtern
Zugang zur API-Funktionalität
JWT-Erstellung
Wir werden einen Token-basierten Autorisierungsmechanismus verwenden, bei dem wir nach erfolgreicher Authentifizierung des Benutzers ein neues Token mit einer Verfallszeit generieren und an den Benutzer zurückgeben.
Der Benutzer muss dieses Token bei jeder Anfrage angeben, um seine Identität nachzuweisen und auf die Anwendungen zugreifen zu können, die eine Autorisierung erfordern.
Benutzeranfragen filtern
Jedes Mal, wenn wir ein Token in der Anfrage erhalten, müssen wir es verifizieren und Spring Security mitteilen, dass der Benutzer autorisiert ist und auf die eingeschränkten Endpunkte zugreifen kann. Wir benötigen einen Filter für jede Anfrage.
Zugang zur API-Funktionalität
Wir werden drei verschiedene API-Routen verwenden, um die Authentifizierung abzuschließen. Jede dient einem bestimmten Zweck:
Abrufen der Telefonnummer des Benutzers zum Senden des OTP
Verify des OTP und Rückgabe des JWT an den Benutzer
Allgemeiner Endpunkt für Tests
Sehen wir uns an, wie jeder dieser Punkte separat umgesetzt werden kann.
Handhabung der JWT-Verschlüsselung
JWT ist eine Kombination aus zwei verschiedenen Verschlüsselungsmethoden, die mit JWS und JWE erstellt wurden und mit einem symmetrischen Schlüssel verschlüsselt werden können SECRET_KEY mit einer Nutzlast.
Die Nutzdaten können sensible Daten enthalten, die wir zur Validierung des Benutzers verwenden können (z. B. Ablaufdatum, Benutzerdaten usw.)
Die Verwendung desselben SECRET_KEYkönnen wir das Token entschlüsseln, um die Nutzdaten zu erhalten und sie bei Bedarf zu verwenden.
Um JWT zu handhaben, verwenden wir das io.jsonwebtoken::jjwt Paket.
dependencies {
implementation 'io.jsonwebtoken:jjwt:0.12.6'
}Erstellen Sie eine JWTUtils Klasse unter dem util Paket und fügen Sie den folgenden Code hinzu.
package com.example.vonage.auth.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.cglib.core.internal.Function;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
public class JWTUtil {
private final String SECRET_KEY = "secret key";
public String extractIdentifier(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token){
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token){
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token){
return extractExpiration(token).before(new Date());
}
public String generateToken(String details) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, details);
}
private String createToken(Map<String, Object> claims, String subject) {
long time = System.currentTimeMillis();
long expiry = Duration.ofDays(10).toMillis();
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(time))
.setExpiration(new Date(time + expiry))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public Boolean validateToken(String token, String identifier) {
final String phoneNumber = extractIdentifier(token);
return phoneNumber.equals(identifier) && !isTokenExpired(token);
}
}Diese enthält die gesamte Logik in Bezug auf das JWT, die zur Erstellung eines neuen Tokens, zur Validierung des Tokens und zum Abrufen von Identifikatoren aus dem Token zur Validierung des Benutzers usw. verwendet werden kann.
Hier ersetzen wir den SECRET_KEY durch ein beliebiges gemeinsames Geheimnis und greifen darauf über die Eigenschaftsdateien der Anwendung zu, anstatt es fest zu kodieren.
Außerdem übergeben wir bei der Erzeugung des Tokens die Details (Telefonnummer) als String, aber Sie können jedes beliebige Objekt speichern und es extrahieren.
Wir haben die Validierung extrem einfach gehalten, wir speichern nur die Telefonnummer im Token, und dieselbe wird jedes Mal für die Autorisierung verwendet.
In der validateToken Methode werden die Telefonnummern abgeglichen und geprüft, ob das Token abgelaufen ist.
Das Verfallsdatum des Tokens beträgt zehn Tage Duration.ofDays(10).toMillis() ab dem Zeitpunkt der Erstellung des Tokens.
Filter hinzufügen, um einen Benutzer zu authentifizieren und den Kontext festzulegen
Jedes Mal, wenn wir eine Anfrage erhalten, die eine Autorisierung erfordert, müssen wir das Token validieren und den Kontext festlegen, dass der Benutzer authentifiziert ist.
Wir werden einen neuen Filter hinzufügen, der den OncePerRequestFilter- wie der Name schon sagt, erweitert; dieser Filter wird für jede Anfrage einmal ausgeführt.
Erstellen Sie eine neue Java-Klasse namens JWTFilter innerhalb des filters Paket und fügen Sie den folgenden Code hinzu.
Hier wird die Logik zum Extrahieren des Tokens aus der Anfrage und dessen Validierung gespeichert. Falls autorisiert, wird der Kontext festgelegt, dass der Benutzer authentifiziert ist.
package com.example.vonage.auth.filters;
import com.example.vonage.auth.utils.JWTUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JWTFilter extends OncePerRequestFilter {
@Autowired
JWTUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String jwt = null, identifier = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer")) {
jwt = authorizationHeader.substring(7);
identifier = jwtUtil.extractIdentifier(jwt);
}
if (identifier != null && jwtUtil.validateToken(jwt, identifier) && SecurityContextHolder.getContext().getAuthentication() == null) {
AuthenticationFilter apiToken = new AuthenticationFilter("abc", "xyz", AuthorityUtils.createAuthorityList());
SecurityContextHolder.getContext().setAuthentication(apiToken);
}
filterChain.doFilter(request, response);
}
}Von jeder Anfrage erhalten wir den Authorization Header und extrahieren daraus das Token nach dem Text BearerDeshalb erhalten wir die Teilzeichenkette nach den ersten sieben Zeichen (einschließlich eines Leerzeichens nach dem Wort Bearer).
Sobald wir das Token haben, extrahieren wir den Bezeichner für die Validierung aus.
Für die Validierung testen wir, ob der Benutzer bereits authentifiziert ist oder nicht, was nicht der Fall sein wird, da wir eine STATELESS Sitzung haben und ob der Bezeichner derselbe ist und das Token nicht abgelaufen ist. Die Sitzung ist zustandslos, weil wir keine Sitzung aufrechterhalten, da es sich um eine tokenbasierte Autorisierung handelt. Diese Einstellung wird in der Frühjahrssicherheit aktualisiert.
Denn wir haben nur die Rufnummer als Kennung verwendet haben, gibt es keine Gegenprüfung. Wir können die Sicherheit erhöhen, indem wir die E-Mail des Benutzers oder ein anderes eindeutiges Objekt speichern und dann die Telefonnummer aus der Datenbank abrufen und eine Überprüfung durchführen.
Um den Authentifizierungskontext zu erhalten, haben wir die AbstractAuthenticationToken und haben einen eindeutigen Schlüssel und ein Geheimnis angegeben.
AuthenticationFilter apiToken = new AuthenticationFilter("abc", "xyz", AuthorityUtils.createAuthorityList());
SecurityContextHolder.getContext().setAuthentication(apiToken);abc und xyz können durch ein eindeutiges Identifikationspaar ersetzt werden, z. B. phone number und email.
Erstellen Sie eine neue Java-Klasse namens AuthenticationFilter unter dem filters Paket, um den Authenticated-Kontext zu erhalten.
package com.example.vonage.auth.filters;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
import java.util.Collection;
@Transient
public class AuthenticationFilter extends AbstractAuthenticationToken {
private String apiKey, keySecret;
/**
* Creates a token with the supplied array of authorities.
*
* @param authorities the collection of <tt>GrantedAuthority</tt>s for the principal
* represented by this authentication object.
*/
public AuthenticationFilter(String apiKey, String keySecret, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.apiKey = apiKey;
this.keySecret = keySecret;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return keySecret;
}
@Override
public Object getPrincipal() {
return apiKey;
}
}Sobald die Autorisierung erfolgt ist, werden wir die Anfrage weiterleiten filterChain.doFilter(request, response);
Unser Code für den Filter ist fertig! Jetzt müssen wir ihn für jede Anfrage außer der Anmeldung verwenden.
Aktualisieren wir die Spring Security-Konfiguration, um das Gleiche zu tun.
Erstellen Sie eine neue Java-Klasse namens WebSecurityConfig unter dem config Paket und fügen Sie den folgenden Code hinzu.
package com.example.vonage.auth.config;
import com.example.vonage.auth.error.AuthError;
import com.example.vonage.auth.filters.JWTFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
AuthError authErrorHandler;
@Autowired
JWTFilter jwtFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests(configurer ->
configurer.antMatchers("/api/login/**").permitAll().anyRequest().authenticated()
).exceptionHandling().authenticationEntryPoint(authErrorHandler);
}
}Hier haben wir die cors und csrf konfiguriert, damit die API funktioniert, und unsere jwtFilter vor jedem internen Spring-Sicherheitsfilter.
UsernamePasswordAuthenticationFilter.class ist der erste Filter mit Priorität. Aber vorher läuft unser Filter und setzt den Kontext. Daher sind keine weiteren Prüfungen erforderlich und die Anfrage wird weiter verarbeitet.
Wir haben auch die Sitzung zustandslos gemacht .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) da wir Token für die Autorisierung verwenden.
Alle Authentifizierungsanfragen werden von der Sicherheitsprüfung umgangen "/api/login/**".
Am Ende haben wir eine allgemeine Fehlerklasse hinzugefügt, um den Fehler zu behandeln und eine benutzerdefinierte Antwort bereitzustellen .exceptionHandling().authenticationEntryPoint(authErrorHandler);.
Erstellen Sie eine Java-Klasse namens AuthError unter dem error Paket und fügen Sie den folgenden Code hinzu.
package com.example.vonage.auth.error;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
@Component
public class AuthError implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper();
String responseMsg = mapper.writeValueAsString("Unathorized User");
response.getWriter().write(responseMsg);
response.setContentType("application/json");
}
} Definition von Lotsen für die eingeschränkten und unbeschränkten Routen
Unser Filter und die Token-Validierung sind nun vorhanden. Hören wir uns die Anfrage an und authentifizieren den Benutzer.
Dienstleistungen
Erstellen Sie eine Java-Klasse namens 'AuthService' unter dem services Paket.
package com.example.vonage.auth.services;
import com.example.vonage.auth.utils.JWTUtil;
import com.vonage.client.VonageClient;
import com.vonage.client.verify.CheckResponse;
import com.vonage.client.verify.VerifyResponse;
import com.vonage.client.verify.VerifyStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AuthService {
@Autowired
JWTUtil jwtUtil;
final VonageClient client = VonageClient.builder()
.apiKey("7ed*****")
.apiSecret("W**u*E*VlaWe****")
.build();
public String init(String identifier) {
VerifyResponse response = client.getVerifyClient().verify("91900*******", "Vonage");
if (response.getStatus() == VerifyStatus.OK) {
return response.getRequestId();
}
else {
return "ERROR! " + response.getStatus() + " " + response.getErrorText();
}
}
public String verify(String identifier, String request_id, String otp) {
CheckResponse response = client.getVerifyClient().check(request_id, otp);
if (response.getStatus() == VerifyStatus.OK) {
return jwtUtil.generateToken(identifier);
}
else {
return "Verification failed: " + response.getErrorText();
}
}
}AuthService wird zwei Methoden haben: init und verify.
init akzeptiert die Rufnummer als Parameter und sendet das OTP an diese Rufnummer. Die Antwort gibt ein request_id zurück, das zusammen mit dem OTP für die Validierung verwendet wird.
verify akzeptiert die Telefonnummer, die request_id und das OTP als Parameter und verifiziert die request_id und das OTP. Nach der Verifizierung wird die Telefonnummer als Identifikator verwendet, ein neues Token erzeugt und die Antwort zurückgegeben.
Steuerungen
Auf eine Anwendung kann über Endpunkte zugegriffen werden, die dem Benutzer zur Verfügung gestellt werden. Die Endpunkte können öffentlich, privat oder geschützt sein. In unserem Fall ist die Anmelde-API öffentlich zugänglich, während alle anderen APIs geschützt sind und für den Zugriff eine Benutzerauthentifizierung erfordern.
Anmeldung (Login)
Für den Zugang zu diesen Routen ist keine Genehmigung erforderlich.
Erstellen Sie eine Java-Klasse namens 'Auth' unter dem controllers Paket und fügen Sie den folgenden Code hinzu.
package com.example.vonage.auth.controllers;
import com.example.vonage.auth.services.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.constraints.NotBlank;
@Validated
@RestController
@RequestMapping("api/login")
public class Auth {
@Autowired
AuthService authService;
@PostMapping(path="/init", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public String init(@NotBlank String identifier){
return authService.sendOtp(identifier);
}
@PostMapping(path="/verify", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
public String verify(@NotBlank String identifier, @NotBlank String request_id, @NotBlank String otp) {
return authService.verify(identifier, request_id, otp);
}
} Sonstiges (eingeschränkt)
Für den Zugriff auf diese Route ist eine Autorisierung erforderlich.
Erstellen Sie eine Java-Klasse namens 'Hello' unter dem controllers Paket und fügen Sie den folgenden Code hinzu.
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Validated
@RestController
@RequestMapping("api/")
public class Hello {
@GetMapping(path="/hello")
public String hello(){
return "Hello world";
}
} Prüfung
Hier haben wir drei API-Endpunkte.
/api/hello ohne Autorisierung
Wenn kein Authorization Header in der Anfrage vorhanden ist oder ein ungültiges Token angegeben wird, wird ein Fehler ausgegeben, unauthorized user.

/api/login/init
Sie akzeptiert die Telefonnummer im URL-codierten Format und gibt nach dem Senden des OTPs die Request-ID zurück.

/api/login/verify
Sie akzeptiert die Telefonnummer, die Anfrage-ID und das OTP im URL-codierten Format und gibt das JWT-Token nach dem Senden des OTP zurück.

/api/hello mit Autorisierung
In der Kopfzeile der Anfrage übergeben Sie
Authorization : Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI5MDA0NzU4NTM0IiwiZXhwIjoxNjYzOTYxMzIwLCJpYXQiOjE2NjM5MjUzMjB9.fY2536Zuz2KNzinZvWU5CNZ6K1p_wOu0pkEbwqP8gbg
Schlussfolgerung
Nachdem wir nun einen zweistufigen Anmeldefluss mit Telefonnummer und OTP erstellt haben, könnte unser nächster Schritt darin bestehen, eine Zwei-Faktor-Authentifizierung hinzuzufügen, bei der wir denselben Benutzer erneut verifizieren können, indem wir einen Anruf an die Telefonnummer tätigen, indem wir die Vonage Voice API.
Vonage hat eine interessante Reihe von APIs zur Verfügung, die Sie nutzen können, um Ihr Produkt auf die nächste Stufe zu heben und eine bessere Benutzererfahrung zu bieten.
Das Engagement der Community ist immer willkommen. Werden Sie Mitglied bei Vonage auf GitHub für Code-Beispiele und dem Gemeinschaft Slack für Rückfragen jederzeit zur Verfügung. Senden Sie uns einen Tweet und lassen Sie uns wissen, welches interessante Problem Sie mit der Vonage API gelöst haben.
Sie können mich auch erreichen auf Twitter oder über meinen Blog learnersbucket.com.
Teilen Sie:
Senior Frontend-Ingenieur mit Sitz in Mumbai, Indien. Ich arbeite an der Fullstack-Entwicklung mit React und Spring Boot für Indiens meistgeschätztes Fintech. Auch der Gründer von learnersbucket.com, wo ich über JavaScript Interview Vorbereitung Führer schreiben.