Статьи

Добавление входа в социальные сети в веб-приложение Spring MVC: регистрация и вход

Первая часть этого руководства описывала, как мы можем настроить Spring Social 1.1.0 и Spring Security 3.2.0, но оставила два очень важных вопроса без ответа.

Эти вопросы:

  • Как пользователь может создать новую учетную запись?
  • Как пользователь может войти?

Настало время испачкать руки и ответить на эти вопросы. Требования нашего примера приложения:

  • Должна быть возможность создать «традиционную» учетную запись пользователя. Это означает, что пользователь аутентифицирован с использованием имени пользователя и пароля.
  • Должно быть возможно создать учетную запись пользователя с помощью SaaS API-провайдера, такого как Facebook или Twitter. В этом случае пользователь проходит проверку подлинности провайдером SaaS API.
  • Должно быть возможно войти в систему, используя имя пользователя и пароль.
  • Должен быть возможен вход в систему с использованием SaaS API-провайдера.

Давайте начнем выполнять эти требования. Первое, что нам нужно сделать, это создать страницу входа в систему для нашего приложения.

Создание страницы входа

Страница входа в наше приложение имеет три обязанности, которые описаны ниже:

  1. Он должен обеспечивать способ входа в систему с использованием имени пользователя и пароля.
  2. На нем должна быть ссылка на страницу регистрации. Если пользователь хочет создать «традиционную» учетную запись, он может сделать это, нажав на эту ссылку.
  3. Он должен иметь ссылки, которые запускают социальный знак в потоке. Эти ссылки могут быть использованы для двух целей:
    • Если у данного пользователя есть учетная запись, он может войти в систему с помощью поставщика SaaS API.
    • Если у пользователя нет учетной записи, он может создать ее с помощью поставщика SaaS API.

Конфигурация контекста приложения, которую мы создали в первой части этого урока, определяет некоторые требования для нашей страницы входа. Эти требования:

  1. Если анонимный пользователь пытается получить доступ к защищенной странице, он перенаправляется на URL / логин.
  2. Когда форма входа в систему нашего приложения отправлена, наше приложение должно создать запрос POST для URL ‘/ login / authenticate’.
  3. Мы должны включить токен CSRF в запрос POST, который создается при отправке формы для входа. Причина этого заключается в том, что защита SpringR 3.2.0 от CSRF включена по умолчанию, когда мы настраиваем Spring Security с помощью конфигурации Java.
  4. Имя параметра имени пользователя — имя пользователя . Это значение по умолчанию для параметра имени пользователя, когда Spring Security настраивается с использованием конфигурации Java
  5. Имя параметра пароляпароль . Это значение по умолчанию для параметра пароля, когда Spring Security настраивается с помощью конфигурации Java.
  6. В случае неудачного входа в систему, пользователь перенаправляется на URL / логин? Error = bad_credentials. Это означает, что когда запрашивается страница входа в систему и значение параметра запроса об ошибке равно «bad_credentials», мы должны показать сообщение об ошибке пользователю.
  7. SocialAuthenticationFilter обрабатывает запросы GET, отправленные на URL / auth / {provider}. Это значит, что
    • Мы можем запустить вход в Facebook, отправив запрос GET на URL / auth / facebook.
    • Мы можем запустить вход в Twitter, отправив запрос GET на URL / auth / twitter.

Давайте начнем с создания контроллера, который отображает страницу входа.

Создание контроллера

Мы можем реализовать контроллер, который отображает страницу входа, выполнив следующие действия:

  1. Создайте класс LoginController и аннотируйте созданный класс аннотацией @Controller .
  2. Добавьте метод showLoginPage () в класс контроллера. Этот метод возвращает имя визуализированного представления.
  3. Реализуйте метод showLoginPage () , выполнив следующие действия:
    1. Аннотируйте метод с помощью аннотации @RequestMapping и убедитесь, что метод showLoginPage () обрабатывает запросы GET, отправленные на URL ‘/ login’.
    2. Вернуть имя вида входа в систему («пользователь / логин»).

Исходный код класса LoginController выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
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";
    }
}

Наш следующий шаг — создать страницу входа в систему с помощью JSP. Посмотрим, как это делается.

Создание страницы JSP

Мы можем создать страницу входа в систему, выполнив следующие действия:

  1. Убедитесь, что форма входа и кнопки входа в систему показаны только анонимным пользователям. Мы можем сделать это, выполнив следующие действия:
    1. Оберните форму входа и кнопки входа в систему в тег авторизации библиотеки тегов Spring Security .
    2. Установите значение атрибута доступа isAnonymous () .
  2. Показать сообщение об ошибке, если войти не удается. Мы можем получить локализованное сообщение об ошибке, используя тег сообщения библиотеки тегов Spring, если значение параметра запроса с именем error равно «bad_credentials».
  3. Реализуйте форму входа, выполнив следующие действия:
    1. Убедитесь, что при отправке формы входа в систему отправляется запрос POST по URL / login / authenticate.
    2. Добавьте токен CSRF в запрос, который отправляется при отправке формы входа. Это необходимо, потому что мы включили защиту CSRF в Spring Security в первой части этого руководства.
    3. Добавьте поле имени пользователя в форму входа.
    4. Добавьте поле пароля в форму входа.
    5. Добавьте кнопку отправки в форму входа.
  4. Добавьте ссылку «Создать учетную запись пользователя» под формой входа. Эта ссылка создает запрос GET для URL ‘/ user / register’ (страница регистрации).
  5. Добавьте кнопки социальных сетей на страницу входа, выполнив следующие действия:
    1. Добавьте кнопку входа в Facebook. Эта кнопка должна создать запрос GET для URL ‘/ auth / facebook’.
    2. Добавить кнопку входа в Twitter. Эта кнопка должна создать запрос GET для URL ‘/ auth / twitter’.
  6. Убедитесь, что отображается справочное сообщение, если прошедший проверку пользователь заходит на страницу входа. Мы можем сделать это, выполнив следующие действия:
    1. Оберните область сообщения об ошибке внутри тега authorize библиотеки тегов Spring Security .
    2. Установите значение атрибута доступа isAuthenticated () .
    3. Получите локализованное сообщение об ошибке, используя тег сообщения библиотеки тегов Spring .

Исходный код страницы login.jsp выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!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">×</button>
                    <spring:message code="text.login.page.login.failed.error"/>
                </div>
            </c:if>
            <!-- Specifies action and HTTP method -->
            <form action="/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="/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="<c:url value="/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="<c:url value="/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>

Примечание: наше приложение использует Twitter Bootstrap 3 . Кнопки входа в социальные сети создаются с помощью плагина Twitter Bootstrap, который называется Social Buttons for Twitter Bootstrap 3 .

Мы создали страницу входа, которая отвечает нашим требованиям. Соответствующая часть нашей страницы входа выглядит следующим образом:

Социально-Логин-формы

Наш следующий шаг — реализовать функцию регистрации. Давайте начнем.

Реализация функции регистрации

