Java >> Java Tutorial >  >> Tag >> Spring

Hinzufügen einer sozialen Anmeldung zu einer Spring MVC-Webanwendung:Registrierung und Anmeldung

Im ersten Teil dieses Tutorials wurde beschrieben, wie wir Spring Social 1.1.0 und Spring Security 3.2.0 konfigurieren können, aber zwei sehr wichtige Fragen blieben unbeantwortet.

Diese Fragen lauten:

  • Wie kann ein Benutzer ein neues Benutzerkonto erstellen?
  • Wie kann sich ein Benutzer anmelden?

Es ist an der Zeit, sich die Hände schmutzig zu machen und diese Fragen zu beantworten. Die Anforderungen unserer Beispielanwendung sind:

  • Es muss möglich sein, ein "traditionelles" Benutzerkonto zu erstellen. Das bedeutet, dass der Benutzer mit Benutzername und Passwort authentifiziert wird.
  • Es muss möglich sein, ein Benutzerkonto über einen SaaS-API-Anbieter wie Facebook oder Twitter zu erstellen. In diesem Fall wird der Benutzer vom SaaS-API-Anbieter authentifiziert.
  • Die Anmeldung mit Benutzername und Passwort muss möglich sein.
  • Die Anmeldung muss über einen SaaS-API-Anbieter möglich sein.

Beginnen wir damit, diese Anforderungen zu erfüllen. Als erstes müssen wir eine Anmeldeseite für unsere Anwendung erstellen.

Anmeldeseite erstellen

Die Anmeldeseite unserer Anwendung hat drei Verantwortlichkeiten, die im Folgenden beschrieben werden:

  1. Es muss eine Möglichkeit bieten, sich mit Benutzername und Passwort anzumelden.
  2. Es muss einen Link zur Registrierungsseite geben. Wenn ein Benutzer ein "traditionelles" Benutzerkonto erstellen möchte, kann er dies tun, indem er auf diesen Link klickt.
  3. Es muss die Links enthalten, die den Social-Sign-In-Fluss starten. Diese Links können für zwei Zwecke verwendet werden:
    • Wenn der betreffende Benutzer ein Benutzerkonto hat, kann er sich über einen SaaS-API-Anbieter anmelden.
    • Wenn der Benutzer kein Benutzerkonto hat, kann er eines erstellen, indem er einen SaaS-API-Anbieter verwendet.

Die Anwendungskontextkonfiguration, die wir im ersten Teil dieses Tutorials erstellt haben, legt einige Anforderungen für unsere Anmeldeseite fest. Diese Anforderungen sind:

  1. Wenn ein anonymer Benutzer versucht, auf eine geschützte Seite zuzugreifen, wird er auf die URL '/login' umgeleitet.
  2. Wenn das Anmeldeformular unserer Anwendung gesendet wird, muss unsere Anwendung eine POST-Anforderung an die URL „/login/authenticate“ erstellen.
  3. Wir müssen ein CSRF-Token in die POST-Anforderung einfügen, die erstellt wird, wenn unser Anmeldeformular gesendet wird. Der Grund dafür ist, dass der CSRF-Schutz von Spring Security 3.2.0 standardmäßig aktiviert ist, wenn wir Spring Security mithilfe der Java-Konfiguration konfigurieren.
  4. Der Name des username-Parameters ist username . Dies ist der Standardwert des username-Parameters, wenn Spring Security mithilfe der Java-Konfiguration konfiguriert wird
  5. Der Name des Passwortparameters ist password . Dies ist der Standardwert des Passwortparameters, wenn Spring Security mithilfe der Java-Konfiguration konfiguriert wird.
  6. Wenn eine Formularanmeldung fehlschlägt, wird der Benutzer zur URL „/login?error=bad_credentials“ umgeleitet. Das heißt, wenn die Login-Seite angefordert wird und der Wert von error Anfrageparameter 'bad_credentials' ist, müssen wir dem Benutzer eine Fehlermeldung anzeigen.
  7. Der SocialAuthenticationFilter verarbeitet GET-Anforderungen, die an die URL „/auth/{provider}“ gesendet werden. Das bedeutet, dass
    • Wir können den Facebook-Anmeldeablauf starten, indem wir eine GET-Anforderung an die URL „/auth/facebook“ senden.
    • Wir können den Twitter-Anmeldeablauf starten, indem wir eine GET-Anfrage an die URL „/auth/twitter“ senden.

Beginnen wir mit der Erstellung eines Controllers, der die Anmeldeseite darstellt.

Controller erstellen

Wir können den Controller implementieren, der die Anmeldeseite rendert, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen LoginController class und kommentieren Sie die erstellte Klasse mit dem @Controller Anmerkung.
  2. Fügen Sie eine showLoginPage() hinzu -Methode an die Controller-Klasse. Diese Methode gibt den Namen der gerenderten Ansicht zurück.
  3. Implementieren Sie die showLoginPage() Methode, indem Sie die folgenden Schritte ausführen:
    1. Annotieren Sie die Methode mit @RequestMapping Anmerkung und stellen Sie sicher, dass die showLoginPage() -Methode verarbeitet GET-Anforderungen, die an die URL „/login“ gesendet werden.
    2. Gib den Namen der Login-Ansicht zurück ('user/login').

Der Quellcode des LoginController Klasse sieht wie folgt aus:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class LoginController {

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String showLoginPage() {
        return "user/login";
    }
}

Unser nächster Schritt besteht darin, die Anmeldeseite mithilfe von JSP zu erstellen. Mal sehen, wie das gemacht wird.

Erstellen der JSP-Seite

Wir können die Anmeldeseite erstellen, indem wir diesen Schritten folgen:

  1. Stellen Sie sicher, dass das Anmeldeformular und die Schaltflächen für die soziale Anmeldung nur anonymen Benutzern angezeigt werden. Wir können dies tun, indem wir diesen Schritten folgen:
    1. Verpacken Sie das Anmeldeformular und die Schaltflächen für die soziale Anmeldung in autorisieren -Tag der Spring Security-Tag-Bibliothek.
    2. Stellen Sie den Wert des Zugriffs ein Attribut zu isAnonymous() .
  2. Eine Fehlermeldung anzeigen, wenn die Anmeldung fehlschlägt. Wir können die lokalisierte Fehlermeldung erhalten, indem wir die Nachricht verwenden -Tag der Spring-Tag-Bibliothek, wenn der Wert des Anfrageparameters namens error ist 'bad_credentials'.
  3. Implementieren Sie das Anmeldeformular, indem Sie diesen Schritten folgen:
    1. Stellen Sie sicher, dass beim Absenden des Anmeldeformulars eine POST-Anforderung an die URL „/login/authenticate“ gesendet wird.
    2. CSRF-Token zur Anfrage hinzufügen, die gesendet wird, wenn das Anmeldeformular gesendet wird. Dies ist erforderlich, da wir im ersten Teil dieses Tutorials den CSRF-Schutz von Spring Security aktiviert haben.
    3. Fügen Sie einen Benutzernamen hinzu Feld zum Login-Formular.
    4. Fügen Sie ein Passwort hinzu Feld zum Login-Formular.
    5. Fügen Sie eine Senden-Schaltfläche zum Anmeldeformular hinzu.
  4. Fügen Sie den Link „Benutzerkonto erstellen“ unter dem Anmeldeformular hinzu. Dieser Link erstellt eine GET-Anforderung an die URL „/user/register“ (Registrierungsseite).
  5. Fügen Sie Social-Sign-Schaltflächen zur Anmeldeseite hinzu, indem Sie die folgenden Schritte ausführen:
    1. Facebook-Anmeldeschaltfläche hinzufügen. Diese Schaltfläche muss eine GET-Anfrage an die URL „/auth/facebook“ erstellen.
    2. Twitter-Anmeldeschaltfläche hinzufügen. Diese Schaltfläche muss eine GET-Anforderung an die URL „/auth/twitter“ erstellen.
  6. Stellen Sie sicher, dass eine Hilfemeldung angezeigt wird, wenn ein authentifizierter Benutzer auf die Anmeldeseite zugreift. Wir können dies tun, indem wir diesen Schritten folgen:
    1. Bringen Sie den Fehlermeldungsbereich in die Autorisierung ein -Tag der Spring Security-Tag-Bibliothek.
    2. Stellen Sie den Wert des Zugriffs ein Attribut zu isAuthenticated() .
    3. Erhalten Sie die lokalisierte Fehlermeldung, indem Sie die Nachricht verwenden -Tag der Spring-Tag-Bibliothek.

Der Quellcode von login.jsp Seite sieht wie folgt aus:

<!DOCTYPE html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<html>
<head>
    <title></title>
    <link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/static/css/social-buttons-3.css"/>
</head>
<body>
<div class="page-header">
    <h1><spring:message code="label.user.login.page.title"/></h1>
</div>
<!-- 
	If the user is anonymous (not logged in), show the login form
	and social sign in buttons.
