javascript
将社交登录添加到Spring MVC Web应用程序:注册和登录
本教程的第一部分描述了如何配置Spring Social 1.1.0和Spring Security 3.2.0,但它留下了兩個非常重要的問題尚未解答。
這些問題是:
- 用戶如何創建新用戶帳戶?
- 用戶如何登錄?
現在該弄臟我們的手并回答這些問題了。 我們的示例應用程序的要求是:
- 必須有可能創建一個“傳統”用戶帳戶。 這意味著使用用戶名和密碼對用戶進行身份驗證。
- 必須可以通過使用諸如Facebook或Twitter的SaaS API提供程序來創建用戶帳戶。 在這種情況下,用戶將通過SaaS API提供程序進行身份驗證。
- 必須可以使用用戶名和密碼登錄。
- 必須可以使用SaaS API提供程序登錄。
讓我們開始滿足這些要求。 我們要做的第一件事是為我們的應用程序創建一個登錄頁面。
創建登錄頁面
我們的應用程序的登錄頁面具有以下三個職責:
- 如果有問題的用戶具有用戶帳戶,則可以使用SaaS API提供程序登錄。
- 如果用戶沒有用戶帳戶,則可以使用SaaS API提供程序來創建一個。
我們在本教程的第一部分中創建的應用程序上下文配置為登錄頁面指定了一些要求。 這些要求是:
- 我們可以通過將GET請求發送到url'/ auth / facebook'來啟動Facebook登錄流程。
- 我們可以通過將GET請求發送到網址“ / auth / twitter”來啟動Twitter登錄流程。
讓我們從創建一個呈現登錄頁面的控制器開始。
創建控制器
通過執行以下步驟,我們可以實現呈現登錄頁面的控制器:
LoginController類的源代碼如下所示:
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頁面
我們可以按照以下步驟創建登錄頁面:
login.jsp頁面的源代碼如下所示:
<!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 formand 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 insteadof 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 3的Twitter Bootstrap插件(稱為Social Buttons)創建的。
現在,我們已經創建了滿足我們要求的登錄頁面。 登錄頁面的相關部分如下所示:
我們的下一步是實現注冊功能。 讓我們開始吧。
實施注冊功能
我們的示例應用程序的注冊功能有兩個要求:
另外,我們在本教程的第一部分中創建的應用程序上下文配置為注冊功能指定了一個要求:
注冊頁面的網址必須為“ / signup”。 這是注冊(也稱為注冊)頁面的默認值,目前,如果我們使用Java配置來配置應用程序上下文,則無法覆蓋此url。 但是,由于url'/ signup'看起來有點丑陋,因此我們將其替換為url'/ user / register'。
注意 :如果使用XML配置文件配置了應用程序上下文,則可以覆蓋注冊URL的默認值(查找名為signUpUrl的屬性)。
通過使用以下方法之一,示例應用程序的用戶可以進入注冊頁面:
因為很難從如此簡短的描述中獲得總體思路,所以我創建了一個圖,該圖說明了用戶在結束示例應用程序的注冊頁面之前必須遵循的步驟。 此圖有兩個規則:
該圖如下所示:
讓我們繼續前進,首先為注冊表單創建一個表單對象。
創建表單對象
表單對象是一個數據傳輸對象,其中包含輸入到注冊表中的信息,并指定用于驗證該信息的驗證約束。
在實現表單對象之前,讓我們快速看一下用于驗證表單對象的驗證約束。 這些約束描述如下:
- @Email批注可確保用戶提供的電子郵件地址格式正確。
- @NotEmpty批注確保該字段的值不能為空或null。
- @Size批注可確保字段值的長度不超過字段的最大長度。
讓我們繼續創建表單對象。 我們可以按照以下步驟進行操作:
RegistrationForm類的源代碼如下所示:
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是一個枚舉,用于標識用于認證用戶的SaaS API提供程序。 其源代碼如下:
public enum SocialMediaService {FACEBOOK,TWITTER }等一下,我們不是就忘了什么嗎?
那些奇怪的注釋(例如@PasswordsNotEqual和@PasswordsNotEmpty)到底是什么?
好吧,它們是自定義bean驗證約束。 讓我們找出如何創建這些約束。
創建自定義驗證約束
我們必須為示例應用程序創建兩個自定義驗證約束。 如果用戶正在創建“普通”用戶帳戶,我們必須確保:
我們可以按照以下步驟創建自定義驗證約束:
注意: Hibernate Validator 4.2的參考手冊具有有關創建自定義驗證約束的更多信息。
讓我們從創建約束注釋開始。
創建約束注釋
創建約束注釋時,我們必須始終遵循以下常見步驟:
@CommonConstraint批注的源代碼如下所示:
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批注。 我們可以按照以下步驟進行操作:
@PasswordsNotEmpty批注的源代碼如下所示:
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批注。 我們可以按照以下步驟進行操作:
@PasswordsNotEqual批注的源代碼如下所示:
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 ""; }現在,我們已經創建了約束注釋。 讓我們繼續來看一下在為自定義約束注釋實現驗證器類時使用的實用程序類。
創建驗證實用程序類
驗證實用程序類提供了兩種靜態方法,下面將對其進行介紹:
- 第一種方法用于將驗證錯誤添加到已驗證對象的字段。
- 第二種方法返回所請求字段的值。
我們可以按照以下步驟實現此類:
ValidatorUtil類的源代碼如下所示:
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注釋注釋的類。 我們可以按照以下步驟進行操作:
PasswordsNotEmptyValidator類的源代碼如下所示:
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext;public class PasswordsNotEmptyValidator implements ConstraintValidator<PasswordsNotEmpty, Object> {private String validationTriggerFieldName;private String passwordFieldName;private String passwordVerificationFieldName;@Overridepublic void initialize(PasswordsNotEmpty constraintAnnotation) {validationTriggerFieldName = constraintAnnotation.triggerFieldName();passwordFieldName = constraintAnnotation.passwordFieldName();passwordVerificationFieldName = constraintAnnotation.passwordVerificationFieldName();}@Overridepublic 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注釋的類。 我們可以按照以下步驟進行操作:
PasswordsNotEqualValidator的源代碼如下所示:
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext;public class PasswordsNotEqualValidator implements ConstraintValidator<PasswordsNotEqual, Object> {private String passwordFieldName;private String passwordVerificationFieldName;@Overridepublic void initialize(PasswordsNotEqual constraintAnnotation) {this.passwordFieldName = constraintAnnotation.passwordFieldName();this.passwordVerificationFieldName = constraintAnnotation.passwordVerificationFieldName();}@Overridepublic 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));} }這就對了。 現在,我們已經實現了自定義驗證約束。 讓我們找出如何呈現注冊頁面。
渲染注冊頁面
我們的注冊頁面的要求如下:
讓我們從發現如何將用戶重定向到注冊頁面開始。
將用戶重定向到注冊頁面
在我們開始實現呈現注冊頁面的controller方法之前,我們必須實現一個將用戶重定向到正確url的controller。 該控制器的要求如下:
- 它必須處理發送到url'/ signup'的GET請求。
- 它必須將請求重定向到URL“ /用戶/注冊”。
注意:如果要使用XML配置文件配置應用程序的應用程序上下文,則可以跳過此步驟。 僅當您使用Java配置來配置應用程序的應用程序上下文時,才需要執行此步驟。 這樣做的原因是,目前只有在使用XML配置時 (搜索名為signUpUrl的屬性),您才可以配置注冊URL。
我們可以按照以下步驟實現此控制器:
SignUpController類的源代碼如下所示:
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";} }讓我們繼續前進,找出如何實現呈現注冊頁面的controller方法。
實施控制器方法
呈現注冊頁面的控制器方法具有一項重要職責:創建表單對象并預填充其字段。 如果用戶正在創建“普通”用戶帳戶,則此控制器方法將創建一個空的表單對象。 另一方面,如果用戶正在使用社交登錄來創建用戶帳戶,則此控制器方法將使用所使用的SaaS API提供程序提供的信息來設置表單對象的字段值。 我們可以通過執行以下步驟來實現呈現注冊頁面的controller方法:
UserController類的相關部分如下所示:
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頁面:
注意: Spring 3.2參考手冊提供了有關Spring JSP標簽庫的form標簽的更多信息。
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="/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 signInProvideras 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 ensurethat 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 ensurethat 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 ensurethat 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 fieldsto 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 ensurethat 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 ensurethat 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 insteadof 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. 我們可以按照以下步驟進行操作:
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 class:
The relevant part of the UserController class looks as follows:
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;@Autowiredpublic 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. 我們可以通過執行以下步驟來實現此方法:
The source code of the SecurityUtil class looks as follows:
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);} }Note: 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.
Note: Our example application doesn't really need a separate base class for entities because it has only one entity. However, I decided to add it anyway because this is often a good idea in real life applications. Let's move on and find out how we can create the domain model. First, we have to create a BaseEntity class. 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. 我們可以按照以下步驟實現此類:
The source code of the BaseEntity class looks as follows:
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;@Versionprivate long version;public abstract ID getId();//Other getters are omitted for the sake of clarity.@PrePersistpublic void prePersist() {DateTime now = DateTime.now();this.creationTime = now;this.modificationTime = now;}@PreUpdatepublic void preUpdate() {this.modificationTime = DateTime.now();} }Second, we have to create the User class. We can create this class following these steps:
Note: You can get more information about the builder pattern by reading a blog post called The builder pattern in practice .
The source code of the User class looks as follows:
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 claritypublic 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. 其源代碼如下:
public enum Role {ROLE_USER }The SocialMediaService is an enum which identifies the SaaS API provider which was used to authenticate the user. 其源代碼如下:
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 object. 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 interface looks as follows:
public interface UserService {public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException; }Second, we have to implement the UserService interface. We can do it by following these steps:
RepositoryUserService類的源代碼如下所示:
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;@Autowiredpublic RepositoryUserService(PasswordEncoder passwordEncoder, UserRepository repository) {this.passwordEncoder = passwordEncoder;this.repository = repository;}@Transactional@Overridepublic 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. 讓我們找出如何做到這一點。
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:
Note: If you want to get more information about Spring Data JPA, you can take a look at my Spring Data JPA tutorial . The source code of the UserRepository interface looks as follows:
import org.springframework.data.jpa.repository.JpaRepository;public interface UserRepository extends JpaRepository<User, Long> {public User findByEmail(String email); }就是這樣! Let's move on and spend a moment to summarize what we have achieved during this blog post.
摘要
We have now implemented the requirements of our example application. 這意味著
- 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:
這篇博客文章告訴我們以下內容:
- 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.
PS The example application of this blog post is available at Github .
翻譯自: https://www.javacodegeeks.com/2013/10/adding-social-sign-in-to-a-spring-mvc-web-application-registration-and-login.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的将社交登录添加到Spring MVC Web应用程序:注册和登录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux命令参数(linux 命令参数
- 下一篇: 基于注释的Spring MVC Web应