Функция регистрации нашего примера приложения имеет два требования:

  1. Должна быть возможность создать «нормальную» учетную запись пользователя.
  2. Должно быть возможно создать учетную запись пользователя, используя социальный вход.

Кроме того, конфигурация контекста приложения, которую мы создали в первой части этого руководства, определяет одно требование для функции регистрации:

URL-адрес страницы регистрации должен быть «/ регистрация». Это значение по умолчанию для страницы регистрации (также известной как регистрация), и в настоящее время невозможно изменить этот URL, если мы настраиваем контекст приложения с помощью конфигурации Java. Однако, так как URL / signup выглядит немного некрасиво, мы заменим этот URL на URL / user / register.

Примечание . Можно переопределить значение по умолчанию для URL- адреса регистрации, если контекст приложения настроен с использованием файлов конфигурации XML (найдите свойство с именем signUpUrl ).

Пользователь нашего примера приложения может перейти на страницу регистрации одним из следующих способов:

  1. Он нажимает ссылку «Создать учетную запись пользователя». Эта ссылка запускает «нормальный» процесс регистрации.
  2. Он нажимает кнопку социального входа в систему, которая запускает социальный вход в поток.

Поскольку из такого поверхностного описания трудно понять общую идею, я создал диаграмму, которая иллюстрирует шаги, которые пользователь должен выполнить, прежде чем он перейдет на страницу регистрации нашего примера приложения. Эта диаграмма имеет два правила:

  1. Серый цвет представляет действия, которые являются ответственностью нашего примера приложения.
  2. Синий цвет представляет действия, которые являются ответственностью поставщика SaaS API.

Эта диаграмма выглядит следующим образом:

Регистрация

Давайте начнем с создания объекта формы для формы регистрации.

Создание объекта формы

Объект формы — это объект передачи данных, который содержит информацию, введенную в форму регистрации, и определяет ограничения проверки, которые используются для проверки этой информации.

Прежде чем мы реализуем объект формы, давайте кратко рассмотрим ограничения валидации, которые мы используем для проверки нашего объекта формы. Эти ограничения описаны ниже:

  • Аннотация @Email гарантирует, что адрес электронной почты, указанный пользователем, правильно сформирован.
  • Аннотация @NotEmpty гарантирует, что значение поля не может быть пустым или нулевым.
  • Аннотация @Size гарантирует, что длина значения поля не превышает максимальную длину поля.

Давайте двигаться дальше и создадим объект формы. Мы можем сделать это, выполнив следующие действия:

  1. Создайте класс с именем RegistrationForm .
  2. Добавьте поле электронной почты в класс и укажите его ограничения проверки, следуя этим правилам:
    1. Письмо должно быть правильно сформировано.
    2. Электронная почта не может быть пустой или нулевой.
    3. Максимальная длина письма составляет 100 символов.
  3. Добавьте поле firstName в класс и укажите его ограничения проверки, следуя этим правилам:
    1. Имя не может быть пустым или нулевым.
    2. Максимальная длина имени составляет 100 символов.
  4. Добавьте поле lastName в класс и укажите его ограничения проверки, следуя этим правилам:
    1. Фамилия не может быть пустой или нулевой.
    2. Максимальная длина фамилии составляет 100 символов.
  5. Добавьте поле пароля к классу.
  6. Добавьте поле passwordVerification в класс.
  7. Добавьте поле signInProvider в класс. Тип этого поля — SocialMediaService .
  8. Добавьте метод isNormalRegistration () в созданный класс. Этот метод возвращает true, если значение поля signInProvider равно нулю. Если значение этого поля не равно нулю, этот метод возвращает false.
  9. Добавьте метод isSocialSignIn () в созданный класс. Этот метод возвращает true, если значение поля signInProvider не равно нулю. Если значение этого поля равно нулю, этот метод возвращает значение false.

Исходный код класса RegistrationForm выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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.
}

SocialMediaService — это перечисление, которое идентифицирует поставщика API SaaS, который использовался для аутентификации пользователя. Его исходный код выглядит следующим образом:

1
2
3
4
public enum SocialMediaService {
    FACEBOOK,
    TWITTER
}

Подожди, мы не забыли что-то?

Что за странные аннотации, такие как @PasswordsNotEqual и @PasswordsNotEmpty ?

Ну, они являются пользовательскими ограничениями проверки бина. Давайте выясним, как мы можем создать эти ограничения.

Создание пользовательских ограничений проверки

Мы должны создать два пользовательских ограничения проверки для нашего примера приложения. Если пользователь создает «нормальную» учетную запись пользователя, мы должны убедиться, что:

  1. Поля password и passwordVerification нашего объекта формы не могут быть пустыми или нулевыми.
  2. Поля password и passwordVerification равны.

Мы можем создать пользовательские ограничения проверки, выполнив следующие действия:

  1. Создайте аннотацию ограничения.
  2. Реализуйте пользовательский класс валидатора, который гарантирует, что ограничение не нарушено.

Примечание . Справочное руководство по Hibernate validator 4.2 содержит дополнительную информацию о создании пользовательских ограничений проверки .

Давайте начнем с создания аннотаций ограничений.

Создание аннотаций ограничений

Когда мы создаем аннотации ограничений, мы должны всегда выполнять следующие общие шаги:

  1. Создайте тип аннотации. Давайте предположим, что именем нашего типа аннотации является CommonConstraint .
  2. Создайте аннотацию созданного типа аннотации с помощью аннотации @Target и установите для нее значение {ElementType.TYPE, ElementType.ANNOTATION_TYPE} (Javadoc перечисления ElementType ). Это означает, что и классы, и типы аннотаций могут быть аннотированы аннотацией @CommonConstraint .
  3. Создайте аннотацию созданного типа аннотации с помощью аннотации @Retention и установите для нее значение RetentionPolicy.RUNTIME . Это означает, что аннотация @CommonConstraint доступна во время выполнения, и ее можно прочитать с помощью отражения.
  4. Создайте аннотацию созданного типа аннотации с помощью аннотации @Constraint и установите значение ее атрибута validatedBy . Значение этого атрибута указывает класс, который проверяет классы, аннотированные аннотацией @CommonConstraint .
  5. Аннотируйте класс с помощью аннотации @Documented . Это означает, что аннотация @CommonConstraint видна в документации Javadoc для всех классов, которые с ней связаны.
  6. Добавьте атрибут сообщения к типу аннотации. Тип этого атрибута — String , а его значение по умолчанию — CommonConstraint.
  7. Добавьте атрибут groups к типу аннотации. Тип этого атрибута является массивом типа Class <?> , А его значением по умолчанию является пустой массив. Этот атрибут позволяет создавать группы проверки .
  8. Добавьте атрибут полезной нагрузки к типу аннотации. Тип этого атрибута является массивом типа Class <? расширяет Payload> , и его значением по умолчанию является пустой массив. Этот атрибут не используется API Bean Validation, но клиенты API могут назначать пользовательские объекты PayLoad ограничению.

Исходный код аннотации @CommonConstraint выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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 {};
}