-->
<sec:authorize access="isAnonymous()">
	<!-- Login form -->
    <div class="panel panel-default">
        <div class="panel-body">
            <h2><spring:message code="label.login.form.title"/></h2>
			<!--
				Error message is shown if login fails.
			-->
            <c:if test="${param.error eq 'bad_credentials'}">
                <div class="alert alert-danger alert-dismissable">
                    <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
                    <spring:message code="text.login.page.login.failed.error"/>
                </div>
            </c:if>
			<!-- Specifies action and HTTP method -->
            <form action="${pageContext.request.contextPath}/login/authenticate" method="POST" role="form">
				<!-- Add CSRF token -->
                <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
                <div class="row">
                    <div id="form-group-email" class="form-group col-lg-4">
                        <label class="control-label" for="user-email"><spring:message code="label.user.email"/>:</label>
                        <!-- Add username field to the login form -->
						<input id="user-email" name="username" type="text" class="form-control"/>
                    </div>
                </div>

                <div class="row">
                    <div id="form-group-password" class="form-group col-lg-4">
                        <label class="control-label" for="user-password"><spring:message code="label.user.password"/>:</label>
                        <!-- Add password field to the login form -->
						<input id="user-password" name="password" type="password" class="form-control"/>
                    </div>
                </div>
				<div class="row">
					<div class="form-group col-lg-4">
						<!-- Add submit button -->
						<button type="submit" class="btn btn-default"><spring:message code="label.user.login.submit.button"/></button>
					</div>
				</div>
            </form>
			<div class="row">
				<div class="form-group col-lg-4">
					<!-- Add create user account link -->
					<a href="${pageContext.request.contextPath}/user/register"><spring:message code="label.navigation.registration.link"/></a>
				</div>
			</div>
        </div>
    </div>
	<!-- Social Sign In Buttons -->
    <div class="panel panel-default">
        <div class="panel-body">
            <h2><spring:message code="label.social.sign.in.title"/></h2>
            <div class="row social-button-row">
                <div class="col-lg-4">
					<!-- Add Facebook sign in button -->
                    <a href="${pageContext.request.contextPath}/auth/facebook"><button class="btn btn-facebook"><i class="icon-facebook"></i> | <spring:message code="label.facebook.sign.in.button"/></button></a>
                </div>
            </div>
            <div class="row social-button-row">
                <div class="col-lg-4">
					<!-- Add Twitter sign in Button -->
                    <a href="${pageContext.request.contextPath}/auth/twitter"><button class="btn btn-twitter"><i class="icon-twitter"></i> | <spring:message code="label.twitter.sign.in.button"/></button></a>
                </div>
            </div>
        </div>
    </div>
</sec:authorize>
<!-- 
	If the user is already authenticated, show a help message instead
	of the login form and social sign in buttons.
-->
<sec:authorize access="isAuthenticated()">
    <p><spring:message code="text.login.page.authenticated.user.help"/></p>
</sec:authorize>
</body>
</html>

Wir haben jetzt die Anmeldeseite erstellt, die unseren Anforderungen entspricht. Der relevante Teil unserer Anmeldeseite sieht wie folgt aus:

Unser nächster Schritt ist die Implementierung der Registrierungsfunktion. Fangen wir an.

Implementierung der Registrierungsfunktion

Die Registrierungsfunktion unserer Beispielanwendung hat zwei Anforderungen:

  1. Es muss möglich sein, ein "normales" Benutzerkonto anzulegen.
  2. Es muss möglich sein, ein Benutzerkonto über Social Sign-in zu erstellen.

Außerdem spezifiziert die Anwendungskontextkonfiguration, die wir im ersten Teil dieses Tutorials erstellt haben, eine Anforderung für die Registrierungsfunktion:

Die URL der Registrierungsseite muss „/signup“ lauten. Dies ist der Standardwert der Anmeldeseite (auch als Registrierung bezeichnet), und im Moment ist es nicht möglich, diese URL zu überschreiben, wenn wir den Anwendungskontext mithilfe der Java-Konfiguration konfigurieren. Da die URL „/signup“ jedoch etwas hässlich aussieht, werden wir diese URL durch die URL „/user/register“ ersetzen.

Hinweis :Es ist möglich, den Standardwert der Anmelde-URL zu überschreiben, wenn der Anwendungskontext mithilfe von XML-Konfigurationsdateien konfiguriert wird (suchen Sie nach der Eigenschaft namens signUpUrl ).

Der Benutzer unserer Beispielanwendung kann mit einer der folgenden Methoden zur Registrierungsseite gelangen:

  1. Er klickt auf den Link „Benutzerkonto erstellen“. Dieser Link startet den "normalen" Registrierungsprozess.
  2. Er klickt auf die Schaltfläche „Anmeldung in sozialen Netzwerken“, wodurch der Ablauf der Anmeldung in sozialen Netzwerken gestartet wird.

Da es schwierig ist, sich aus einer so flachen Beschreibung einen Überblick zu verschaffen, habe ich ein Diagramm erstellt, das die Schritte veranschaulicht, die ein Benutzer befolgen muss, bevor er auf der Registrierungsseite unserer Beispielanwendung landet. Dieses Diagramm hat zwei Regeln:

  1. Die graue Farbe stellt Aktionen dar, die in der Verantwortung unserer Beispielanwendung liegen.
  2. Die blaue Farbe stellt Aktionen dar, die in der Verantwortung des SaaS-API-Anbieters liegen.

Dieses Diagramm sieht wie folgt aus:

Fahren wir fort und erstellen zunächst ein Formularobjekt für das Registrierungsformular.

Formularobjekt erstellen

Das Formularobjekt ist ein Datenübertragungsobjekt, das die in das Registrierungsformular eingegebenen Informationen enthält und die Validierungsbeschränkungen spezifiziert, die verwendet werden, um diese Informationen zu validieren.

Bevor wir das Formularobjekt implementieren, werfen wir einen kurzen Blick auf die Validierungsbeschränkungen, die wir verwenden, um unser Formularobjekt zu validieren. Diese Einschränkungen werden im Folgenden beschrieben:

  • Die @E-Mail Annotation stellt sicher, dass die vom Benutzer angegebene E-Mail-Adresse wohlgeformt ist.
  • Das @NotEmpty Annotation stellt sicher, dass der Wert des Felds nicht leer oder null sein kann.
  • Die @Size Annotation stellt sicher, dass die Länge des Feldwerts nicht länger als die maximale Länge des Felds ist.

Lassen Sie uns weitermachen und das Formularobjekt erstellen. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Erstellen Sie eine Klasse namens RegistrationForm .
  2. Fügen Sie eine E-Mail-Adresse hinzu -Feld in die Klasse ein und geben Sie seine Validierungseinschränkungen an, indem Sie diese Regeln befolgen:
    1. Die E-Mail muss wohlgeformt sein.
    2. Die E-Mail-Adresse darf nicht leer oder null sein.
    3. Die maximale Länge der E-Mail beträgt 100 Zeichen.
  3. Fügen Sie einen Vornamen hinzu -Feld in die Klasse ein und geben Sie seine Validierungseinschränkungen an, indem Sie diese Regeln befolgen:
    1. Der Vorname darf nicht leer oder null sein.
    2. Die maximale Länge des Vornamens beträgt 100 Zeichen.
  4. Fügen Sie einen Nachnamen hinzu -Feld in die Klasse ein und geben Sie seine Validierungseinschränkungen an, indem Sie diese Regeln befolgen:
    1. Der Nachname darf nicht leer oder null sein.
    2. Die maximale Länge des Nachnamens beträgt 100 Zeichen.
  5. Fügen Sie ein Passwort hinzu Feld an die Klasse.
  6. Fügen Sie eine Passwortverifizierung hinzu Feld an die Klasse.
  7. Fügen Sie einen SignInProvider hinzu Feld zur Klasse. Der Typ dieses Felds ist SocialMediaService .
  8. Fügen Sie eine isNormalRegistration() hinzu Methode zur erstellten Klasse. Diese Methode gibt true zurück, wenn der Wert von signInProvider Feld ist null. Wenn der Wert dieses Felds nicht null ist, gibt diese Methode false zurück.
  9. Fügen Sie ein isSocialSignIn() hinzu -Methode auf die erstellte Klasse. Diese Methode gibt true zurück, wenn der Wert von signInProvider Feld ist nicht null. Wenn der Wert dieses Felds null ist, gibt diese Methode false zurück.

Der Quellcode des RegistrationForm Klasse sieht wie folgt aus:

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.Size;

@PasswordsNotEmpty(
        triggerFieldName = "signInProvider",
        passwordFieldName = "password",
        passwordVerificationFieldName = "passwordVerification"
)
@PasswordsNotEqual(
        passwordFieldName = "password",
        passwordVerificationFieldName = "passwordVerification"
)
public class RegistrationForm {

    @Email
    @NotEmpty
    @Size(max = 100)
    private String email;

    @NotEmpty
    @Size(max = 100)
    private String firstName;

    @NotEmpty
    @Size(max = 100)
    private String lastName;

    private String password;

    private String passwordVerification;

    private SocialMediaService signInProvider;

	//Constructor is omitted for the of clarity.
	
	public boolean isNormalRegistration() {
		return signInProvider == null;
	}

	public boolean isSocialSignIn() {
		return signInProvider != null;
	}
	
	//other methods are omitted for the sake of clarity.
}

Der SocialMediaService ist eine Aufzählung, die den SaaS-API-Anbieter identifiziert, der zur Authentifizierung des Benutzers verwendet wurde. Sein Quellcode sieht wie folgt aus:

public enum SocialMediaService {
    FACEBOOK,
    TWITTER
}

Warte, haben wir nicht gerade etwas vergessen?

Was in aller Welt sind diese seltsamen Anmerkungen wie @PasswordsNotEqual und @PasswordsNotEmpty ?

Nun, es handelt sich um benutzerdefinierte Bean-Validierungseinschränkungen. Lassen Sie uns herausfinden, wie wir diese Einschränkungen erstellen können.

