日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

将社交登录添加到Spring MVC Web应用程序:注册和登录

發布時間:2023/12/3 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 将社交登录添加到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提供程序來創建一個。
  • 我們在本教程的第一部分中創建的應用程序上下文配置為登錄頁面指定了一些要求。 這些要求是:

  • 如果匿名用戶嘗試訪問受保護的頁面,則會將其重定向到URL'/ login'。
  • 提交我們的應用程序的登錄表單后,我們的應用程序必須創建一個POST請求以url'/ login / authenticate'。
  • 我們必須在提交登錄表單時創建的POST請求中包含CSRF令牌 。 原因是當我們使用Java配置來配置Spring Security時,默認情況下啟用了Spring Security 3.2.0的CSRF保護 。
  • username參數的名稱是username 。 使用Java配置配置Spring Security時,這是username參數的默認值
  • password參數的名稱為password 。 使用Java配置配置Spring Security時,password參數的默認值。
  • 如果表單登錄失敗,則將用戶重定向到url'/ login?error = bad_credentials'。 這意味著當請求登錄頁面并且錯誤請求參數的值是'bad_credentials'時,我們必須向用戶顯示錯誤消息。
  • SocialAuthenticationFilter處理GET請求發送到url'/ auth / {provider}'的過程。 這意味著
    • 我們可以通過將GET請求發送到url'/ auth / facebook'來啟動Facebook登錄流程。
    • 我們可以通過將GET請求發送到網址“ / auth / twitter”來啟動Twitter登錄流程。
  • 讓我們從創建一個呈現登錄頁面的控制器開始。

    創建控制器

    通過執行以下步驟,我們可以實現呈現登錄頁面的控制器:

  • 創建一個LoginController類,并使用@Controller注釋對創建的類進行注釋。
  • 將showLoginPage()方法添加到控制器類。 此方法返回渲染視圖的名稱。
  • 通過執行以下步驟來實現showLoginPage()方法:
  • 使用@RequestMapping注釋對方法進行注釋,并確保showLoginPage()方法處理發送到url'/ login'的GET請求。
  • 返回登錄視圖的名稱(“用戶/登錄”)。
  • 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頁面

    我們可以按照以下步驟創建登錄頁面:

  • 確保僅對匿名用戶顯示登錄表單和社交登錄按鈕。 我們可以按照以下步驟進行操作:
  • 在Spring Security標簽庫的authorize標簽內包裝登錄表單和社會登錄按鈕。
  • 將訪問屬性的值設置為isAnonymous() 。
  • 如果登錄失敗,則顯示錯誤消息。 如果名為error的請求參數的值為'bad_credentials' ,則可以使用Spring標記庫的message標記獲取本地化的錯誤消息。
  • 通過執行以下步驟來實現登錄表單:
  • 確保在提交登錄表單后,將POST請求發送到URL'/ login / authenticate'。
  • 將CSRF令牌添加到提交登錄表單時發送的請求中。 這是必需的,因為我們在本教程的第一部分中啟用了Spring Security的CSRF保護。
  • 將用戶名字段添加到登錄表單。
  • 在登錄表單中添加一個密碼字段。
  • 在登錄表單中添加一個提交按鈕。
  • 在登錄表單下方添加“創建用戶帳戶”鏈接。 此鏈接創建一個GET請求,以請求url'/ user / register'(注冊頁面)。
  • 通過執行以下步驟,將社交符號按鈕添加到登錄頁面:
  • 添加Facebook登錄按鈕。 此按鈕必須創建一個GET請求以url'/ auth / facebook'。
  • 添加Twitter登錄按鈕。 此按鈕必須創建一個GET請求以url'/ auth / twitter'。
  • 如果經過身份驗證的用戶訪問登錄頁面,請確保顯示幫助消息。 我們可以按照以下步驟進行操作:
  • 將錯誤消息區域包裝在Spring Security標簽庫的authorize標簽內 。
  • 將訪問屬性的值設置為isAuthenticated() 。
  • 通過使用Spring標記庫的message標記獲取本地化的錯誤消息。
  • 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的屬性)。

    通過使用以下方法之一,示例應用程序的用戶可以進入注冊頁面:

  • 他單擊“創建用戶帳戶”鏈接。 此鏈接開始“正常”注冊過程。
  • 他單擊社交登錄按鈕,這將啟動社交登錄流程。
  • 因為很難從如此簡短的描述中獲得總體思路,所以我創建了一個圖,該圖說明了用戶在結束示例應用程序的注冊頁面之前必須遵循的步驟。 此圖有兩個規則:

  • 灰色代表動作,這些動作是我們示例應用程序的責任。
  • 藍色代表由SaaS API提供程序負責的操作。
  • 該圖如下所示:

    讓我們繼續前進,首先為注冊表單創建一個表單對象。

    創建表單對象

    表單對象是一個數據傳輸對象,其中包含輸入到注冊表中的信息,并指定用于驗證該信息的驗證約束。

    在實現表單對象之前,讓我們快速看一下用于驗證表單對象的驗證約束。 這些約束描述如下:

    • @Email批注可確保用戶提供的電子郵件地址格式正確。
    • @NotEmpty批注確保該字段的值不能為空或null。
    • @Size批注可確保字段值的長度不超過字段的最大長度。

    讓我們繼續創建表單對象。 我們可以按照以下步驟進行操作:

  • 創建一個名為RegistrationForm的類。
  • 將電子郵件字段添加到該類,并按照以下規則指定其驗證約束:
  • 電子郵件必須格式正確。
  • 電子郵件不能為空或為空。
  • 電子郵件的最大長度為100個字符。
  • 向這些類添加一個firstName字段,并按照以下規則指定其驗證約束:
  • 名字不能為空或為空。
  • 名字的最大長度為100個字符。
  • 按照以下規則,將lastName字段添加到類中并指定其驗證約束:
  • 姓氏不能為空或為空。
  • 姓氏的最大長度為100個字符。
  • 將密碼字段添加到該類。
  • 將passwordVerification字段添加到該類。
  • 將signInProvider字段添加到該類。 該字段的類型為SocialMediaService 。
  • 將isNormalRegistration()方法添加到創建的類。 如果signInProvider字段的值為null,則此方法返回true。 如果該字段的值不為null,則此方法返回false。
  • 將isSocialSignIn()方法添加到創建的類。 如果signInProvider字段的值不為null,則此方法返回true。 如果該字段的值為null,則此方法返回false。
  • 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驗證約束。 讓我們找出如何創建這些約束。

    創建自定義驗證約束

    我們必須為示例應用程序創建兩個自定義驗證約束。 如果用戶正在創建“普通”用戶帳戶,我們必須確保:

  • 我們的表單對象的password和passwordVerification字段不能為空或為null。
  • password和passwordVerification字段相等。
  • 我們可以按照以下步驟創建自定義驗證約束:

  • 創建一個約束注釋 。
  • 實現一個自定義驗證器類,以確保約束不被打破。
  • 注意: Hibernate Validator 4.2的參考手冊具有有關創建自定義驗證約束的更多信息。

    讓我們從創建約束注釋開始。

    創建約束注釋

    創建約束注釋時,我們必須始終遵循以下常見步驟:

  • 創建注釋類型。 假設注釋類型的名稱為CommonConstraint 。
  • 用@Target注釋注釋創建的注釋類型,并將其值設置為{ElementType.TYPE,ElementType.ANNOTATION_TYPE} ( ElementType枚舉的Javadoc)。 這意味著可以使用@CommonConstraint批注對類和批注類型進行批注。
  • 用@Retention批注注釋創建的批注類型,并將其值設置為RetentionPolicy.RUNTIME 。 這意味著@CommonConstraint批注在運行時可用,并且可以通過反射來讀取。
  • 使用@Constraint批注對創建的批注類型進行批注,并設置其validatedBy屬性的值。 此屬性的值指定用于驗證用@CommonConstraint注釋注釋的類的類。
  • 用@Documented注釋對類進行注釋。 這意味著@CommonConstraint注釋在所有帶有注釋的類的Javadoc文檔中都是可見的。
  • 將消息屬性添加到注釋類型。 該屬性的類型為String ,其默認值為'CommonConstraint'。
  • 將組屬性添加到注釋類型。 該屬性的類型為Class <?>類型的數組,其默認值為空數組。 此屬性允許創建驗證組 。
  • 將有效負載屬性添加到注釋類型。 此屬性的類型是Class <?類型的數組。 擴展Payload> ,其默認值為空數組。 Bean驗證API不會使用此屬性,但是API的客戶端可以將自定義PayLoad對象分配給約束。
  • @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 。
  • 將@Constraint批注的validatedBy屬性的值設置為PasswordsNotEmptyValidator.class 。
  • 將triggerFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認值為空字符串。 此屬性指定字段的名稱,如果其值為null,則觸發我們的自定義約束。
  • 將passwordFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認值為空字符串。 此屬性指定包含用戶密碼的字段的名稱。
  • 將passwordVerificationFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認值為空字符串。 此屬性指定包含用戶密碼驗證的字段的名稱。
  • @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 。
  • 將@Constraint批注的validatedBy屬性的值設置為PasswordsNotEqualValidator.class 。
  • 將passwordFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認值為空字符串。 此屬性指定包含用戶密碼的字段的名稱。
  • 將passwordVerificationFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認值為空字符串。 此屬性指定包含用戶密碼驗證的字段的名稱。
  • @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的類。
  • 將一個addValidationError()方法添加到ValidatorUtil類。 此方法采用以下兩個參數:
  • 第一個參數是字段的名稱。
  • 第二個參數是ConstraintValidatorContext對象。
  • 通過執行以下步驟來實現addValidationError()方法:
  • 創建一個新的約束沖突消息,并確保在構建約束沖突消息時,將由約束注釋指定的消息用作前綴。
  • 將字段添加到約束驗證錯誤。
  • 創建約束驗證錯誤。
  • 將一個getFieldValue()方法添加到ValidatorUtil類。 此方法返回指定字段的字段值,并采用以下兩個參數:
  • 第一個參數是包含請求字段的對象。
  • 第二個參數是請求字段的名稱。
  • 通過執行以下步驟來實現getFieldValue()方法:
  • 獲取對反映所請求字段的Field對象的引用。
  • 確保即使字段是私有的,我們也可以訪問該字段的值。
  • 返回字段值。
  • 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類,并實現ConstraintValidator接口。 ConstraintValidator接口定義兩個類型參數,下面將對其進行描述:
  • 第一個類型參數是注釋類型。 將此類型參數的值設置為PasswordsNotEmpty 。
  • 第二個類型參數是可以由驗證器驗證的元素的類型。 將此類型參數的值設置為Object (我們可以將其設置為RegistrationForm,但是使用Object類型可確保我們的驗證器不限于此示例應用程序)。
  • 在創建的類中添加一個私人的validateTriggerFieldName字段,并將其類型設置為String 。
  • 在創建的類中添加一個專用的passwordFieldName字段,并將其類型設置為String 。
  • 在創建的類中添加一個專用的passwordVerificationFieldName字段,并將其類型設置為String 。
  • 將ConstraintValidator接口的initialize(PasswordsNotEmptyconstraintAnnotation)方法添加到驗證器類,并通過以下步驟實現該方法:
  • 設置validationTriggerFieldName字段的值。
  • 設置passwordFieldName字段的值。
  • 設置passwordVerificationFieldName字段的值。
  • 將私有的isNullOrEmpty(String field)方法添加到創建的類。 如果作為方法參數給出的String為null或為空,則此方法返回true。 否則,此方法返回false。
  • 向創建的類添加一個專用的passwordsAreValid(Object value,ConstraintValidatorContext context)方法。 如果密碼字段有效,則此方法返回true,否則返回false。 此方法采用以下兩個方法參數:
  • 第一個方法參數是經過驗證的對象。
  • 第二個方法參數是ConstraintValidatorContext對象。
  • 通過執行以下步驟來實現passwordsAreValid()方法:
  • 通過調用ValidatorUtil類的getFieldValue()方法獲取密碼字段的值。 將驗證的對象和密碼字段的名稱作為方法參數傳遞。
  • 如果密碼字段的值為空或為null,請通過調用ValidatorUtil類的addValidationError()方法來添加驗證錯誤。 將密碼字段的名稱和ConstraintValidatorContext對象作為方法參數傳遞。
  • 通過調用ValidatorUtil類的getFieldValue()方法獲取passwordVerification字段的值。 將驗證的對象和密碼驗證字段的名稱作為方法參數傳遞。
  • 如果密碼驗證字段的值為空或為null,請通過調用ValidatorUtil類的addValidationError()方法來添加驗證錯誤。 將密碼驗證字段的名稱和ConstraintValidatorContext對象作為方法參數傳遞。
  • 如果發現驗證錯誤,則返回false。 否則返回true。
  • 將ConstraintValidator接口的isValid(Object value,ConstraintValidatorContext上下文)方法添加到驗證器類,并通過以下步驟實現它:
  • 通過調用ConstraintValidatorContext接口的disableDefaultConstraintViolation()方法來禁用默認錯誤消息。
  • 向該方法添加一個try-catch結構,并捕獲所有檢查的異常。 如果引發了檢查的異常,請捕獲該異常并將其包裝在RuntimeException中 。 這是必需的,因為ConstraintValidator接口的isValid()方法無法引發已檢查的異常,請按照以下步驟實現try塊:
  • 通過調用ValidatorUtil類的getFieldValue()方法來獲取驗證觸發器字段的值。 將驗證對象和驗證觸發器字段的名稱作為方法參數傳遞。
  • 如果驗證觸發器字段的值為null,則調用passwordFieldsAreValid()方法并將驗證對象和ConstraintValidatorContext對象作為方法參數傳遞。 返回此方法返回的布爾值。
  • 如果驗證觸發器字段的值不為null,則返回true。
  • 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類,并實現ConstraintValidator接口。 ConstraintValidator接口定義兩個類型參數,下面將對其進行描述:
  • 第一個類型參數是注釋類型。 將此類型參數的值設置為PasswordsNotEqual 。
  • 第二個類型參數是可以由驗證器驗證的元素的類型。 將此類型參數的值設置為Object (我們可以將其設置為RegistrationForm,但是使用Object類型可確保我們的驗證器不限于此示例應用程序)。
  • 在創建的類中添加一個專用的passwordFieldName字段,并將其類型設置為String 。
  • 在創建的類中添加一個專用的passwordVerificationFieldName字段,并將其類型設置為String 。
  • 將ConstraintValidator接口的initialize(PasswordsNotEqual ConsulantAnnotation)方法添加到驗證器類,并通過以下步驟實現該方法:
  • 設置passwordFieldName字段的值。
  • 設置passwordVerificationFieldName字段的值。
  • 向創建的類添加一個專用的passwordsAreNotEqual(String password,String passwordVerification)方法。 如果作為方法參數給出的密碼和密碼驗證不相等,則此方法返回true。 否則,此方法返回false。
  • 將ConstraintValidator接口的isValid(Object value,ConstraintValidatorContext上下文)方法添加到驗證器類,并通過以下步驟實現它:
  • 通過調用ConstraintValidatorContext接口的disableDefaultConstraintViolation()方法來禁用默認錯誤消息。
  • 向該方法添加一個try-catch結構,并捕獲所有檢查的異常。 如果引發了檢查的異常,請捕獲該異常并將其包裝在RuntimeException中 。 這是必需的,因為ConstraintValidator接口的isValid()方法無法引發已檢查的異常,請按照以下步驟實現try塊:
  • 通過調用ValidatorUtil類的getFieldValue()方法來獲取密碼字段的值。 將驗證的對象和密碼字段的名稱作為方法參數傳遞。
  • 通過調用ValidatorUtil類的getFieldValue()方法來獲取密碼驗證字段的值。 將驗證的對象和密碼驗證字段的名稱作為方法參數傳遞。
  • 通過調用passwordsAreNotEqual()方法來檢查密碼是否不相等。 將密碼和密碼驗證作為方法參數傳遞。
  • 如果密碼和密碼驗證不相等,請通過調用ValidatorUtil類的addValidationError()方法將驗證錯誤添加到密碼和密碼驗證字段中。 返回false。
  • 如果密碼和密碼驗證分別是,則返回true。
  • 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));} }

    這就對了。 現在,我們已經實現了自定義驗證約束。 讓我們找出如何呈現注冊頁面。

    渲染注冊頁面

    我們的注冊頁面的要求如下:

  • 注冊頁面的網址必須為“ / user / register”。
  • 如果用戶正在創建“普通”用戶帳戶,則我們的應用程序必須提供空白的注冊表格。
  • 如果用戶使用社交登錄,則必須使用SaaS API提供程序提供的信息來預填充注冊表單的表單字段。
  • 讓我們從發現如何將用戶重定向到注冊頁面開始。

    將用戶重定向到注冊頁面

    在我們開始實現呈現注冊頁面的controller方法之前,我們必須實現一個將用戶重定向到正確url的controller。 該控制器的要求如下:

    • 它必須處理發送到url'/ signup'的GET請求。
    • 它必須將請求重定向到URL“ /用戶/注冊”。

    注意:如果要使用XML配置文件配置應用程序的應用程序上下文,則可以跳過此步驟。 僅當您使用Java配置來配置應用程序的應用程序上下文時,才需要執行此步驟。 這樣做的原因是,目前只有在使用XML配置時 (搜索名為signUpUrl的屬性),您才可以配置注冊URL。

    我們可以按照以下步驟實現此控制器:

  • 創建一個SignUpController類,并使用@Controller注釋對該類進行注釋。
  • 將公共的redirectRequestToRegistrationPage()方法添加到創建的類中。 此方法的返回類型為String 。
  • 通過執行以下步驟來實現redirectRequestToRegistrationPage()方法:
  • 使用@RequestMapping注釋對方法進行注釋,并確保該方法處理發送到url'/ signup'的GET請求。
  • 返回字符串 “ redirect:/ user / register”。 這會將請求重定向到url'/ user / register' 。
  • 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方法:

  • 創建控制器類,并使用@Controller注釋對其進行注釋。
  • 用@SessionAttributes批注注釋該類,并將其值設置為'user'。 我們使用此注釋來確保將名為“用戶”(我們的表單對象)的模型屬性存儲到會話中。
  • 將私有createRegistrationDTO()方法添加到該類。 此方法將Connection對象作為方法參數,并返回RegistrationForm對象。 我們可以通過執行以下步驟來實現此方法:
  • 創建一個新的RegistrationForm對象。
  • 如果作為方法參數給出的Connection對象不為null,則用戶正在使用社交登錄創建新的用戶帳戶。在這種情況下,我們必須
  • 通過調用Connection類的fetchUserProfile()方法來獲取UserProfile對象。 該對象包含SaaS API提供程序返回的用戶信息。
  • 將電子郵件,名字和姓氏設置為表單對象。 我們可以通過調用UserProfile類的方法來獲取此信息。
  • 通過調用Connection類的getKey()方法來獲取ConnectionKey對象。 該對象包含使用的社交登錄提供者的ID和提供者特定的用戶ID。
  • 通過執行以下步驟,將登錄提供程序設置為表單對象:
  • 通過調用ConnectionKey類的getProviderId()方法獲取登錄提供程序。
  • 將getProviderId()方法返回的String轉換為大寫。
  • 通過調用社會媒體服務枚舉的nameOf()方法來獲取正確的值。 將登錄提供程序(大寫)作為方法參數傳遞(這意味著SocialMediaService枚舉的值取決于登錄提供程序ID)。
  • 將返回值設置為表單對象。
  • 返回表單對象。
  • 呈現注冊頁面的控制器方法稱為showRegistrationForm() 。 將此方法添加到控制器類并通過以下步驟實現它:
  • 使用@RequestMapping注釋對方法進行注釋,并確保控制器方法處理發送到URL'/ user / register'的GET請求。
  • 添加一個WebRequest對象作為方法參數。 我們使用WebRequest作為方法參數,因為它使我們可以輕松訪問請求元數據。
  • 添加一個Model對象作為方法參數。
  • 通過調用ProviderSignInUtils類的靜態getConnection()方法來獲取Connection對象。 將WebRequest對象作為方法參數傳遞。 如果WebRequest對象不包含SaaS API提供程序元數據(表示用戶正在創建普通用戶帳戶),則此方法返回null。 如果找到元數據,則此方法使用該信息創建Connection對象,并返回創建的對象。
  • 通過調用私有的createRegistrationDTO()方法來獲取表單對象。 將Connection對象作為方法參數傳遞。
  • 將表單對象設置為要建模的模型屬性,稱為“用戶”。
  • 返回注冊表單視圖的名稱(“ user / registrationForm”)。
  • 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 Security標簽庫的authorize標簽內包裝登錄表單和社會登錄按鈕。
  • 將訪問屬性的值設置為isAnonymous() 。
  • 通過執行以下步驟來實施注冊表:
  • 確保在提交注冊表后,將POST請求發送到URL'/ user / register'。
  • 將CSRF令牌添加到請求中 。 這是必需的,因為我們在本教程的第一部分中啟用了Spring Security的CSRF保護。
  • 如果從表單對象中找到登錄提供程序,請將其作為隱藏字段添加到表單中。
  • 在表單中添加一個firstName字段,并確保顯示與firstName字段有關的驗證錯誤。
  • 在表單中添加一個lastName字段,并確保顯示與lastName字段有關的驗證錯誤。
  • 將電子郵件字段添加到表單,并確保顯示有關電子郵件字段的驗證錯誤。
  • 如果用戶正在創建普通用戶帳戶(表單對象的signInProvider字段的值為null),請按照下列步驟操作:
  • 在表單中添加一個密碼字段,并確保顯示有關密碼字段的驗證錯誤。
  • 將passwordVerification字段添加到表單,并確保顯示與passwordVerification字段有關的驗證錯誤。
  • 將提交按鈕添加到表單
  • 如果經過身份驗證的用戶訪問注冊頁面,請確保顯示幫助消息。 我們可以按照以下步驟進行操作:
  • 將錯誤消息區域包裝在Spring Security標簽庫的authorize標簽內 。
  • 將訪問屬性的值設置為isAuthenticated() 。
  • 通過使用Spring標記庫的message標記獲取本地化的錯誤消息。
  • 注意: 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. 我們可以按照以下步驟進行操作:

  • Validate the information entered to the registration form. If the information is not valid, we render the registration form and show validation error messages to the user.
  • Ensure that the email address given by the user is unique. If the email address is not unique, we render the registration form and show an error message to the user.
  • Create a new user account and log in the user.
  • Redirect the user to the front page.
  • This process is illustrated in the following diagram:

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

    Implementing the Controller Method

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

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

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

  • Add a private UserService field to the controller class.
  • Add a constructor which takes a UserService object as a constructor argument to the RegistrationController class and implement it by following these steps:
  • Annotate the constructor with the @Autowired annotation. This ensures that the dependencies of this bean are injected by using constructor injection.
  • Set the value of service field.
  • Add a private addFieldError() method to the controller class. This method is used to add binding errors to the binding result. The method parameters of this method are described in the following:
  • The objectName parameter is the name of the form object.
  • The fieldName parameter is the name of the form field which contains invalid value.
  • The fieldValue parameter contains the value of the form field.
  • The errorCode parameter is the error code of the field error.
  • The result parameter is a BindingResult object.
  • Implement the addFieldError() method by following these steps:
  • Create a new FieldError object by using the method parameters.
  • Add the created FieldError object to the binding result by calling the AddError() method of the BindingResult class.
  • Add a private createUserAccount() method to the controller class. This method returns the created User object, and takes a RegistrationForm and BindingResult objects as method parameters. If the email address is found from the database, this method returns null. 通過執行以下步驟來實現此方法:
  • Add a try-catch structure to the method and catch DuplicateEmailException objects.
  • Implement the try block by calling the registerNewUserAccount() method of the UserService interface. Pass the RegistrationForm object as a method parameter. Return the information of the created user account.
  • Implement the catch block by calling the private addFieldError() method. Pass the required information as method parameters. This ensures that the user receives an error message which informs him that the email address entered to the registration form is found from the database. Return null.
  • Add a public registerUserAccount() method to the controller class and implement it by following these steps:
  • Annotate the method with the @RequestMapping annotation and ensure that the method processes POST request send to url '/user/register'.
  • Add a RegistrationForm object as a method parameter and annotate it with the following annotations:
  • Annotate the method parameter with the @Valid annotation. This ensures that the information of this object is validated before the controller method is called.
  • Annotate the method parameter with the @ModelAttribute annotation and set its value to 'user' (this is the name of the form object).
  • Add a BindingResult object as a method parameter.
  • Add a WebRequest object as a method parameter. This object is required because we need to access the metadata of the request after the a new user account has been created.
  • If the binding result has errors, return the name of the form view.
  • Call the private createUserAccount() method and pass the RegistrationForm and BindingResult objects as method parameters.
  • If the User object returned by the createUserAccount() method is null, it means that the email address was found from the database. Return the name of the form view.
  • Log the created user in by calling the static loginInUser() method of the SecurityUtil class. Pass the created User object as a method parameter.
  • Call the static handlePostSignUp() method of the ProviderSignInUtils class. Pass the email address of the created user and the WebRequest object as method parameters. If the user created user account by using social sign in, this method persists the connection to the UserConnection table. If the user created a normal user account, this method doesn't do anything.
  • Redirect the user to the front page of our application by returning a String 'redirect:/'. This will redirect the request to url '/' .
  • The relevant part of the 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. 我們可以通過執行以下步驟來實現此方法:

  • Create a new ExampleUserDetails object by using the information of the created user.
  • Create a new UsernamePasswordAuthenticationToken object and pass the following arguments to its constructor:
  • The first argument is the principal (aka logged in user). Pass the created ExampleUserDetails object as the first constructor argument.
  • The second argument contains the credentials of the user. Pass null as the second constructor argument.
  • The third argument contains the the authorities of the user. We can get the authorities by calling the getAuthorities() method of the ExampleUserDetails class.
  • Set created Authentication object into security context by following these steps:
  • Get the SecurityContext object by calling the static getContext() method of the SecurityContextHolder class.
  • Call the static setAuthentication() method of the SecurityContext class and pass the created UsernamePasswordAuthenticationToken object as a method parameter.
  • 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. 我們可以按照以下步驟實現此類:

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

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

  • Create a class which implements the UserService interface and annotate this class with the @Service annotation.
  • Add a PasswordEncoder field to the created class.
  • Add a UserRepository field to to created class.
  • Add a constructor which takes PasswordEncoder and UserRepository objects as constructor arguments to the service class. 通過執行以下步驟來實現構造函數:
  • Annotate the constructor with the @Autowired annotation. This ensures that the dependencies of this bean are injected by using constructor injection.
  • Set the values of passwordEncoder and repository fields.
  • Add a private emailExist() method to the service class. This method takes a email address as a method argument and returns a boolean . 通過執行以下步驟來實現此方法:
  • Get the user whose email address is equal to the email address given as a method parameter by calling the findByEmail() method of the UserRepository interface. Pass the email address as a method parameter.
  • If a user is found, return true.
  • If a user is not found, return false.
  • Add a private encodePassword() method to service class. This method takes a RegistrationForm object as a method parameter and returns the encoded password. 通過執行以下步驟來實現此方法:
  • Find out if the user is creating a normal user account. We can get this information by calling the isNormalRegistration() method of the RegistrationForm class. If this method returns true, obtain the encoded password by calling the encode() method of the PasswordEncoder class. Pass the cleartext password as a method parameter. Return the encoded password.
  • If the user is creating a user account by using social sign in, return null.
  • Add a registerNewUserAccount() method to the service class and implement it by following these steps:
  • Annotate the method with the @Transactional annotation. This means that the method is executed “inside” a read-write transaction.
  • Find out if the email address is found from the database. We can do this by calling the private emailExist() method. Pass the RegistrationForm object as a method parameter. If this method returns true, throw a new DuplicateEmailException .
  • Obtain the encoded password by calling the private encodePassword() method. Pass the RegistrationForm object as a method parameter.
  • Get the builder object by calling the getBuilder() method of the User class and set the following information to the created User object:
  • 電子郵件地址
  • 名字
  • 密碼
  • Find out if the user is creating a new user account by using social sign in. We can do this by calling the <em<issocialsignin() method of the egistrationForm class. If this method returns true, set the used social sign in provider by calling the signInProvider() method of the User.Builder class. Pass the used sign in provider as a method parameter. </em<issocialsignin()
  • Create the User object.
  • Persist the User object to the database by calling the save() method of the UserRepository interface. Pass the created User object as a method parameter.
  • Return the persisted object.
  • 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:

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

    Reference: Adding Social Sign In to a Spring MVC Web Application: Registration and Login from our JCG partner Petri Kainulainen at the Petri Kainulainen blog.

    翻譯自: https://www.javacodegeeks.com/2013/10/adding-social-sign-in-to-a-spring-mvc-web-application-registration-and-login.html

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的将社交登录添加到Spring MVC Web应用程序:注册和登录的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。