Давайте продолжим и выясним, как мы можем создавать аннотации @PasswordsNotEmpty и @PasswordNotEqual . Сначала мы должны создать аннотацию @PasswordsNotEmpty . Мы можем сделать это, выполнив следующие действия:

  1. Выполните общие шаги, описанные ранее, и внесите следующие изменения в созданную аннотацию:
    1. Переименуйте тип аннотации в PasswordsNotEmpty .
    2. Установите значение атрибута validatedBy аннотации @Constraint равным PasswordsNotEmptyValidator.class .
  2. Добавьте атрибут triggerFieldName к типу аннотации. Тип этого атрибута — String , а его значением по умолчанию является пустая строка. Этот атрибут указывает имя поля, которое вызывает наше пользовательское ограничение, если его значение равно нулю.
  3. Добавьте атрибут passwordFieldName к типу аннотации. Тип этого атрибута — String , а его значением по умолчанию является пустая строка. Этот атрибут указывает имя поля, которое содержит пароль пользователя.
  4. Добавьте атрибут passwordVerificationFieldName в тип аннотации. Тип этого атрибута — String , а его значением по умолчанию является пустая строка. Этот атрибут указывает имя поля, в котором содержится проверка пароля пользователя.

Исходный код аннотации @PasswordsNotEmpty выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 "";
}

Во-вторых, нам нужно создать аннотацию @PasswordsNotEqual . Мы можем сделать это, выполнив следующие действия:

  1. Выполните общие шаги, описанные ранее, и внесите следующие изменения в созданную аннотацию:
    1. Переименуйте тип аннотации в PasswordsNotEqual .
    2. Установите значение атрибута validatedBy аннотации @Constraint равным PasswordsNotEqualValidator.class .
  2. Добавьте атрибут passwordFieldName к типу аннотации. Тип этого атрибута — String , а его значением по умолчанию является пустая строка. Этот атрибут указывает имя поля, которое содержит пароль пользователя.
  3. Добавьте атрибут passwordVerificationFieldName к типу аннотации. Тип этого атрибута — String , а его значением по умолчанию является пустая строка. Этот атрибут указывает имя поля, в котором содержится проверка пароля пользователя.

Исходный код аннотации @PasswordsNotEqual выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 "";
}

Теперь мы создали наши аннотации ограничений. Давайте продолжим и посмотрим на служебный класс, который мы используем при реализации классов валидаторов для наших пользовательских аннотаций ограничений.

Создание класса утилиты валидации

Утилита валидации предоставляет два статических метода, которые описаны ниже:

  • Первый метод используется для добавления ошибок проверки в поле проверенного объекта.
  • Второй метод возвращает значение запрошенного поля.

Мы можем реализовать этот класс, выполнив следующие действия:

  1. Создайте класс с именем ValidatorUtil .
  2. Добавьте метод addValidationError () в класс ValidatorUtil . Этот метод принимает два параметра, которые описаны ниже:
    1. Первый параметр — это имя поля.
    2. Второй параметр — это объект ConstraintValidatorContext .
  3. Реализуйте метод addValidationError () , выполнив следующие действия:
    1. Создайте новое нарушение ограничения и убедитесь, что сообщение, указанное в аннотации ограничения, используется в качестве префикса при создании сообщения о нарушении ограничения.
    2. Добавьте поле к ошибке проверки ограничения.
    3. Создайте ошибку проверки ограничения.
  4. Добавьте метод getFieldValue () в класс ValidatorUtil . Этот метод возвращает значение поля указанного поля и принимает два параметра, которые описаны ниже:
    1. Первый параметр — это объект, который содержит запрошенное поле.
    2. Второй параметр — это имя запрашиваемого поля.
  5. Реализуйте метод getFieldValue () , выполнив следующие действия:
    1. Получить ссылку на объект Field, который отражает запрошенное поле.
    2. Убедитесь, что мы можем получить доступ к значению поля, даже если поле является частным.
    3. Вернуть значение поля.

Исходный код класса ValidatorUtil выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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);
    }
}

Теперь мы готовы реализовать наши классы валидаторов. Посмотрим, как это сделать.

Создание классов валидаторов

Сначала мы должны создать класс валидатора, который может проверять классы, аннотированные аннотацией @PasswordsNotEmpty . Мы можем сделать это, выполнив следующие действия:

  1. Создайте класс PasswordsNotEmptyValidator и реализуйте интерфейс ConstraintValidator . Интерфейс ConstraintValidator определяет два параметра типа, которые описаны ниже:
    1. Первым параметром типа является тип аннотации. Установите значение этого параметра типа в PasswordsNotEmpty .
    2. Второй параметр типа — это тип элемента, который может быть проверен валидатором. Установите значение этого параметра типа в Object (Мы могли бы установить это в RegistrationForm, но использование типа Object гарантирует, что наш валидатор не ограничен этим примером приложения).
  2. Добавьте личное поле validationTriggerFieldName в созданный класс и установите его тип в String .
  3. Добавьте личное поле passwordFieldName в созданный класс и установите его тип в String .
  4. Добавьте личное поле passwordVerificationFieldName в созданный класс и установите его тип в String .
  5. Добавьте метод initialize (PasswordsNotEmpty constraintAnnotation) интерфейса ConstraintValidator в класс validator и реализуйте его, выполнив следующие действия:
    1. Установите значение поля validationTriggerFieldName .
    2. Установите значение поля passwordFieldName .
    3. Установите значение поля passwordVerificationFieldName .
  6. Добавьте закрытый метод isNullOrEmpty (String field) к созданному классу. Этот метод возвращает true, если строка, указанная в качестве параметра метода, равна нулю или пуста. В противном случае этот метод возвращает false.
  7. Добавьте закрытый метод passwordsAreValid (значение объекта, контекст ConstraintValidatorContext) к созданному классу. Этот метод возвращает true, если поля пароля верны, и false в противном случае. Этот метод принимает два параметра метода, которые описаны ниже:
    1. Первый параметр метода — это проверенный объект.
    2. Второй параметр метода — это объект ConstraintValidatorContext .
  8. Реализуйте метод passwordsAreValid () , выполнив следующие действия:
    1. Получите значение поля пароля , вызвав метод getFieldValue () класса ValidatorUtil . Передайте проверенный объект и имя поля пароля в качестве параметров метода.
    2. Если значение поля пароля пустое или пустое, добавьте ошибку проверки, вызвав метод addValidationError () класса ValidatorUtil . Передайте имя поля пароля и объект ConstraintValidatorContext в качестве параметров метода.
    3. Получите значение поля passwordVerification , вызвав метод getFieldValue () класса ValidatorUtil . Передайте проверенный объект и имя поля подтверждения пароля в качестве параметров метода.
    4. Если значение поля подтверждения пароля пустое или пустое, добавьте ошибку проверки, вызвав метод addValidationError () класса ValidatorUtil . Передайте имя поля подтверждения пароля и объект ConstraintValidatorContext в качестве параметров метода.
    5. Если были обнаружены ошибки проверки, верните false. В противном случае верните истину.
  9. Добавьте метод isValid (значение объекта, контекст ConstraintValidatorContext) интерфейса ConstraintValidator к классу валидатора и реализуйте его, выполнив следующие действия:
    1. Отключите сообщение об ошибке по умолчанию, вызвав метод disableDefaultConstraintViolation () интерфейса ConstraintValidatorContext .
    2. Добавьте структуру метода try-catch и перехватите все проверенные исключения. Если выброшено исключение, перехватите его и оберните внутри RuntimeException . Это необходимо, поскольку метод isValid () интерфейса ConstraintValidator не может генерировать отмеченные исключения. Реализуйте блок try, выполнив следующие действия:
      1. Получите значение поля триггера проверки, вызвав метод getFieldValue () класса ValidatorUtil . Передайте проверенный объект и имя поля триггера проверки в качестве параметров метода.
      2. Если значение поля триггера проверки равно нулю, вызовите метод passwordFieldsAreValid () и передайте проверенный объект и объект ConstraintValidatorContext в качестве параметров метода. Вернуть логическое значение, возвращаемое этим методом.
      3. Если значение поля триггера проверки не равно нулю, верните true.