Erstellen der benutzerdefinierten Validierungseinschränkungen

Wir müssen zwei benutzerdefinierte Validierungseinschränkungen für unsere Beispielanwendung erstellen. Wenn der Benutzer ein "normales" Benutzerkonto erstellt, müssen wir Folgendes sicherstellen:

  1. Das Passwort und PasswortVerifizierung Felder unseres Formularobjekts dürfen nicht leer oder null sein.
  2. Das Passwort und PasswortVerifizierung Felder sind gleich.

Wir können benutzerdefinierte Validierungseinschränkungen erstellen, indem wir diesen Schritten folgen:

  1. Einschränkungsanmerkung erstellen.
  2. Implementieren Sie eine benutzerdefinierte Validierungsklasse, die sicherstellt, dass die Einschränkung nicht verletzt wird.

Beginnen wir mit der Erstellung der Constraint-Anmerkungen.

Erstellen der Constraint-Anmerkungen

Wenn wir die Constraint-Anmerkungen erstellen, müssen wir immer die folgenden allgemeinen Schritte befolgen:

  1. Erstellen Sie einen Anmerkungstyp. Nehmen wir an, dass der Name unseres Anmerkungstyps CommonConstraint ist .
  2. Kommentieren Sie den erstellten Anmerkungstyp mit @Target Anmerkung und setzen Sie ihren Wert auf {ElementType.TYPE, ElementType.ANNOTATION_TYPE} (das Javadoc des ElementType Aufzählung). Das bedeutet, dass sowohl Klassen als auch Annotationstypen mit @CommonConstraint annotiert werden können Anmerkung.
  3. Kommentieren Sie den erstellten Anmerkungstyp mit @Retention -Anmerkung und setzen Sie ihren Wert auf RetentionPolicy.RUNTIME . Das bedeutet, dass die @CommonConstraint Annotation ist zur Laufzeit verfügbar und kann durch Reflektion gelesen werden.
  4. Kommentieren Sie den erstellten Anmerkungstyp mit @Constraint -Anmerkung und legen Sie den Wert von validatedBy fest Attribut. Der Wert dieses Attributs gibt die Klasse an, die die mit @CommonConstraint annotierten Klassen validiert Anmerkung.
  5. Kommentieren Sie die Klasse mit @Documented Anmerkung. Das bedeutet, dass die @CommonConstraint Annotation ist in der Javadoc-Dokumentation aller Klassen sichtbar, die damit annotiert sind.
  6. Fügen Sie eine Nachricht hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist String , und sein Standardwert ist 'CommonConstraint'.
  7. Fügen Sie Gruppen hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist ein Array vom Typ Class , und sein Standardwert ist ein leeres Array. Dieses Attribut ermöglicht die Erstellung von Validierungsgruppen.
  8. Fügen Sie eine Nutzlast hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist ein Array vom Typ Class , und sein Standardwert ist ein leeres Array. Dieses Attribut wird nicht von der Bean-Validierungs-API verwendet, aber Clients der API können benutzerdefinierte PayLoad zuweisen widerspricht der Beschränkung.