Исходный код класса PasswordsNotEmptyValidator выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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();
    }
}

Во-вторых, мы должны создать класс валидатора, который проверяет классы, аннотированные аннотацией @PasswordsNotEqual . Мы можем сделать это, выполнив следующие действия:

  1. Создайте класс PasswordsNotEqualValidator и реализуйте интерфейс ConstraintValidator . Интерфейс ConstraintValidator определяет два параметра типа, которые описаны ниже:
    1. Первым параметром типа является тип аннотации. Установите значение этого параметра типа в PasswordsNotEqual .
    2. Второй параметр типа — это тип элемента, который может быть проверен валидатором. Установите значение этого параметра типа в Object (Мы могли бы установить это в RegistrationForm, но использование типа Object гарантирует, что наш валидатор не ограничен этим примером приложения).
  2. Добавьте личное поле passwordFieldName в созданный класс и установите его тип в String .
  3. Добавьте личное поле passwordVerificationFieldName в созданный класс и установите его тип в String .
  4. Добавьте метод initialize (PasswordsNotEqual constraintAnnotation) интерфейса ConstraintValidator в класс validator и реализуйте его, выполнив следующие действия:
    1. Установите значение поля passwordFieldName .
    2. Установите значение поля passwordVerificationFieldName .
  5. Добавьте закрытый метод passwordsAreNotEqual (String password, String passwordVerification) в созданный класс. Если пароль и проверка пароля, заданные в качестве параметров метода, не равны, этот метод возвращает значение true. В противном случае этот метод возвращает false.
  6. Добавьте метод isValid (значение объекта, контекст ConstraintValidatorContext) интерфейса ConstraintValidator к классу валидатора и реализуйте его, выполнив следующие действия:
    1. Отключите сообщение об ошибке по умолчанию, вызвав метод disableDefaultConstraintViolation () интерфейса ConstraintValidatorContext .
    2. Добавьте структуру метода try-catch и перехватите все проверенные исключения. Если выброшено исключение, перехватите его и оберните внутри RuntimeException . Это необходимо, поскольку метод isValid () интерфейса ConstraintValidator не может генерировать отмеченные исключения. Реализуйте блок try, выполнив следующие действия:
      1. Получите значение поля пароля, вызвав метод getFieldValue () класса ValidatorUtil . Передайте проверенный объект и имя поля пароля в качестве параметров метода.
      2. Получите значение поля подтверждения пароля, вызвав метод getFieldValue () класса ValidatorUtil . Передайте проверенный объект и имя поля подтверждения пароля в качестве параметров метода.
      3. Проверьте, не совпадают ли пароли, вызвав метод passwordsAreNotEqual () . Передайте пароль и подтверждение пароля в качестве параметров метода.
      4. Если пароль и подтверждение пароля не равны, добавьте ошибку проверки для обоих паролей и проверки пароля полей, вызвав addValidationError () метод ValidatorUtil класса. Вернуть ложь.
      5. Если пароль и проверка пароля были, верните true.

Исходный код PasswordsNotEqualValidator выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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));
    }
}

Вот и все.Теперь мы реализовали наши пользовательские ограничения проверки. Давайте узнаем, как мы можем визуализировать страницу регистрации.

Визуализация страницы регистрации

Требования нашей регистрационной страницы следующие:

  1. URL-адрес страницы регистрации должен быть «/ пользователь / регистрация».
  2. Если пользователь создает «нормальную» учетную запись, наше приложение должно предоставить пустую регистрационную форму.
  3. Если пользователь использует социальный вход, информация, предоставляемая поставщиком SaaS API, должна использоваться для предварительного заполнения полей формы регистрационной формы.

Давайте начнем с выяснения, как мы можем перенаправить пользователя на страницу регистрации.

Перенаправление пользователя на страницу регистрации

Прежде чем мы сможем начать реализацию метода контроллера, который отображает страницу регистрации, мы должны реализовать контроллер, который перенаправляет пользователя на правильный URL-адрес. Требования этого контроллера следующие:

  • Он должен обрабатывать запросы GET, отправленные на URL ‘/ signup’.
  • Он должен перенаправлять запросы на URL / user / register.

Примечание. Если вы конфигурируете контекст приложения с помощью файлов конфигурации XML, вы можете пропустить этот шаг. Этот шаг требуется только в том случае, если вы настраиваете контекст приложения для своего приложения с помощью конфигурации Java. Причина этого заключается в том, что в настоящий момент вы можете настроить URL-адрес регистрации, только если вы используете конфигурацию XML ( найдите свойство с именем signUpUrl ).

Мы можем реализовать этот контроллер, выполнив следующие действия:

  1. Создайте класс SignUpController и аннотируйте класс аннотацией @Controller .
  2. Добавьте открытый метод redirectRequestToRegistrationPage () к созданному классу. Тип возвращаемого значения этого метода — String .
  3. Реализуйте метод redirectRequestToRegistrationPage () , выполнив следующие действия:
    1. Аннотируйте метод с помощью аннотации @RequestMapping и убедитесь, что метод обрабатывает запросы GET, отправленные на URL ‘/ signup’.
    2. Вернуть строку ‘redirect: / user / register’. Это перенаправит запрос на URL / user / register .

Исходный код класса SignUpController выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
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";
    }
}

Давайте продолжим и выясним, как мы можем реализовать метод контроллера, который отображает страницу регистрации.

Реализация метода контроллера

Метод контроллера, который отображает страницу регистрации, имеет одну важную ответственность: он создает объект формы и предварительно заполняет его поля. Если пользователь создает «обычную» учетную запись пользователя, этот метод контроллера создает пустой объект формы. С другой стороны, если пользователь создает учетную запись пользователя с помощью входа в систему, этот метод контроллера устанавливает значения полей объекта формы, используя информацию, предоставленную используемым поставщиком API SaaS. Мы можем реализовать метод контроллера, который отображает страницу регистрации, выполнив следующие действия:

  1. Создайте класс контроллера и аннотируйте его аннотацией @Controller .
  2. Комментирование класса с @SessionAttributes аннотацией и установите его значение «пользователя». Мы используем эту аннотацию, чтобы гарантировать, что атрибут модели с именем ‘user’ (наш объект формы) будет сохранен в сеансе.
  3. Добавьте приватный метод createRegistrationDTO () в класс. Этот метод принимает объект Connection в качестве параметра метода и возвращает объект RegistrationForm . Мы можем реализовать этот метод, выполнив следующие действия:
    1. Создайте новый объект RegistrationForm .
    2. Если объект Connection, указанный в качестве параметра метода, не равен NULL, пользователь создает новую учетную запись пользователя, используя социальный вход. Если это так, мы должны
      1. Получить UserProfile объект, вызвав fetchUserProfile () метод подключения класса. Этот объект содержит информацию о пользователе, возвращаемую поставщиком API SaaS.
      2. Установите адрес электронной почты, имя и фамилию для объекта формы. Мы можем получить эту информацию, вызвав методы класса UserProfile .
      3. Получить ConnectionKey объект, вызвав GETKEY () метод подключения класса. Этот объект содержит идентификатор используемого социального входа в провайдере и идентификатор пользователя конкретного провайдера.
      4. Установите поставщик входа в объект формы, выполнив следующие действия:
        1. Чтобы получить поставщика входа, вызовите метод getProviderId () класса ConnectionKey .
        2. Преобразуйте строку, возвращаемую методом getProviderId (), в верхний регистр.
        3. Получите правильное значение перечисления SocialMediaService , вызвав его метод nameOf () . Передайте поставщика входа (в верхнем регистре) в качестве параметра метода (это означает, что значения перечисления SocialMediaService зависит от идентификатора входа в провайдера).
        4. Установите возвращаемое значение для объекта формы.
    3. Вернуть объект формы.
  4. Метод контроллера, который отображает страницу регистрации, называется showRegistrationForm () . Добавьте этот метод в класс контроллера и реализуйте его, выполнив следующие действия:
    1. Аннотируйте метод с помощью аннотации @RequestMapping и убедитесь, что метод контроллера обрабатывает запросы GET, отправленные на URL ‘/ user / register’.
    2. Добавьте объект WebRequest в качестве параметра метода. Мы используем WebRequest в качестве параметра метода, поскольку он дает нам простой доступ к метаданным запроса.
    3. Добавьте объект Model в качестве параметра метода.
    4. Получите объект Connection , вызвав статический метод getConnection () класса ProviderSignInUtils . Передайте объект WebRequest в качестве параметра метода. Этот метод возвращает значение null, если объект WebRequest не содержит метаданных поставщика SaaS API (это означает, что пользователь создает обычную учетную запись пользователя). Если метаданные найдены, этот метод создает объект Connection с использованием этой информации и возвращает созданный объект.
    5. Получите объект формы, вызвав закрытый метод createRegistrationDTO () . Передайте объект Connection в качестве параметра метода.
    6. Установите объект формы для модели в качестве атрибута модели под названием «пользователь».
    7. Возвращает имя представления формы регистрации (‘user / registrationForm’).

Соответствующая часть класса UserController выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;
    }
}

Следующее, что нам нужно сделать, это создать страницу JSP. Давайте двигаться дальше и узнаем, как это делается.

Создание страницы JSP

Мы можем создать страницу JSP, которая содержит регистрационную форму, выполнив следующие действия:

  1. Убедитесь, что регистрационная форма показывается только анонимным пользователям. Мы можем сделать это, выполнив следующие действия:
    1. Оберните форму входа и кнопки входа в систему в тег авторизации библиотеки тегов Spring Security .
    2. Установите значение атрибута доступа isAnonymous () .
  2. Реализуйте регистрационную форму, выполнив следующие действия:
    1. Убедитесь, что при отправке регистрационной формы отправляется запрос POST по URL ‘/ user / register’.
    2. Добавьте токен CSRF в запрос . Это необходимо, потому что мы включили защиту CSRF в Spring Security в первой части этого руководства.
    3. Если провайдер входа найден в объекте формы, добавьте его в форму как скрытое поле.
    4. Добавьте поле firstName в форму и убедитесь, что отображаются ошибки проверки, касающиеся поля firstName .
    5. Добавьте поле lastName в форму и убедитесь, что отображаются ошибки проверки, касающиеся поля lastName .
    6. Добавьте поле электронной почты в форму и убедитесь, что отображаются ошибки проверки, касающиеся поля электронной почты .
    7. Если пользователь создает учетную запись обычного пользователя (значение поля signInProvider объекта формы равно нулю), выполните следующие действия:
      1. Добавьте поле пароля в форму и убедитесь, что отображаются ошибки проверки относительно поля пароля .
      2. Добавьте в форму поле passwordVerification и убедитесь, что отображаются ошибки проверки, касающиеся поля passwordVerification .
    8. Добавить кнопку отправки в форму
  3. Убедитесь, что отображается справочное сообщение, если аутентифицированный пользователь заходит на страницу регистрации. Мы можем сделать это, выполнив следующие действия:
    1. Оберните область сообщения об ошибке внутри тега authorize библиотеки тегов Spring Security .
    2. Установите значение атрибута доступа isAuthenticated () .
    3. Получите локализованное сообщение об ошибке, используя тег сообщения библиотеки тегов Spring .

Примечание . Справочное руководство по Spring 3.2 содержит дополнительную информацию о тегах формы библиотеки тегов Spring JSP .

Исходный код страницы registrationForm.jsp выглядит следующим образом:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
<!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="/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>

Давайте продолжим и выясним, как мы можем обработать подачу регистрационной формы.

Обработка формы представления формы регистрации

Наш следующий шаг — обработать отправку формы регистрационной формы. Мы можем сделать это, выполнив следующие действия:

  1. Проверьте информацию, введенную в регистрационную форму. Если информация неверна, мы предоставляем регистрационную форму и показываем пользователю сообщения об ошибках проверки.
  2. Убедитесь, что адрес электронной почты, указанный пользователем, является уникальным. Если адрес электронной почты не является уникальным, мы предоставляем регистрационную форму и показываем пользователю сообщение об ошибке.
  3. Создайте новую учетную запись пользователя и войдите в систему.
  4. Перенаправить пользователя на главную страницу.

Этот процесс иллюстрируется следующей диаграммой:

социально-регистрацияформ подать

Давайте начнем с реализации метода контроллера, который обрабатывает отправку формы регистрационной формы.

Реализация метода контроллера

Метод контроллера, который обрабатывает представления формы регистрационной формы, имеет следующие обязанности:

  • Это гарантирует, что информация, введенная в регистрационную форму, является действительной.
  • Он информирует пользователя, если адрес электронной почты, введенный в регистрационную форму, найден в базе данных.
  • Он передает объект формы на сервисный уровень.
  • Он сохраняет соединение с таблицей UserConnection, если пользователь создает новую учетную запись пользователя с помощью входа в систему.
  • Он регистрирует пользователя после создания новой учетной записи пользователя.