Der Quellcode von @CommonConstraint Anmerkung sieht wie folgt aus:

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target( { TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CommonConstraintValidator.class)
@Documented
public @interface CommonConstraint {

    String message() default "CommonConstraint";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Lassen Sie uns weitermachen und herausfinden, wie wir @PasswordsNotEmpty erstellen können und @PasswordNotEqual Anmerkungen.

Zuerst müssen wir das @PasswordsNotEmpty erstellen Anmerkung. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Folgen Sie den zuvor beschriebenen allgemeinen Schritten und nehmen Sie die folgenden Änderungen an der erstellten Anmerkung vor:
    1. Benennen Sie den Anmerkungstyp in PasswordsNotEmpty um .
    2. Legen Sie den Wert von @Constraint fest validatedBy der Anmerkung -Attribut zu PasswordsNotEmptyValidator.class .
  2. Fügen Sie einen triggerFieldName hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist String , und sein Standardwert ist eine leere Zeichenfolge. Dieses Attribut gibt den Namen des Felds an, das unsere benutzerdefinierte Einschränkung auslöst, wenn sein Wert null ist.
  3. Fügen Sie einen passwordFieldName hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist String , und sein Standardwert ist eine leere Zeichenfolge. Dieses Attribut gibt den Namen des Feldes an, das das Passwort des Benutzers enthält.
  4. Fügen Sie einen passwordVerificationFieldName hinzu Attribut zum Anmerkungstyp. Der Typ dieses Attributs ist String , und sein Standardwert ist eine leere Zeichenfolge. Dieses Attribut gibt den Namen des Feldes an, das die Passwortverifizierung des Benutzers enthält.

Der Quellcode von @PasswordsNotEmpty Anmerkung sieht wie folgt aus:

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target( { TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordsNotEmptyValidator.class)
@Documented
public @interface PasswordsNotEmpty {

    String message() default "PasswordsNotEmpty";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String triggerFieldName() default "";

    String passwordFieldName() default "";

    String passwordVerificationFieldName() default "";
}

Zweitens müssen wir @PasswordsNotEqual erstellen Anmerkung. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Folgen Sie den zuvor beschriebenen allgemeinen Schritten und nehmen Sie die folgenden Änderungen an der erstellten Anmerkung vor:
    1. Benennen Sie den Anmerkungstyp in PasswordsNotEqual um .
    2. Legen Sie den Wert von @Constraint fest validatedBy der Anmerkung Attribut zu PasswordsNotEqualValidator.class .
  2. Fügen Sie einen passwordFieldName hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist String , und sein Standardwert ist eine leere Zeichenfolge. Dieses Attribut gibt den Namen des Feldes an, das das Passwort des Benutzers enthält.
  3. Fügen Sie einen passwordVerificationFieldName hinzu Attribut zum Annotationstyp. Der Typ dieses Attributs ist String , und sein Standardwert ist eine leere Zeichenfolge. Dieses Attribut gibt den Namen des Feldes an, das die Passwortverifizierung des Benutzers enthält.

Der Quellcode von @PasswordsNotEqual Anmerkung sieht wie folgt aus:

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target( { TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordsNotEqualValidator.class)
@Documented
public @interface PasswordsNotEqual {

    String message() default "PasswordsNotEqual";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    String passwordFieldName() default "";

    String passwordVerificationFieldName() default "";
}

Wir haben jetzt unsere Constraint-Anmerkungen erstellt. Lassen Sie uns weitermachen und einen Blick auf eine Utility-Klasse werfen, die wir verwenden, wenn wir die Validator-Klassen für unsere benutzerdefinierten Constraint-Anmerkungen implementieren.

Erstellen der Validierungsdienstprogrammklasse

Die Validation-Utility-Klasse stellt zwei statische Methoden bereit, die im Folgenden beschrieben werden:

  • Die erste Methode wird verwendet, um Validierungsfehler zu einem Feld des validierten Objekts hinzuzufügen.
  • Die zweite Methode gibt den Wert des angeforderten Felds zurück.

Wir können diese Klasse implementieren, indem wir diesen Schritten folgen:

  1. Erstellen Sie eine Klasse namens ValidatorUtil .
  2. Fügen Sie einen addValidationError() hinzu -Methode zum ValidatorUtil Klasse. Diese Methode benötigt zwei Parameter, die im Folgenden beschrieben werden:
    1. Der erste Parameter ist der Name des Feldes.
    2. Der zweite Parameter ist ein ConstraintValidatorContext-Objekt.
  3. Implementieren Sie addValidationError() Methode, indem Sie die folgenden Schritte ausführen:
    1. Erstellen Sie eine neue Einschränkungsverletzung und stellen Sie sicher, dass die durch die Einschränkungsanmerkung angegebene Nachricht als Präfix verwendet wird, wenn die Einschränkungsverletzungsnachricht erstellt wird.
    2. Fügen Sie das Feld zum Einschränkungsvalidierungsfehler hinzu.
    3. Erstellen Sie den Constraint-Validierungsfehler.
  4. Fügen Sie ein getFieldValue() hinzu -Methode zum ValidatorUtil Klasse. Diese Methode gibt den Feldwert des angegebenen Felds zurück und übernimmt zwei Parameter, die im Folgenden beschrieben werden:
    1. Der erste Parameter ist das Objekt, das das angeforderte Feld enthält.
    2. Der zweite Parameter ist der Name des angeforderten Felds.
  5. Implementieren Sie getFieldValue() Methode, indem Sie die folgenden Schritte ausführen:
    1. Erhalten Sie eine Referenz auf das Feld Objekt, das das angeforderte Feld widerspiegelt.
    2. Stellen Sie sicher, dass wir auf den Wert des Felds zugreifen können, auch wenn das Feld privat ist.
    3. Gibt den Feldwert zurück.

Der Quellcode von ValidatorUtil Klasse sieht wie folgt aus:

import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;

public class ValidatorUtil {

    public static void addValidationError(String field, ConstraintValidatorContext context) {
        context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate())
                .addNode(field)
                .addConstraintViolation();
    }

    public static Object getFieldValue(Object object, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Field f = object.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        return f.get(object);
    }
}

Wir sind jetzt bereit, unsere Validator-Klassen zu implementieren. Mal sehen, wie das gemacht wird.

Erstellen der Validator-Klassen

Zuerst müssen wir die Validator-Klasse erstellen, die Klassen validieren kann, die mit @PasswordsNotEmpty annotiert sind Anmerkung. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen PasswordsNotEmptyValidator Klasse und implementieren den ConstraintValidator Schnittstelle. Der ConstraintValidator interface definiert zwei Typparameter, die im Folgenden beschrieben werden:
    1. Der erste Typparameter ist der Anmerkungstyp. Setzen Sie den Wert dieses Typparameters auf PasswordsNotEmpty .
    2. Der zweite Typparameter ist der Elementtyp, der vom Validator validiert werden kann. Setzen Sie den Wert dieses Typparameters auf Object (Wir könnten dies auf RegistrationForm setzen aber mit dem Typ Objekt stellt sicher, dass unser Validator nicht auf diese Beispielanwendung beschränkt ist).
  2. Fügen Sie einen privaten validationTriggerFieldName hinzu -Feld auf die erstellte Klasse und setzen Sie ihren Typ auf String .
  3. Fügen Sie einen privaten passwordFieldName hinzu -Feld auf die erstellte Klasse und setzen Sie ihren Typ auf String .
  4. Fügen Sie einen privaten passwordVerificationFieldName hinzu -Feld auf die erstellte Klasse und setzen Sie ihren Typ auf String .
  5. Fügen Sie initialize(PasswordsNotEmpty constraintAnnotation) hinzu Methode des ConstraintValidator Schnittstelle zur Validator-Klasse und implementieren Sie sie, indem Sie die folgenden Schritte ausführen:
    1. Legen Sie den Wert von validationTriggerFieldName fest Feld.
    2. Legen Sie den Wert von passwordFieldName fest Feld.
    3. Legen Sie den Wert von passwordVerificationFieldName fest Feld.
  6. Fügen Sie ein privates isNullOrEmpty(String field) hinzu -Methode auf die erstellte Klasse. Diese Methode gibt true zurück, wenn der String als Methodenparameter angegeben ist null oder leer. Andernfalls gibt diese Methode false zurück.
  7. Fügen Sie ein privates passwordsAreValid(Object value, ConstraintValidatorContext context) hinzu -Methode auf die erstellte Klasse. Diese Methode gibt „true“ zurück, wenn die Kennwortfelder gültig sind, andernfalls „false“. Diese Methode nimmt zwei Methodenparameter entgegen, die im Folgenden beschrieben werden:
    1. Der erste Methodenparameter ist das validierte Objekt.
    2. Der zweite Methodenparameter ist ein ConstraintValidatorContext Objekt.
  8. Implementieren Sie passwordsAreValid() Methode, indem Sie die folgenden Schritte ausführen:
    1. Beziehen Sie den Wert des Passworts durch Aufrufen von getFieldValue() Methode des ValidatorUtil Klasse. Übergeben Sie das validierte Objekt und den Namen des Passwortfelds als Methodenparameter.
    2. Wenn der Wert des Passworts Feld leer oder null ist, fügen Sie einen Validierungsfehler hinzu, indem Sie addValidationError() aufrufen Methode des ValidatorUtil Klasse. Übergeben Sie den Namen des Passwortfelds und den ConstraintValidatorContext Objekt als Methodenparameter.
    3. Beziehen Sie den Wert der passwordVerification durch Aufrufen von getFieldValue() Methode des ValidatorUtil Klasse. Übergeben Sie das validierte Objekt und den Namen des Felds zur Passwortüberprüfung als Methodenparameter.
    4. Wenn der Wert des Felds zur Passwortüberprüfung leer oder null ist, fügen Sie einen Validierungsfehler hinzu, indem Sie addValidationError() aufrufen Methode des ValidatorUtil Klasse. Übergeben Sie den Namen des Passwortverifizierungsfelds und den ConstraintValidatorContext Objekt als Methodenparameter.
    5. Wenn Validierungsfehler gefunden wurden, geben Sie false zurück. Ansonsten true zurückgeben.
  9. Fügen Sie isValid(Object value, ConstraintValidatorContext context) hinzu Methode des ConstraintValidator Schnittstelle zur Validator-Klasse und implementieren Sie sie, indem Sie die folgenden Schritte ausführen:
    1. Deaktivieren Sie die Standardfehlermeldung, indem Sie disableDefaultConstraintViolation() aufrufen Methode des ConstraintValidatorContext Schnittstelle.
    2. Fügen Sie der Methode eine Try-Catch-Struktur hinzu und fangen Sie alle geprüften Ausnahmen ab. Wenn eine geprüfte Ausnahme ausgelöst wird, fangen Sie sie ab und packen Sie sie in eine RuntimeException . Dies ist erforderlich, weil isValid() Methode des ConstraintValidator Schnittstelle kann geprüfte Ausnahmen nicht auslösen Implementieren Sie den try-Block, indem Sie die folgenden Schritte ausführen:
      1. Erhalten Sie den Wert des Validierungstriggerfelds, indem Sie getFieldValue() aufrufen Methode des ValidatorUtil Klasse. Übergeben Sie das validierte Objekt und den Namen des Validierungstriggerfelds als Methodenparameter.
      2. Wenn der Wert des Validierungstriggerfelds null ist, rufen Sie passwordFieldsAreValid() auf -Methode und übergeben Sie das validierte Objekt und den ConstraintValidatorContext Objekt als Methodenparameter. Gibt den von dieser Methode zurückgegebenen booleschen Wert zurück.
      3. Wenn der Wert des Validierungstriggerfelds nicht null ist, wird wahr zurückgegeben.

Der Quellcode des PasswordsNotEmptyValidator Klasse sieht wie folgt aus:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordsNotEmptyValidator implements ConstraintValidator<PasswordsNotEmpty, Object> {

    private String validationTriggerFieldName;
    private String passwordFieldName;
    private String passwordVerificationFieldName;

    @Override
    public void initialize(PasswordsNotEmpty constraintAnnotation) {
        validationTriggerFieldName = constraintAnnotation.triggerFieldName();
        passwordFieldName = constraintAnnotation.passwordFieldName();
        passwordVerificationFieldName = constraintAnnotation.passwordVerificationFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        context.disableDefaultConstraintViolation();
        try {
            Object validationTrigger = ValidatorUtil.getFieldValue(value, validationTriggerFieldName);
            if (validationTrigger == null) {
                return passwordFieldsAreValid(value, context);
            }
        }
        catch (Exception ex) {
            throw new RuntimeException("Exception occurred during validation", ex);
        }

        return true;
    }

    private boolean passwordFieldsAreValid(Object value, ConstraintValidatorContext context) throws NoSuchFieldException, IllegalAccessException {
        boolean passwordWordFieldsAreValid = true;

        String password = (String) ValidatorUtil.getFieldValue(value, passwordFieldName);
        if (isNullOrEmpty(password)) {
            ValidatorUtil.addValidationError(passwordFieldName, context);
            passwordWordFieldsAreValid = false;
        }

        String passwordVerification = (String) ValidatorUtil.getFieldValue(value, passwordVerificationFieldName);
        if (isNullOrEmpty(passwordVerification)) {
            ValidatorUtil.addValidationError(passwordVerificationFieldName, context);
            passwordWordFieldsAreValid = false;
        }

        return passwordWordFieldsAreValid;
    }

    private boolean isNullOrEmpty(String field) {
        return field == null || field.trim().isEmpty();
    }
}

Zweitens müssen wir die Validator-Klasse erstellen, die Klassen validiert, die mit @PasswordsNotEqual annotiert sind Anmerkung. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Erstellen Sie einen PasswordsNotEqualValidator Klasse und implementieren den ConstraintValidator Schnittstelle. Der ConstraintValidator interface definiert zwei Typparameter, die im Folgenden beschrieben werden:
    1. Der erste Typparameter ist der Anmerkungstyp. Setzen Sie den Wert dieses Typparameters auf PasswordsNotEqual .
    2. Der zweite Typparameter ist der Elementtyp, der vom Validator validiert werden kann. Setzen Sie den Wert dieses Typparameters auf Object (Wir könnten dies auf RegistrationForm setzen aber mit dem Typ Objekt stellt sicher, dass unser Validator nicht auf diese Beispielanwendung beschränkt ist).
  2. Fügen Sie einen privaten passwordFieldName hinzu -Feld auf die erstellte Klasse und setzen Sie ihren Typ auf String .
  3. Fügen Sie einen privaten passwordVerificationFieldName hinzu -Feld auf die erstellte Klasse und setzen Sie ihren Typ auf String .
  4. Fügen Sie initialize(PasswordsNotEqual constraintAnnotation) hinzu Methode des ConstraintValidator Schnittstelle zur Validator-Klasse und implementieren Sie sie, indem Sie die folgenden Schritte ausführen:
    1. Legen Sie den Wert von passwordFieldName fest Feld.
    2. Legen Sie den Wert von passwordVerificationFieldName fest Feld.
  5. Fügen Sie ein privates passwordsAreNotEqual(String password, String passwordVerification) hinzu -Methode auf die erstellte Klasse. Wenn das als Methodenparameter angegebene Passwort und die Passwortüberprüfung nicht gleich sind, gibt diese Methode „true“ zurück. Andernfalls gibt diese Methode false zurück.
  6. Fügen Sie isValid(Object value, ConstraintValidatorContext context) hinzu Methode des ConstraintValidator Schnittstelle zur Validator-Klasse und implementieren Sie sie, indem Sie die folgenden Schritte ausführen:
    1. Deaktivieren Sie die Standardfehlermeldung, indem Sie disableDefaultConstraintViolation() aufrufen Methode des ConstraintValidatorContext Schnittstelle.
    2. Fügen Sie der Methode eine Try-Catch-Struktur hinzu und fangen Sie alle geprüften Ausnahmen ab. Wenn eine geprüfte Ausnahme ausgelöst wird, fangen Sie sie ab und packen Sie sie in eine RuntimeException . Dies ist erforderlich, weil isValid() Methode des ConstraintValidator Schnittstelle kann geprüfte Ausnahmen nicht auslösen Implementieren Sie den try-Block, indem Sie die folgenden Schritte ausführen:
      1. Erhalten Sie den Wert des Passwortfelds, indem Sie getFieldValue() aufrufen Methode des ValidatorUtil Klasse. Übergeben Sie das validierte Objekt und den Namen des Passwortfelds als Methodenparameter.
      2. Erhalten Sie den Wert des Felds zur Passwortüberprüfung, indem Sie getFieldValue() aufrufen Methode des ValidatorUtil Klasse. Übergeben Sie das validierte Objekt und den Namen des Felds zur Passwortüberprüfung als Methodenparameter.
      3. Überprüfen Sie, ob Passwörter nicht gleich sind, indem Sie passwordsAreNotEqual() aufrufen Methode. Übergeben Sie das Passwort und die Passwortüberprüfung als Methodenparameter.
      4. Wenn das Passwort und die Passwortverifizierung nicht gleich sind, fügen Sie einen Validierungsfehler zu den Feldern für das Passwort und die Passwortverifizierung hinzu, indem Sie addValidationError() aufrufen Methode des ValidatorUtil Klasse. Falsch zurückgeben.
      5. Falls Passwort und Passwortüberprüfung waren, wird wahr zurückgegeben.

Der Quellcode des PasswordsNotEqualValidator sieht wie folgt aus:

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class PasswordsNotEqualValidator implements ConstraintValidator<PasswordsNotEqual, Object> {

    private String passwordFieldName;

    private String passwordVerificationFieldName;

    @Override
    public void initialize(PasswordsNotEqual constraintAnnotation) {
        this.passwordFieldName = constraintAnnotation.passwordFieldName();
        this.passwordVerificationFieldName = constraintAnnotation.passwordVerificationFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        context.disableDefaultConstraintViolation();
        try {
            String password = (String) ValidatorUtil.getFieldValue(value, passwordFieldName);
            String passwordVerification = (String) ValidatorUtil.getFieldValue(value, passwordVerificationFieldName);

            if (passwordsAreNotEqual(password, passwordVerification)) {
                ValidatorUtil.addValidationError(passwordFieldName, context);
                ValidatorUtil.addValidationError(passwordVerificationFieldName, context);

                return false;
            }
        }
        catch (Exception ex) {
            throw new RuntimeException("Exception occurred during validation", ex);
        }

        return true;
    }

    private boolean passwordsAreNotEqual(String password, String passwordVerification) {
        return !(password == null ? passwordVerification == null : password.equals(passwordVerification));
    }
}

Das ist es. Wir haben jetzt unsere benutzerdefinierten Validierungseinschränkungen implementiert. Lassen Sie uns herausfinden, wie wir die Registrierungsseite darstellen können.

Rendern der Registrierungsseite

Die Anforderungen unserer Registrierungsseite lauten wie folgt:

  1. Die URL der Registrierungsseite muss '/user/register' lauten.
  2. Wenn der Benutzer ein "normales" Benutzerkonto erstellt, muss unsere Anwendung ein leeres Registrierungsformular ausgeben.
  3. Wenn der Benutzer Social Sign-in verwendet, müssen die vom SaaS-API-Anbieter bereitgestellten Informationen verwendet werden, um die Formularfelder des Registrierungsformulars vorab auszufüllen.

Beginnen wir damit, herauszufinden, wie wir Benutzer auf die Registrierungsseite umleiten können.

Umleitung des Benutzers zur Registrierungsseite

Bevor wir mit der Implementierung der Controller-Methode beginnen können, die die Registrierungsseite rendert, müssen wir einen Controller implementieren, der den Benutzer auf die richtige URL umleitet. Die Anforderungen dieses Controllers lauten wie folgt:

  • Es muss GET-Anfragen verarbeiten, die an die URL '/signup' gesendet werden.
  • Es muss Anfragen an die URL '/user/register' umleiten.

Wir können diesen Controller wie folgt implementieren:

  1. Erstellen Sie einen SignUpController class und kommentieren Sie die Klasse mit @Controller Anmerkung.
  2. Fügen Sie eine öffentliche redirectRequestToRegistrationPage() hinzu -Methode auf die erstellte Klasse. Der Rückgabetyp dieser Methode ist String .
  3. Implementieren Sie die redirectRequestToRegistrationPage() Methode, indem Sie die folgenden Schritte ausführen:
    1. Annotieren Sie die Methode mit @RequestMapping Anmerkung und stellen Sie sicher, dass die Methode GET-Anforderungen verarbeitet, die an die URL „/signup“ gesendet werden.
    2. Gib einen String zurück 'umleiten:/user/register'. Dadurch wird die Anfrage an die URL „/user/register“ umgeleitet.

Der Quellcode des SignUpController Klasse sieht wie folgt aus:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class SignUpController {

    @RequestMapping(value = "/signup", method = RequestMethod.GET)
    public String redirectRequestToRegistrationPage() {
        return "redirect:/user/register";
    }
}

Lassen Sie uns weitermachen und herausfinden, wie wir die Controller-Methode implementieren können, die die Registrierungsseite rendert.

Implementierung der Controller-Methode

Die Controller-Methode, die die Registrierungsseite darstellt, hat eine wichtige Verantwortung:

Es erstellt das Formularobjekt und füllt seine Felder vorab aus. Wenn der Benutzer ein "normales" Benutzerkonto erstellt, erstellt diese Controller-Methode ein leeres Formularobjekt. Wenn der Benutzer andererseits ein Benutzerkonto mit Social Sign-in erstellt, legt diese Controller-Methode die Feldwerte des Formularobjekts fest, indem sie die Informationen verwendet, die vom verwendeten SaaS-API-Anbieter bereitgestellt werden.

Wir können die Controller-Methode implementieren, die die Registrierungsseite rendert, indem wir diesen Schritten folgen:

  1. Erstellen Sie die Controller-Klasse und kommentieren Sie sie mit @Controller Anmerkung.
  2. Kommentieren Sie die Klasse mit den @SessionAttributes annotation und setzen Sie ihren Wert auf 'user'. Wir verwenden diese Anmerkung, um sicherzustellen, dass ein Modellattribut namens „Benutzer“ (unser Formularobjekt) in der Sitzung gespeichert wird.
  3. Fügen Sie ein privates createRegistrationDTO() hinzu Methode zur Klasse. Diese Methode benötigt eine Verbindung Objekt als Methodenparameter und gibt ein RegistrationForm zurück Objekt. Wir können diese Methode implementieren, indem wir die folgenden Schritte ausführen:
    1. Erstellen Sie ein neues Registrierungsformular Objekt.
    2. Wenn die Verbindung Das als Methodenparameter angegebene Objekt ist nicht null, der Benutzer erstellt ein neues Benutzerkonto, indem er sich über soziale Netzwerke anmeldet. Wenn dies der Fall ist, müssen wir dies tun
      1. Holen Sie sich ein Benutzerprofil Objekt durch Aufrufen von fetchUserProfile() Methode der Verbindung Klasse. Dieses Objekt enthält die vom SaaS-API-Anbieter zurückgegebenen Benutzerinformationen.
      2. Legen Sie die E-Mail, den Vornamen und den Nachnamen für das Formularobjekt fest. Wir können diese Informationen erhalten, indem wir die Methoden des UserProfile aufrufen Klasse.
      3. Holen Sie sich einen ConnectionKey Objekt durch Aufrufen von getKey() Methode der Verbindung Klasse. Dieses Objekt enthält die ID des verwendeten Social-Sign-In-Anbieters und eine anbieterspezifische Benutzer-ID.
      4. Stellen Sie den Anmeldeanbieter auf das Formularobjekt ein, indem Sie die folgenden Schritte ausführen:
        1. Rufen Sie den Anmeldeanbieter ab, indem Sie getProviderId() aufrufen Methode des ConnectionKey Klasse.
        2. Transformiere den String zurückgegeben von getProviderId() Methode in Großbuchstaben.
        3. Ermitteln Sie den korrekten Wert des SocialMediaService enum durch Aufrufen von nameOf() Methode. Übergeben Sie den Anmeldeanbieter (in Großbuchstaben) als Methodenparameter (Dies bedeutet, dass die Werte des SocialMediaService enum hängt von den Anmeldeanbieter-IDs ab).
        4. Setzen Sie den zurückgegebenen Wert auf das Formularobjekt.
    3. Formularobjekt zurückgeben.
  4. Die Controller-Methode, die die Registrierungsseite darstellt, heißt showRegistrationForm() . Fügen Sie diese Methode der Controller-Klasse hinzu und implementieren Sie sie, indem Sie die folgenden Schritte ausführen:
    1. Annotieren Sie die Methode mit @RequestMapping Anmerkung und stellen Sie sicher, dass die Controller-Methode GET-Anforderungen verarbeitet, die an die URL „/user/register“ gesendet werden.
    2. Fügen Sie eine Webanfrage hinzu Objekt als Methodenparameter. Wir verwenden die WebRequest als Methodenparameter, da es uns einen einfachen Zugriff auf Anforderungsmetadaten ermöglicht.
    3. Fügen Sie ein Modell hinzu Objekt als Methodenparameter.
    4. Stellen Sie eine Verbindung her Objekt durch Aufrufen des statischen getConnection() method of the ProviderSignInUtils Klasse. Pass the WebRequest object as a method parameter. This method returns null if the WebRequest object doesn't contain SaaS API provider metadata (this means that user is creating a normal user account). If the metadata is found, this method creates a Connection object by using that information and returns the created object.
    5. Get the form object by calling the private createRegistrationDTO() Methode. Pass the Connection Objekt als Methodenparameter.
    6. Set the form object to model as a model attribute called 'user'.
    7. Return the name of the registration form view ('user/registrationForm').

The relevant part of the RegistrationController Klasse sieht wie folgt aus:

import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.UserProfile;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.context.request.WebRequest;

@Controller
@SessionAttributes("user")
public class RegistrationController {

    @RequestMapping(value = "/user/register", method = RequestMethod.GET)
    public String showRegistrationForm(WebRequest request, Model model) {
        Connection<?> connection = ProviderSignInUtils.getConnection(request);

        RegistrationForm registration = createRegistrationDTO(connection);
        model.addAttribute("user", registration);

        return "user/registrationForm";
    }

    private RegistrationForm createRegistrationDTO(Connection<?> connection) {
        RegistrationForm dto = new RegistrationForm();

        if (connection != null) {
            UserProfile socialMediaProfile = connection.fetchUserProfile();
            dto.setEmail(socialMediaProfile.getEmail());
            dto.setFirstName(socialMediaProfile.getFirstName());
            dto.setLastName(socialMediaProfile.getLastName());

            ConnectionKey providerKey = connection.getKey();
            dto.setSignInProvider(SocialMediaService.valueOf(providerKey.getProviderId().toUpperCase()));
        }

        return dto;
    }
}

The next thing that we have to do is to create the JSP page. Let’s move on and find out how this is done.

Creating the JSP Page

We can create the JSP page which contains the registration form by following these steps:

  1. Ensure that the registration form is shown only to anonymous users. We can do this by following these steps:
    1. Wrap the login form and social sign in buttons inside the authorize tag of the Spring Security tag library.
    2. Set the value of the access attribute to isAnonymous() .
  2. Implement the registration form by following these steps:
    1. Ensure that when the registration form is submitted, a POST request is send to url '/user/register'.
    2. Add a CSRF token to the request. This is required because we enabled the CSRF protection of Spring Security in the first part of this tutorial.
    3. If the sign in provider is found from the form object, add it to the form as a hidden field.
    4. Add a firstName field to the form and ensure that the validation errors concerning the firstName field are shown.
    5. Add a lastName field to the form and ensure that the validation errors concerning the lastName field are shown.
    6. Add an email field to the form and ensure that the validation errors concerning the email field are shown.
    7. If the user is creating a normal user account (the value of the form object's signInProvider field is null), follow these steps:
      1. Add a password field to the form and ensure that the validation errors concerning the password field are shown.
      2. Add a passwordVerification field to the form and ensure that validation errors concerning the passwordVerification field are shown.
    8. Add a submit button to the form
  3. Ensure that a help message is shown if an authenticated user accesses the registration page. We can do this by following these steps:
    1. Wrap the error message area inside the authorize tag of the Spring Security tag library.
    2. Set the value of the access attribute to isAuthenticated() .
    3. Get the localized error message by using the message tag of the Spring tag library.

The source code of the registrationForm.jsp page looks as follows:

<!DOCTYPE html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<html>
<head>
    <title></title>
    <script type="text/javascript" src="${pageContext.request.contextPath}/static/js/app/user.form.js"></script>
</head>
<body>
    <div class="page-header">
        <h1><spring:message code="label.user.registration.page.title"/></h1>
    </div>
	<!--
	    If the user is anonymous (not logged in), show the registration form.
	-->
    <sec:authorize access="isAnonymous()">
        <div class="panel panel-default">
            <div class="panel-body">
				<!-- 
					Ensure that when the form is submitted, a POST request is send to url
					'/user/register'.
				-->
                <form:form action="${pageContext.request.contextPath}/user/register" commandName="user" method="POST" enctype="utf8" role="form">
                    <!-- Add CSRF token to the request. -->
					<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
                    <!--
						If the user is using social sign in, add the signInProvider
						as a hidden field.
					-->
					<c:if test="${user.signInProvider != null}">
                        <form:hidden path="signInProvider"/>
                    </c:if>
                    <div class="row">
                        <div id="form-group-firstName" class="form-group col-lg-4">
                            <label class="control-label" for="user-firstName"><spring:message code="label.user.firstName"/>:</label>
							<!--
								Add the firstName field to the form and ensure 
								that validation errors are shown.
							-->
                            <form:input id="user-firstName" path="firstName" cssClass="form-control"/>
                            <form:errors id="error-firstName" path="firstName" cssClass="help-block"/>
                        </div>
                    </div>
                    <div class="row">
                        <div id="form-group-lastName" class="form-group col-lg-4">
                            <label class="control-label" for="user-lastName"><spring:message code="label.user.lastName"/>:</label>
							<!--
								Add the lastName field to the form and ensure
								that validation errors are shown.
							-->
                            <form:input id="user-lastName" path="lastName" cssClass="form-control"/>
                            <form:errors id="error-lastName" path="lastName" cssClass="help-block"/>
                        </div>
                    </div>
                    <div class="row">
                        <div id="form-group-email" class="form-group col-lg-4">
                            <label class="control-label" for="user-email"><spring:message code="label.user.email"/>:</label>
							<!-- 
								Add the email field to the form and ensure
								that validation errors are shown.
							-->
                            <form:input id="user-email" path="email" cssClass="form-control"/>
                            <form:errors id="error-email" path="email" cssClass="help-block"/>
                        </div>
                    </div>
					<!--
						If the user is creating a normal user account, add password fields
						to the form.
					-->
                    <c:if test="${user.signInProvider == null}">
                        <div class="row">
                            <div id="form-group-password" class="form-group col-lg-4">
                                <label class="control-label" for="user-password"><spring:message code="label.user.password"/>:</label>
								<!--
									Add the password field to the form and ensure 
									that validation errors are shown.
								-->
                                <form:password id="user-password" path="password" cssClass="form-control"/>
                                <form:errors id="error-password" path="password" cssClass="help-block"/>
                            </div>
                        </div>
                        <div class="row">
                            <div id="form-group-passwordVerification" class="form-group col-lg-4">
                                <label class="control-label" for="user-passwordVerification"><spring:message code="label.user.passwordVerification"/>:</label>
								<!-- 
									Add the passwordVerification field to the form and ensure
									that validation errors are shown.
								-->
                                <form:password id="user-passwordVerification" path="passwordVerification" cssClass="form-control"/>
                                <form:errors id="error-passwordVerification" path="passwordVerification" cssClass="help-block"/>
                            </div>
                        </div>
                    </c:if>
					<!-- Add the submit button to the form. -->
                    <button type="submit" class="btn btn-default"><spring:message code="label.user.registration.submit.button"/></button>
                </form:form>
            </div>
        </div>
    </sec:authorize>
	<!--
	    If the user is authenticated, show a help message instead
	    of registration form.
	-->
    <sec:authorize access="isAuthenticated()">
        <p><spring:message code="text.registration.page.authenticated.user.help"/></p>
    </sec:authorize>
</body>
</html>

Let's move on and find out how we can process the submission of the registration form.

Processing the Form Submissions of the Registration Form

Our next step is to process the form submissions of the registration form. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Validate the information entered to the registration form. If the information is not valid, we render the registration form and show validation error messages to the user.
  2. Ensure that the email address given by the user is unique. If the email address is not unique, we render the registration form and show an error message to the user.
  3. Create a new user account and log in the user.
  4. Redirect the user to the front page.

This process is illustrated in the following diagram:

Let's start by implementing the controller method which processes the form submissions of the registration form.

Implementing the Controller Method

The controller method which processes the form submissions of the registration form has the following responsibilities:

  • It ensures that the information entered to the registration form is valid.
  • It informs the user if the email address entered to the registration form is found from the database.
  • It passes the form object forward to the service layer.
  • It persists the connection to the UserConnection table if the user is creating a new user account by using social sign in.
  • It logs the user in after a new user account has been created.

We can implement this controller method by making the following changes to the RegistrationController Klasse:

  1. Add a private UserService field to the controller class.
  2. Add a constructor which takes a UserService object as a constructor argument to the RegistrationController class and implement it by following these steps:
    1. Annotate the constructor with the @Autowired Anmerkung. This ensures that the dependencies of this bean are injected by using constructor injection.
    2. Set the value of service Feld.
  3. Add a private addFieldError() method to the controller class. This method is used to add binding errors to the binding result. The method parameters of this method are described in the following:
    1. The objectName parameter is the name of the form object.
    2. The fieldName parameter is the name of the form field which contains invalid value.
    3. The fieldValue parameter contains the value of the form field.
    4. The errorCode parameter is the error code of the field error.
    5. The result parameter is a BindingResult Objekt.
  4. Implement the addFieldError() Methode, indem Sie die folgenden Schritte ausführen:
    1. Create a new FieldError object by using the method parameters.
    2. Add the created FieldError object to the binding result by calling the AddError() method of the BindingResult Klasse.
  5. Add a private createUserAccount() method to the controller class. This method returns the created User object, and takes a RegistrationForm and BindingResult objects as method parameters. If the email address is found from the database, this method returns null. Implement this method by following these steps:
    1. Add a try-catch structure to the method and catch DuplicateEmailException Objekte.
    2. Implement the try block by calling the registerNewUserAccount() method of the UserService Schnittstelle. Pass the RegistrationForm object as a method parameter. Return the information of the created user account.
    3. Implement the catch block by calling the private addFieldError() Methode. Pass the required information as method parameters. This ensures that the user receives an error message which informs him that the email address entered to the registration form is found from the database. Return null.
  6. Add a public registerUserAccount() method to the controller class and implement it by following these steps:
    1. Annotate the method with the @RequestMapping annotation and ensure that the method processes POST request send to url '/user/register'.
    2. Add a RegistrationForm object as a method parameter and annotate it with the following annotations:
      1. Annotate the method parameter with the @Valid Anmerkung. This ensures that the information of this object is validated before the controller method is called.
      2. Annotate the method parameter with the @ModelAttribute annotation and set its value to 'user' (this is the name of the form object).
    3. Add a BindingResult Objekt als Methodenparameter.
    4. Add a WebRequest object as a method parameter. This object is required because we need to access the metadata of the request after the a new user account has been created.
    5. If the binding result has errors, return the name of the form view.
    6. Call the private createUserAccount() method and pass the RegistrationForm and BindingResult objects as method parameters.
    7. If the User object returned by the createUserAccount() method is null, it means that the email address was found from the database. Return the name of the form view.
    8. Log the created user in by calling the static loginInUser() method of the SecurityUtil Klasse. Pass the created User Objekt als Methodenparameter.
    9. Call the static handlePostSignUp() method of the ProviderSignInUtils Klasse. Pass the email address of the created user and the WebRequest object as method parameters. If the user created user account by using social sign in, this method persists the connection to the UserConnection Tisch. If the user created a normal user account, this method doesn't do anything.
    10. Redirect the user to the front page of our application by returning a String 'redirect:/'. This will redirect the request to url '/'.

The relevant part of the RegistrationController Klasse sieht wie folgt aus:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.context.request.WebRequest;

import javax.validation.Valid;

@Controller
@SessionAttributes("user")
public class RegistrationController {

    private UserService service;

    @Autowired
    public RegistrationController(UserService service) {
        this.service = service;
    }

    @RequestMapping(value ="/user/register", method = RequestMethod.POST)
    public String registerUserAccount(@Valid @ModelAttribute("user") RegistrationForm userAccountData,
                                      BindingResult result,
                                      WebRequest request) throws DuplicateEmailException {
        if (result.hasErrors()) {
            return "user/registrationForm";
        }

        User registered = createUserAccount(userAccountData, result);

        if (registered == null) {
            return "user/registrationForm";
        }
        SecurityUtil.logInUser(registered);
        ProviderSignInUtils.handlePostSignUp(registered.getEmail(), request);

        return "redirect:/";
    }

    private User createUserAccount(RegistrationForm userAccountData, BindingResult result) {
        User registered = null;

        try {
            registered = service.registerNewUserAccount(userAccountData);
        }
        catch (DuplicateEmailException ex) {
            addFieldError(
                    "user",
                    "email",
                    userAccountData.getEmail(),
                    "NotExist.user.email",
                    result);
        }

        return registered;
    }

    private void addFieldError(String objectName, String fieldName, String fieldValue,  String errorCode, BindingResult result) {
        FieldError error = new FieldError(
                objectName,
                fieldName,
                fieldValue,
                false,
                new String[]{errorCode},
                new Object[]{},
                errorCode
        );

        result.addError(error);
    }
}

The SecurityUtil class has one static method called loginInUser() . This method takes the information of the created user as a method parameter, and logs the user in programmatically. We can implement this method by following these steps:

  1. Create a new ExampleUserDetails object by using the information of the created user.
  2. Create a new UsernamePasswordAuthenticationToken object and pass the following arguments to its constructor:
    1. The first argument is the principal (aka logged in user). Pass the created ExampleUserDetails object as the first constructor argument.
    2. The second argument contains the credentials of the user. Pass null as the second constructor argument.
    3. The third argument contains the the authorities of the user. We can get the authorities by calling the getAuthorities() method of the ExampleUserDetails Klasse.
  3. Set created Authentication object into security context by following these steps:
    1. Get the SecurityContext object by calling the static getContext() method of the SecurityContextHolder Klasse.
    2. Call the static setAuthentication() method of the SecurityContext class and pass the created UsernamePasswordAuthenticationToken Objekt als Methodenparameter.

The source code of the SecurityUtil Klasse sieht wie folgt aus:

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

public class SecurityUtil {

    public static void logInUser(User user) {
        ExampleUserDetails userDetails = ExampleUserDetails.getBuilder()
                .firstName(user.getFirstName())
                .id(user.getId())
                .lastName(user.getLastName())
                .password(user.getPassword())
                .role(user.getRole())
                .socialSignInProvider(user.getSignInProvider())
                .username(user.getEmail())
                .build();

        Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
It is not a good idea to log in a user who has created a normal user account. Typically you want to send a confirmation email which is used to verify his email address. However, the example application works this way because it simplifies the registration process.

Let's move on and find out how we can create the domain model of our example application.

Creating the Domain Model

The domain model of our application consists of two classes and two enums which are described in the following:

  • The BaseEntity class is a superclass of all entity classes of our application.
  • The User class is the only entity class of our application. It contains the information of a single user.
  • The Role enum specifies the user roles of our application.
  • The SocialMediaService enum specifies the SaaS API providers which are supported by our example application.

Let's move on and find out how we can create the domain model.

First, we have to create a BaseEntity Klasse. It contains the fields which are shared by all entity classes and two callback methods which are used to store values to some of those fields. We can implement this class by following these steps:

  1. Create an abstract BaseEntity class which has one type parameter called ID . This parameter is the type of the entity's private key.
  2. Annotate the class with the @MapperSuperclass Anmerkung. This means that the mapping information of the BaseEntity class is applied to its subclasses.
  3. Add a DateTime field called creationTime to the class and configure it by following these steps:
    1. Annotate the field with the @Column annotation and configure the name of the database column. The value of the nullable attribute to false.
    2. Annotate the field with the @Type annotation and set the value of the type attribute to 'org.jadira.usertype.dateandtime.joda.PersistentDateTime' (Javadoc here ). This marks the field as a custom type and configures the type class which makes it possible to persist DateTime objects with Hibernate.
  4. Add a DateTime field called modificationTime to the class and configure it by using these steps:
    1. Annotate the field with the @Column annotation and set the name of the database column. Ensure that this column is not nullable.
    2. Annotate the field with the @Type annotation and set the value of the type attribute to 'org.jadira.usertype.dateandtime.joda.PersistentDateTime' (check step 3 for more details about this).
  5. Add a long field called version to the class and annotate the field with the @Version Anmerkung. This enables optimistic locking and states the value of the version field serves as optimistic lock value.
  6. Add an abstract getId() method to the class. This method returns the id of the actual entity.
  7. Add a public prePersist() method to the class and annotate the method with the @PrePersist Anmerkung. This method is called before the entity manager persists the object, and it sets the current time as the value of the creationTime and the modificationTime fields.
  8. Add a public preUpdate() method to the class and annotate the method with the @PreUpdate Anmerkung. This method is called before the database UPDATE operation is performed. The implementation of this method sets the current time as the value of the modificationTime Feld.

The source code of the BaseEntity Klasse sieht wie folgt aus:

import org.hibernate.annotations.Type;
import org.joda.time.DateTime;

import javax.persistence.*;

@MappedSuperclass
public abstract class BaseEntity<ID> {

    @Column(name = "creation_time", nullable = false)
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime creationTime;

    @Column(name = "modification_time", nullable = false)
    @Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
    private DateTime modificationTime;

    @Version
    private long version;

    public abstract ID getId();

	//Other getters are omitted for the sake of clarity.

    @PrePersist
    public void prePersist() {
        DateTime now = DateTime.now();
        this.creationTime = now;
        this.modificationTime = now;
    }

    @PreUpdate
    public void preUpdate() {
        this.modificationTime = DateTime.now();
    }
}

Second, we have to create the User Klasse. We can create this class following these steps:

  1. Create a User class which extends the BaseEntity class and give the type of its private key (Long ) as a type parameter.
  2. Annotate the created class with the @Entity Anmerkung.
  3. Annotate the created class with the @Table annotation and ensure that the user information is stored to a database table called 'user_accounts'.
  4. Add a private id field to the class and set its type to Long . Configure the field by following these steps:
    1. Annotate the field with the @Id Anmerkung. This annotation is used to specify the primary key of the entity.
    2. Annotate the field with the @GeneratedValue annotation and set the value of the strategy attribute to GenerationType.AUTO . This means that the persistence provider will pick the appropriate key generation strategy for the used database.
  5. Add a private email field to the class and set its type to String . Annotate the field with the @Column annotation and configure the field by following these rules:
    1. The email address is stored to the 'email' column of the 'users' table.
    2. The maximum length of the email address is 100 characters.
    3. The email address cannot be null.
    4. The email address must be unique.
  6. Add a private firstName field to the class and set its type to String . Annotate the field with the @Column annotation and configure the field by following these rules:
    1. The first name is stored to the 'first_name' column of the 'users' table.
    2. The maximum length of the first name is 100 characters.
    3. The first name cannot be null.
  7. Add a private lastName field to the class and set its to type to String . Annotate the field with the @Column annotation and and configure the field by following these rules:
    1. The last name is stored to the 'last_name' column of the 'users' table.
    2. The maximum length of the last name is 100 characters.
    3. The last name cannot be null.
  8. Add a private password field to the class and set its type to String . Annotate the field with the @Column annotation and configure the field by following these rules:
    1. The password is stored to the 'password' column of the 'users' table.
    2. The maximum length of the password is 255 characters.
  9. Add a private role field to the class and set its type to Role . Annotate the field with the @Enumerated annotation and set its value to EnumType.STRING . This means the value of this field is persisted as enumerated type and that a String value is stored to the database. Annotate the field with the @Column annotation and configure the field by following these rules:
    1. The role is stored to the 'role' column of the 'users' table.
    2. The maximum length of the role is 20 characters.
    3. The role cannot be null.
  10. Add a private signInProvider field to the class and set its type to SocialMediaService . Annotate the field with the @Enumerated annotation and set its value to EnumType.STRING (check step 9 for more details about this). Annotate the field with the @Column annotation and configure the field by following these rules:
    1. The sign in provider is stored to the 'sign_in_provider' field of the 'users' table.
    2. The maximum length of the sign in provider is 20 characters.
  11. Add a public static inner class called Builder to the User Klasse. Implement this class by following these steps:
    1. Add a User field to the class. This field holds a reference to the constructed User Objekt.
    2. Add a constructor to the class. This constructor creates a new User object and sets the role of the created user to Role.ROLE_USER .
    3. Add methods used to set the field values of created User object to the builder class. Each method sets the value given as a method parameter to the correct field and returns a reference to User.Builder Objekt.
    4. Add a build() method to the builder class. This method returns the created User Objekt.
  12. Add a public static getBuilder() method to the User Klasse. This method returns a new User.Builder Objekt.

The source code of the User Klasse sieht wie folgt aus:

import javax.persistence.*;

@Entity
@Table(name = "user_accounts")
public class User extends BaseEntity<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "email", length = 100, nullable = false, unique = true)
    private String email;

    @Column(name = "first_name", length = 100,nullable = false)
    private String firstName;

    @Column(name = "last_name", length = 100, nullable = false)
    private String lastName;

    @Column(name = "password", length = 255)
    private String password;

    @Enumerated(EnumType.STRING)
    @Column(name = "role", length = 20, nullable = false)
    private Role role;

    @Enumerated(EnumType.STRING)
    @Column(name = "sign_in_provider", length = 20)
    private SocialMediaService signInProvider;

	//The constructor and getters are omitted for the sake of clarity

    public static Builder getBuilder() {
        return new Builder();
    }

    public static class Builder {

        private User user;

        public Builder() {
            user = new User();
            user.role = Role.ROLE_USER;
        }

        public Builder email(String email) {
            user.email = email;
            return this;
        }

        public Builder firstName(String firstName) {
            user.firstName = firstName;
            return this;
        }

        public Builder lastName(String lastName) {
            user.lastName = lastName;
            return this;
        }

        public Builder password(String password) {
            user.password = password;
            return this;
        }

        public Builder signInProvider(SocialMediaService signInProvider) {
            user.signInProvider = signInProvider;
            return this;
        }

        public User build() {
            return user;
        }
    }
}

The Role is an enum which specifies the user roles of our application. Its source code looks as follows:

public enum Role {
    ROLE_USER
}

The SocialMediaService is an enum which identifies the SaaS API provider which was used to authenticate the user. Its source code looks as follows:

public enum SocialMediaService {
    FACEBOOK,
    TWITTER
}

Next we will find out how we can implement the service class which creates new user accounts and persists them to the database.

Creating the Service Class

First, we have to create an interface which declares the method used to add new user accounts to the database. This method is described in the following:

The registerNewUserAccount() method takes a RegistrationForm object as method parameter and returns a User Objekt. If the email address stored to the email field of the RegistrationForm object is found from the database, this method throws a DuplicateEmailException .

The source code of the UserService Die Benutzeroberfläche sieht wie folgt aus:

public interface UserService {

    public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException;
}

Second, we have to implement the UserService Schnittstelle. We can do it by following these steps:

  1. Create a class which implements the UserService interface and annotate this class with the @Service Anmerkung.
  2. Add a PasswordEncoder Feld zur erstellten Klasse.
  3. Add a UserRepository field to to created class.
  4. Add a constructor which takes PasswordEncoder and UserRepository objects as constructor arguments to the service class. Implement the constructor by following these steps:
    1. Annotate the constructor with the @Autowired Anmerkung. This ensures that the dependencies of this bean are injected by using constructor injection.
    2. Set the values of passwordEncoder and repository fields.
  5. Add a private emailExist() method to the service class. This method takes a email address as a method argument and returns a boolean . Implement this method by following these steps:
    1. Get the user whose email address is equal to the email address given as a method parameter by calling the findByEmail() method of the UserRepository Schnittstelle. Pass the email address as a method parameter.
    2. If a user is found, return true.
    3. If a user is not found, return false.
  6. Add a private encodePassword() method to service class. This method takes a RegistrationForm object as a method parameter and returns the encoded password. Implement this method by following these steps:
    1. Find out if the user is creating a normal user account. We can get this information by calling the isNormalRegistration() method of the RegistrationForm class . If this method returns true, obtain the encoded password by calling the encode() method of the PasswordEncoder Klasse. Pass the cleartext password as a method parameter. Return the encoded password.
    2. If the user is creating a user account by using social sign in, return null.
  7. Add a registerNewUserAccount() method to the service class and implement it by following these steps:
    1. Annotate the method with the @Transactional Anmerkung. This means that the method is executed "inside" a read-write transaction.
    2. Find out if the email address is found from the database. We can do this by calling the private emailExist() Methode. Pass the RegistrationForm object as a method parameter. If this method returns true, throw a new DuplicateEmailException .
    3. Obtain the encoded password by calling the private encodePassword() Methode. Pass the RegistrationForm Objekt als Methodenparameter.
    4. Get the builder object by calling the getBuilder() method of the User class and set the following information to the created User object:
      • Email address
      • First name
      • Last name
      • Password
    5. Find out if the user is creating a new user account by using social sign in. We can do this by calling the method of the egistrationForm Klasse. If this method returns true, set the used social sign in provider by calling the signInProvider() method of the User.Builder Klasse. Pass the used sign in provider as a method parameter.
    6. Create the User Objekt.
    7. Persist the User object to the database by calling the save() method of the UserRepository Schnittstelle. Pass the created User Objekt als Methodenparameter.
    8. Return the persisted object.

The source code of the RepositoryUserService Klasse sieht wie folgt aus:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class RepositoryUserService implements UserService {

    private PasswordEncoder passwordEncoder;

    private UserRepository repository;

    @Autowired
    public RepositoryUserService(PasswordEncoder passwordEncoder, UserRepository repository) {
        this.passwordEncoder = passwordEncoder;
        this.repository = repository;
    }

    @Transactional
    @Override
    public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {
        if (emailExist(userAccountData.getEmail())) {
            throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");
        }

        String encodedPassword = encodePassword(userAccountData);

        User.Builder user = User.getBuilder()
                .email(userAccountData.getEmail())
                .firstName(userAccountData.getFirstName())
                .lastName(userAccountData.getLastName())
                .password(encodedPassword);

        if (userAccountData.isSocialSignIn()) {
            user.signInProvider(userAccountData.getSignInProvider());
        }

        User registered = user.build();

        return repository.save(registered);
    }

    private boolean emailExist(String email) {
        User user = repository.findByEmail(email);

        if (user != null) {
            return true;
        }

        return false;
    }

    private String encodePassword(RegistrationForm dto) {
        String encodedPassword = null;

        if (dto.isNormalRegistration()) {
            encodedPassword = passwordEncoder.encode(dto.getPassword());
        }

        return encodedPassword;
    }
}

We still have to create the Spring Data JPA repository for our example application. Let's find out how we can do this.

Creating the Spring Data JPA Repository

Our last step is to create a Spring Data JPA repository which is used to

  • Persist new User objects to the database.
  • Find a User object from the database by using email address as a search criteria.

We can create a Spring Data JPA repository which fulfils these requirements by following these steps:

  1. Create the repository interface and extend the JpaRepository Schnittstelle. Give the type of the entity (User ) and type of its private key (Long ) as type parameters. This gives us access to the methods declared by the JpaRepository Schnittstelle. One of those methods is the save() method which is used to persist User objects to the database.
  2. Add a findByEmail() method to the created repository interface. This method takes an email address as a method parameter and returns a User object whose email is equal to the email address given as a method parameter. If no user is found, this method returns null.

The source code of the UserRepository Die Benutzeroberfläche sieht wie folgt aus:

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {

    public User findByEmail(String email);
}

That was it! Let's move on and spend a moment to summarize what we have achieved during this blog post.

The Summary

We have now implemented the requirements of our example application. This means that

  • We have created a registration function which supports both "normal" user accounts and user accounts created by using social sign.
  • The users of our application can log in by using username and password.
  • The users of our application can log in by using social sign in.

Let's refresh our memories and take a look at the registration process. This process is illustrated in the following figure:

This blog post has taught us the following things:

  • We learned how we can start the social sign in flow.
  • We learned how we can pre-populate the field of our registration form by using the information provided by the SaaS API provider.
  • We learned how we can create custom validation constraints which ensures that information entered to the registration form is valid.

The next part of this tutorial describes how we can write unit tests for the web layer of our application.

P.S. The example application of this blog post is available at Github.


Java-Tag