Мы можем реализовать этот метод контроллера, внеся следующие изменения в класс RegistrationController :

  1. Добавьте личное поле UserService в класс контроллера.
  2. Добавьте конструктор, который принимает объект UserService в качестве аргумента конструктора, в класс RegistrationController и реализуйте его, выполнив следующие действия:
    1. Аннотируйте конструктор аннотацией @Autowired . Это гарантирует, что зависимости этого bean-компонента внедряются с помощью инжектора конструктора.
    2. Установите значение поля обслуживания .
  3. Добавьте приватный метод addFieldError () в класс контроллера. Этот метод используется для добавления ошибок привязки к результату привязки. Параметры метода этого метода описаны ниже:
    1. Параметр objectName — это имя объекта формы.
    2. Параметр fieldName — это имя поля формы, которое содержит недопустимое значение.
    3. Параметр fieldValue содержит значение поля формы.
    4. Параметр errorCode является кодом ошибки поля error.
    5. Параметр результата является объектом BindingResult .
  4. Реализуйте метод addFieldError () , выполнив следующие действия:
    1. Создайте новый объект FieldError , используя параметры метода.
    2. Добавьте созданный объект FieldError в результат привязки, вызвав метод AddError () класса BindingResult .
  5. Добавьте приватный метод createUserAccount () в класс контроллера. Этот метод возвращает созданный объект User и принимает объекты RegistrationForm и BindingResult в качестве параметров метода. Если адрес электронной почты найден в базе данных, этот метод возвращает ноль. Реализуйте этот метод, выполнив следующие действия:
    1. Добавьте структуру метода try-catch и перехватите объекты DuplicateEmailException .
    2. Реализуйте блок try, вызвав метод registerNewUserAccount () интерфейса UserService . Передайте объект RegistrationForm в качестве параметра метода. Вернуть информацию о созданной учетной записи пользователя.
    3. Реализуйте блок catch, вызвав закрытый метод addFieldError () . Передайте необходимую информацию в качестве параметров метода. Это гарантирует, что пользователь получит сообщение об ошибке, информирующее его о том, что адрес электронной почты, введенный в регистрационную форму, найден в базе данных. Вернуть ноль.
  6. Добавьте открытый метод registerUserAccount () в класс контроллера и реализуйте его, выполнив следующие действия:
    1. Аннотируйте метод с помощью аннотации @RequestMapping и убедитесь, что метод обрабатывает отправку запроса POST по URL ‘/ user / register’.
    2. Добавьте объект RegistrationForm в качестве параметра метода и аннотируйте его следующими аннотациями:
      1. Аннотируйте параметр метода аннотацией @Valid . Это гарантирует, что информация об этом объекте проверена перед вызовом метода контроллера.
      2. Аннотируйте параметр метода аннотацией @ModelAttribute и установите для него значение «user» (это имя объекта формы).
    3. Добавьте объект BindingResult в качестве параметра метода.
    4. Добавьте объект WebRequest в качестве параметра метода. Этот объект необходим, потому что нам нужно получить доступ к метаданным запроса после создания новой учетной записи пользователя.
    5. Если в результате привязки есть ошибки, верните имя представления формы.
    6. Вызовите частный метод createUserAccount () и передайте объекты RegistrationForm и BindingResult в качестве параметров метода.
    7. Если объект User, возвращенный методом createUserAccount (), имеет значение null, это означает, что адрес электронной почты был найден в базе данных. Вернуть имя вида формы.
    8. Войдите в систему созданного пользователя, вызвав статический метод loginInUser () класса SecurityUtil . Передайте созданный объект User в качестве параметра метода.
    9. Вызвать статический метод handlePostSignUp () класса ProviderSignInUtils . Передайте адрес электронной почты созданного пользователя и объект WebRequest в качестве параметров метода. Если пользователь создал учетную запись пользователя с помощью входа в систему, этот метод сохраняет соединение с таблицей UserConnection . Если пользователь создал обычную учетную запись, этот метод ничего не делает.
    10. Перенаправьте пользователя на главную страницу нашего приложения, вернув строку ‘redirect: /’. Это перенаправит запрос на URL ‘/’ .

Соответствующая часть класса UserController выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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);
    }
}

Класс SecurityUtil имеет один статический метод с именем loginInUser () . Этот метод принимает информацию о созданном пользователе в качестве параметра метода и регистрирует пользователя программным способом. Мы можем реализовать этот метод, выполнив следующие действия:

  1. Создайте новый объект ExampleUserDetails , используя информацию созданного пользователя.
  2. Создайте новый объект UsernamePasswordAuthenticationToken и передайте следующие аргументы его конструктору:
    1. Первый аргумент — принципал (он же зарегистрированный пользователь). Передайте созданный объект ExampleUserDetails в качестве первого аргумента конструктора.
    2. Второй аргумент содержит учетные данные пользователя. Передайте значение null в качестве второго аргумента конструктора.
    3. Третий аргумент содержит полномочия пользователя. Мы можем получить полномочия, вызвав метод getAuthorities () класса ExampleUserDetails .
  3. Установите созданный объект аутентификации в контекст безопасности, выполнив следующие действия:
    1. Получите объект SecurityContext , вызвав статический метод getContext () класса SecurityContextHolder .
    2. Вызовите статический метод setAuthentication () класса SecurityContext и передайте созданный объект UsernamePasswordAuthenticationToken в качестве параметра метода.

Исходный код класса SecurityUtil выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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);
    }
}

Примечание. Не рекомендуется входить в систему пользователя, создавшего учетную запись обычного пользователя. Обычно вы хотите отправить электронное письмо с подтверждением, которое используется для подтверждения его адреса электронной почты. Тем не менее, пример приложения работает таким образом, потому что он упрощает процесс регистрации. Давайте продолжим и выясним, как мы можем создать модель предметной области нашего примера приложения.

Создание модели предметной области

Модель предметной области нашего приложения состоит из двух классов и двух перечислений, которые описаны ниже:

  • Класс BaseEntity является суперклассом всех классов сущностей нашего приложения.
  • Класс User является единственным классом сущности нашего приложения. Содержит информацию одного пользователя.
  • Перечисление Role определяет роли пользователя нашего приложения.
  • Перечисление SocialMediaService указывает поставщиков API SaaS, которые поддерживаются нашим примером приложения.

Примечание: нашему примеру приложения на самом деле не нужен отдельный базовый класс для сущностей, потому что он имеет только одну сущность Тем не менее, я решил добавить его в любом случае, потому что это часто хорошая идея в реальных приложениях. Давайте двигаться дальше и узнаем, как мы можем создать модель предметной области. Сначала нам нужно создать класс BaseEntity . Он содержит поля, которые являются общими для всех классов сущностей, и два метода обратного вызова, которые используются для хранения значений в некоторых из этих полей. Мы можем реализовать этот класс, выполнив следующие действия:

  1. Создайте абстрактный класс BaseEntity, который имеет один параметр типа с именем ID . Этот параметр является типом закрытого ключа объекта.
  2. Аннотируйте класс с помощью аннотации @MapperSuperclass . Это означает, что информация отображения класса BaseEntity применяется к его подклассам.
  3. Добавьте поле DateTime с именем creationTime в класс и настройте его, выполнив следующие действия:
    1. Пометьте поле аннотацией @Column и настройте имя столбца базы данных. Значение атрибута nullable равно false.
    2. Аннотируйте поле аннотацией @Type и установите значение атрибута type равным ‘org.jadira.usertype.dateandtime.joda.PersistentDateTime’ ( здесь Javadoc ). Это помечает поле как пользовательский тип и настраивает класс типа, который позволяет сохранять объекты DateTime в Hibernate.
  4. Добавьте в класс поле DateTime с именемificationTime и настройте его, выполнив следующие действия:
    1. Пометьте поле аннотацией @Column и задайте имя столбца базы данных. Убедитесь, что этот столбец не обнуляется.
    2. Аннотируйте поле аннотацией @Type и установите значение атрибута type равным «org.jadira.usertype.dateandtime.joda.PersistentDateTime» (подробнее об этом см. Шаг 3).
  5. Добавьте в класс длинное поле с именем version и аннотируйте поле аннотацией @Version . Это включает оптимистическую блокировку и утверждает, что значение поля версии служит значением оптимистической блокировки.
  6. Добавьте абстрактный метод getId () в класс. Этот метод возвращает идентификатор фактической сущности.
  7. Добавьте открытый метод prePersist () к классу и аннотируйте метод аннотацией @PrePersist . Этот метод вызывается до того, как менеджер сущностей сохраняет объект, и устанавливает текущее время в качестве значения полей creationTime иificationTime .
  8. Добавьте открытый метод preUpdate () к классу и аннотируйте метод аннотацией @PreUpdate . Этот метод вызывается перед выполнением операции UPDATE базы данных. Реализация этого метода устанавливает текущее время в качестве значения поля Modification .

Исходный код класса BaseEntity выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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();
    }
}

Во-вторых, мы должны создать класс User . Мы можем создать этот класс, выполнив следующие действия:

  1. Создайте класс User, который расширяет класс BaseEntity и дает тип его закрытого ключа ( Long ) в качестве параметра типа.
  2. Аннотируйте созданный класс с аннотацией @Entity .
  3. Аннотируйте созданный класс с помощью аннотации @Table и убедитесь, что информация о пользователе сохраняется в таблице базы данных, называемой «пользователи».
  4. Добавьте приватное поле идентификатора в класс и установите его тип Long . Настройте поле, выполнив следующие действия:
    1. Аннотируйте поле аннотацией @Id . Эта аннотация используется для указания первичного ключа объекта.
    2. Аннотируйте поле аннотацией @GeneratedValue и установите значение атрибута стратегии равным GenerationType.AUTO . Это означает, что поставщик сохраняемости выберет подходящую стратегию генерации ключа для используемой базы данных.
  5. Добавьте личное поле электронной почты в класс и установите его тип String . Пометьте поле аннотацией @Column и настройте поле, выполнив следующие правила:
    1. Адрес электронной почты хранится в столбце «электронная почта» таблицы «пользователи».
    2. Максимальная длина адреса электронной почты составляет 100 символов.
    3. Адрес электронной почты не может быть нулевым.
    4. Адрес электронной почты должен быть уникальным.
  6. Добавьте личное поле firstName к классу и установите его тип String . Пометьте поле аннотацией @Column и настройте поле, выполнив следующие правила:
    1. Первое имя сохраняется в столбце «first_name» таблицы «users».
    2. Максимальная длина имени составляет 100 символов.
    3. Имя не может быть пустым.
  7. Добавьте частное поле lastName в класс и установите для него тип String . Пометьте поле аннотацией @Column и настройте поле, выполнив следующие правила:
    1. Фамилия сохраняется в столбце «last_name» таблицы «users».
    2. Максимальная длина фамилии составляет 100 символов.
    3. Фамилия не может быть нулевой.
  8. Добавьте частное поле пароля к классу и установите его тип String . Пометьте поле аннотацией @Column и настройте поле, выполнив следующие правила:
    1. Пароль хранится в столбце «пароль» таблицы «пользователи».
    2. Максимальная длина пароля составляет 255 символов.
  9. Добавьте частное поле роли в класс и установите для него тип Role . Аннотируйте поле аннотацией @Enumerated и установите его значение в EnumType.STRING . Это означает, что значение этого поля сохраняется как перечислимый тип и что строковое значение сохраняется в базе данных. Пометьте поле аннотацией @Column и настройте поле, выполнив следующие правила:
    1. Роль хранится в столбце «роль» таблицы «пользователи».
    2. Максимальная длина роли — 20 символов.
    3. Роль не может быть нулевой.
  10. Добавьте личное поле signInProvider в класс и установите его тип в SocialMediaService . Аннотируйте поле аннотацией @Enumerated и установите его значение в EnumType.STRING (проверьте шаг 9 для получения дополнительной информации об этом). Пометьте поле аннотацией @Column и настройте поле, выполнив следующие правила:
    1. Поставщик входа сохраняется в поле «sign_in_provider» таблицы «users».
    2. Максимальная длина входа в провайдера составляет 20 символов.
  11. Добавьте открытый статический внутренний класс с именем Builder в класс User . Реализуйте этот класс, выполнив следующие действия:
    1. Добавьте поле пользователя в класс. Это поле содержит ссылку на созданный объект User .
    2. Добавьте конструктор в класс. Этот конструктор создает новый объект User и устанавливает роль созданного пользователя в Role.ROLE_USER .
    3. Добавьте методы, используемые для установки значений полей созданного объекта User, в класс построителя. Каждый метод устанавливает значение, указанное в качестве параметра метода, в правильное поле и возвращает ссылку на объект User.Builder .
    4. Добавьте метод build () в класс построителя. Этот метод возвращает созданный объект User .
  12. Добавьте открытый статический метод getBuilder () в класс User . Этот метод возвращает новый объект User.Builder .

Примечание. Вы можете получить больше информации о шаблоне компоновщика, прочитав в блоге статью «Шаблон компоновщика на практике» .

Исходный код класса User выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import javax.persistence.*;
 
@Entity
@Table(name = "users")
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;
        }
    }
}

Роль — это перечисление, которое определяет роли пользователя нашего приложения. Его исходный код выглядит следующим образом:

1
2
3
public enum Role {
    ROLE_USER
}

SocialMediaService — это перечисление, которое идентифицирует поставщика API SaaS, который использовался для аутентификации пользователя. Его исходный код выглядит следующим образом:

1
2
3
4
public enum SocialMediaService {
    FACEBOOK,
    TWITTER
}

Далее мы узнаем, как мы можем реализовать класс обслуживания, который создает новые учетные записи пользователей и сохраняет их в базе данных.

Создание класса обслуживания

Во-первых, мы должны создать интерфейс, который объявляет метод, используемый для добавления новых учетных записей пользователей в базу данных. Этот метод описан ниже: Метод registerNewUserAccount () принимает объект RegistrationForm в качестве параметра метода и возвращает объект User . Если адрес электронной почты, сохраненный в поле электронной почты объекта RegistrationForm, найден в базе данных, этот метод вызывает исключение DuplicateEmailException . Исходный код интерфейса UserService выглядит следующим образом:

1
2
3
4
public interface UserService {
 
    public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException;
}

Во-вторых, мы должны реализовать интерфейс UserService . Мы можем сделать это, выполнив следующие действия:

  1. Создайте класс, который реализует интерфейс UserService, и аннотируйте этот класс аннотацией @Service .
  2. Добавьте поле PasswordEncoder в созданный класс.
  3. Добавьте поле UserRepository в созданный класс.
  4. Добавьте конструктор, который принимает объекты PasswordEncoder и UserRepository в качестве аргументов конструктора для класса обслуживания. Реализуйте конструктор, выполнив следующие действия:
    1. Аннотируйте конструктор аннотацией @Autowired . Это гарантирует, что зависимости этого bean-компонента внедряются с помощью инжектора конструктора.
    2. Установите значения полей passwordEncoder и репозитория .
  5. Добавьте приватный метод emailExist () в класс обслуживания. Этот метод принимает адрес электронной почты в качестве аргумента метода и возвращает логическое значение . Реализуйте этот метод, выполнив следующие действия:
    1. Получите пользователя, чей адрес электронной почты равен адресу электронной почты, указанному в качестве параметра метода, вызвав метод findByEmail () интерфейса UserRepository . Передайте адрес электронной почты в качестве параметра метода.
    2. Если пользователь найден, верните true.
    3. Если пользователь не найден, верните false.
  6. Добавьте приватный метод encodePassword () в класс обслуживания. Этот метод принимает объект RegistrationForm в качестве параметра метода и возвращает закодированный пароль. Реализуйте этот метод, выполнив следующие действия:
    1. Узнайте, создает ли пользователь нормальную учетную запись пользователя. Мы можем получить эту информацию, вызвав метод isNormalRegistration () класса RegistrationForm. Если этот метод возвращает значение true, получите закодированный пароль, вызвав метод encode () класса PasswordEncoder . Передайте пароль открытого текста в качестве параметра метода. Верните зашифрованный пароль.
    2. Если пользователь создает учетную запись пользователя с помощью входа в систему, верните ноль.
  7. Добавьте метод registerNewUserAccount () в класс обслуживания и реализуйте его, выполнив следующие действия:
    1. Аннотируйте метод с помощью аннотации @Transactional . Это означает, что метод выполняется «внутри» транзакции чтения-записи.
    2. Узнайте, если адрес электронной почты найден в базе данных. Мы можем сделать это, вызвав закрытый метод emailExist () . Передайте объект RegistrationForm в качестве параметра метода. Если этот метод возвращает значение true, создайте новое исключение DuplicateEmailException .
    3. Получите закодированный пароль, вызвав закрытый метод encodePassword () . Передайте объект RegistrationForm в качестве параметра метода.
    4. Получите объект построителя, вызвав метод getBuilder () класса User и установите следующую информацию для созданного объекта User:
      1. Адрес электронной почты
      2. Имя
      3. Фамилия
      4. пароль
    5. Узнайте, создает ли пользователь новую учетную запись пользователя с помощью входа в систему. Мы можем сделать это, вызвав метод <em <issocialsignin () класса egistrationForm . Если этот метод возвращает значение true, установите используемый провайдер социального входа в систему, вызвав метод signInProvider () класса User.Builder . Передайте используемый провайдер входа в качестве параметра метода. </ Ет <issocialsignin ()
    6. Создайте объект User .
    7. Сохраните объект User в базе данных, вызвав метод save () интерфейса UserRepository . Передайте созданный объект User в качестве параметра метода.
    8. Верните сохраненный объект.

Исходный код класса RepositoryUserService выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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;
    }
}

Нам все еще нужно создать репозиторий Spring Data JPA для нашего примера приложения. Давайте выясним, как мы можем это сделать.

Создание репозитория JPA данных Spring

Нашим последним шагом является создание репозитория Spring Data JPA, который используется для

  • Сохранять новые объекты пользователя в базе данных.
  • Найдите объект User из базы данных, используя адрес электронной почты в качестве критерия поиска.

Мы можем создать репозиторий Spring Data JPA, который отвечает этим требованиям, выполнив следующие действия:

  1. Создайте интерфейс репозитория и расширьте интерфейс JpaRepository . В качестве параметров типа укажите тип объекта ( Пользователь ) и тип его закрытого ключа ( Длинный ). Это дает нам доступ к методам, объявленным интерфейсом JpaRepository . Одним из таких методов является метод save (), который используется для сохранения пользовательских объектов в базе данных.
  2. Добавьте метод findByEmail () в созданный интерфейс репозитория. Этот метод принимает адрес электронной почты в качестве параметра метода и возвращает объект User , адрес электронной почты которого равен адресу электронной почты, указанному в качестве параметра метода. Если пользователь не найден, этот метод возвращает ноль.

Примечание: если вы хотите получить больше информации о Spring Data JPA, вы можете взглянуть на мое руководство по Spring Data JPA . Исходный код интерфейса UserRepository выглядит следующим образом:

1
2
3
4
5
6
import org.springframework.data.jpa.repository.JpaRepository;
 
public interface UserRepository extends JpaRepository<User, Long> {
 
    public User findByEmail(String email);
}

Вот и все! Давайте продолжим и потратим немного времени, чтобы подвести итоги того, чего мы достигли в этом посте

Резюме

Теперь мы реализовали требования нашего примера приложения. Это значит, что

  • Мы создали функцию регистрации, которая поддерживает как «обычные» учетные записи пользователей, так и учетные записи пользователей, созданные с использованием социальных сетей.
  • Пользователи нашего приложения могут войти, используя имя пользователя и пароль.
  • Пользователи нашего приложения могут войти через социальный вход.

Давайте освежим наши воспоминания и посмотрим на процесс регистрации. Этот процесс иллюстрируется на следующем рисунке:

социально-потоков для входа в аккаунт

Этот пост научил нас следующим вещам:

  • Мы узнали, как мы можем запустить социальный знак в потоке.
  • Мы узнали, как мы можем предварительно заполнить поле нашей регистрационной формы, используя информацию, предоставленную поставщиком SaaS API.
  • Мы узнали, как мы можем создать собственные ограничения проверки, которые гарантируют, что информация, введенная в регистрационную форму, действительна.

Следующая часть этого руководства описывает, как мы можем написать модульные тесты для веб-слоя нашего приложения.

PS Пример приложения этого блога доступен на Github .