日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

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

發(fā)布時(shí)間:2023/12/3 javascript 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 将社交登录添加到Spring MVC Web应用程序:注册和登录 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

本教程的第一部分描述了如何配置Spring Social 1.1.0和Spring Security 3.2.0,但它留下了兩個(gè)非常重要的問題尚未解答。

這些問題是:

  • 用戶如何創(chuàng)建新用戶帳戶?
  • 用戶如何登錄?

現(xiàn)在該弄臟我們的手并回答這些問題了。 我們的示例應(yīng)用程序的要求是:

  • 必須有可能創(chuàng)建一個(gè)“傳統(tǒng)”用戶帳戶。 這意味著使用用戶名和密碼對用戶進(jìn)行身份驗(yàn)證。
  • 必須可以通過使用諸如Facebook或Twitter的SaaS API提供程序來創(chuàng)建用戶帳戶。 在這種情況下,用戶將通過SaaS API提供程序進(jìn)行身份驗(yàn)證。
  • 必須可以使用用戶名和密碼登錄。
  • 必須可以使用SaaS API提供程序登錄。

讓我們開始滿足這些要求。 我們要做的第一件事是為我們的應(yīng)用程序創(chuàng)建一個(gè)登錄頁面。

創(chuàng)建登錄頁面

我們的應(yīng)用程序的登錄頁面具有以下三個(gè)職責(zé):

  • 它必須提供一種使用用戶名和密碼登錄的方法。
  • 它必須具有指向注冊頁面的鏈接。 如果用戶想要?jiǎng)?chuàng)建“傳統(tǒng)”用戶帳戶,則可以通過單擊此鏈接來執(zhí)行此操作。
  • 它必須具有啟動(dòng)社交登錄流程的鏈接。 這些鏈接可用于兩個(gè)目的:
    • 如果有問題的用戶具有用戶帳戶,則可以使用SaaS API提供程序登錄。
    • 如果用戶沒有用戶帳戶,則可以使用SaaS API提供程序來創(chuàng)建一個(gè)。
  • 我們在本教程的第一部分中創(chuàng)建的應(yīng)用程序上下文配置為登錄頁面指定了一些要求。 這些要求是:

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

    創(chuàng)建控制器

    通過執(zhí)行以下步驟,我們可以實(shí)現(xiàn)呈現(xiàn)登錄頁面的控制器:

  • 創(chuàng)建一個(gè)LoginController類,并使用@Controller注釋對創(chuàng)建的類進(jìn)行注釋。
  • 將showLoginPage()方法添加到控制器類。 此方法返回渲染視圖的名稱。
  • 通過執(zhí)行以下步驟來實(shí)現(xiàn)showLoginPage()方法:
  • 使用@RequestMapping注釋對方法進(jìn)行注釋,并確保showLoginPage()方法處理發(fā)送到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創(chuàng)建登錄頁面。 讓我們看看這是如何完成的。

    創(chuàng)建JSP頁面

    我們可以按照以下步驟創(chuàng)建登錄頁面:

  • 確保僅對匿名用戶顯示登錄表單和社交登錄按鈕。 我們可以按照以下步驟進(jìn)行操作:
  • 在Spring Security標(biāo)簽庫的authorize標(biāo)簽內(nèi)包裝登錄表單和社會(huì)登錄按鈕。
  • 將訪問屬性的值設(shè)置為isAnonymous() 。
  • 如果登錄失敗,則顯示錯(cuò)誤消息。 如果名為error的請求參數(shù)的值為'bad_credentials' ,則可以使用Spring標(biāo)記庫的message標(biāo)記獲取本地化的錯(cuò)誤消息。
  • 通過執(zhí)行以下步驟來實(shí)現(xiàn)登錄表單:
  • 確保在提交登錄表單后,將POST請求發(fā)送到URL'/ login / authenticate'。
  • 將CSRF令牌添加到提交登錄表單時(shí)發(fā)送的請求中。 這是必需的,因?yàn)槲覀冊诒窘坛痰牡谝徊糠种袉⒂昧薙pring Security的CSRF保護(hù)。
  • 將用戶名字段添加到登錄表單。
  • 在登錄表單中添加一個(gè)密碼字段。
  • 在登錄表單中添加一個(gè)提交按鈕。
  • 在登錄表單下方添加“創(chuàng)建用戶帳戶”鏈接。 此鏈接創(chuàng)建一個(gè)GET請求,以請求url'/ user / register'(注冊頁面)。
  • 通過執(zhí)行以下步驟,將社交符號(hào)按鈕添加到登錄頁面:
  • 添加Facebook登錄按鈕。 此按鈕必須創(chuàng)建一個(gè)GET請求以u(píng)rl'/ auth / facebook'。
  • 添加Twitter登錄按鈕。 此按鈕必須創(chuàng)建一個(gè)GET請求以u(píng)rl'/ auth / twitter'。
  • 如果經(jīng)過身份驗(yàn)證的用戶訪問登錄頁面,請確保顯示幫助消息。 我們可以按照以下步驟進(jìn)行操作:
  • 將錯(cuò)誤消息區(qū)域包裝在Spring Security標(biāo)簽庫的authorize標(biāo)簽內(nèi) 。
  • 將訪問屬性的值設(shè)置為isAuthenticated() 。
  • 通過使用Spring標(biāo)記庫的message標(biāo)記獲取本地化的錯(cuò)誤消息。
  • 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>

    注意:我們的應(yīng)用程序使用Twitter Bootstrap 3 。 社交登錄按鈕是使用稱為Twitter Bootstrap 3的Twitter Bootstrap插件(稱為Social Buttons)創(chuàng)建的。

    現(xiàn)在,我們已經(jīng)創(chuàng)建了滿足我們要求的登錄頁面。 登錄頁面的相關(guān)部分如下所示:

    我們的下一步是實(shí)現(xiàn)注冊功能。 讓我們開始吧。

    實(shí)施注冊功能

    我們的示例應(yīng)用程序的注冊功能有兩個(gè)要求:

  • 必須有可能創(chuàng)建一個(gè)“普通”用戶帳戶。
  • 必須可以通過使用社交登錄來創(chuàng)建用戶帳戶。
  • 另外,我們在本教程的第一部分中創(chuàng)建的應(yīng)用程序上下文配置為注冊功能指定了一個(gè)要求:

    注冊頁面的網(wǎng)址必須為“ / signup”。 這是注冊(也稱為注冊)頁面的默認(rèn)值,目前,如果我們使用Java配置來配置應(yīng)用程序上下文,則無法覆蓋此url。 但是,由于url'/ signup'看起來有點(diǎn)丑陋,因此我們將其替換為url'/ user / register'。

    注意 :如果使用XML配置文件配置了應(yīng)用程序上下文,則可以覆蓋注冊URL的默認(rèn)值(查找名為signUpUrl的屬性)。

    通過使用以下方法之一,示例應(yīng)用程序的用戶可以進(jìn)入注冊頁面:

  • 他單擊“創(chuàng)建用戶帳戶”鏈接。 此鏈接開始“正常”注冊過程。
  • 他單擊社交登錄按鈕,這將啟動(dòng)社交登錄流程。
  • 因?yàn)楹茈y從如此簡短的描述中獲得總體思路,所以我創(chuàng)建了一個(gè)圖,該圖說明了用戶在結(jié)束示例應(yīng)用程序的注冊頁面之前必須遵循的步驟。 此圖有兩個(gè)規(guī)則:

  • 灰色代表動(dòng)作,這些動(dòng)作是我們示例應(yīng)用程序的責(zé)任。
  • 藍(lán)色代表由SaaS API提供程序負(fù)責(zé)的操作。
  • 該圖如下所示:

    讓我們繼續(xù)前進(jìn),首先為注冊表單創(chuàng)建一個(gè)表單對象。

    創(chuàng)建表單對象

    表單對象是一個(gè)數(shù)據(jù)傳輸對象,其中包含輸入到注冊表中的信息,并指定用于驗(yàn)證該信息的驗(yàn)證約束。

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

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

    讓我們繼續(xù)創(chuàng)建表單對象。 我們可以按照以下步驟進(jìn)行操作:

  • 創(chuàng)建一個(gè)名為RegistrationForm的類。
  • 將電子郵件字段添加到該類,并按照以下規(guī)則指定其驗(yàn)證約束:
  • 電子郵件必須格式正確。
  • 電子郵件不能為空或?yàn)榭铡?
  • 電子郵件的最大長度為100個(gè)字符。
  • 向這些類添加一個(gè)firstName字段,并按照以下規(guī)則指定其驗(yàn)證約束:
  • 名字不能為空或?yàn)榭铡?
  • 名字的最大長度為100個(gè)字符。
  • 按照以下規(guī)則,將lastName字段添加到類中并指定其驗(yàn)證約束:
  • 姓氏不能為空或?yàn)榭铡?
  • 姓氏的最大長度為100個(gè)字符。
  • 將密碼字段添加到該類。
  • 將passwordVerification字段添加到該類。
  • 將signInProvider字段添加到該類。 該字段的類型為SocialMediaService 。
  • 將isNormalRegistration()方法添加到創(chuàng)建的類。 如果signInProvider字段的值為null,則此方法返回true。 如果該字段的值不為null,則此方法返回false。
  • 將isSocialSignIn()方法添加到創(chuàng)建的類。 如果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是一個(gè)枚舉,用于標(biāo)識(shí)用于認(rèn)證用戶的SaaS API提供程序。 其源代碼如下:

    public enum SocialMediaService {FACEBOOK,TWITTER }

    等一下,我們不是就忘了什么嗎?

    那些奇怪的注釋(例如@PasswordsNotEqual和@PasswordsNotEmpty)到底是什么?

    好吧,它們是自定義bean驗(yàn)證約束。 讓我們找出如何創(chuàng)建這些約束。

    創(chuàng)建自定義驗(yàn)證約束

    我們必須為示例應(yīng)用程序創(chuàng)建兩個(gè)自定義驗(yàn)證約束。 如果用戶正在創(chuàng)建“普通”用戶帳戶,我們必須確保:

  • 我們的表單對象的password和passwordVerification字段不能為空或?yàn)閚ull。
  • password和passwordVerification字段相等。
  • 我們可以按照以下步驟創(chuàng)建自定義驗(yàn)證約束:

  • 創(chuàng)建一個(gè)約束注釋 。
  • 實(shí)現(xiàn)一個(gè)自定義驗(yàn)證器類,以確保約束不被打破。
  • 注意: Hibernate Validator 4.2的參考手冊具有有關(guān)創(chuàng)建自定義驗(yàn)證約束的更多信息。

    讓我們從創(chuàng)建約束注釋開始。

    創(chuàng)建約束注釋

    創(chuàng)建約束注釋時(shí),我們必須始終遵循以下常見步驟:

  • 創(chuàng)建注釋類型。 假設(shè)注釋類型的名稱為CommonConstraint 。
  • 用@Target注釋注釋創(chuàng)建的注釋類型,并將其值設(shè)置為{ElementType.TYPE,ElementType.ANNOTATION_TYPE} ( ElementType枚舉的Javadoc)。 這意味著可以使用@CommonConstraint批注對類和批注類型進(jìn)行批注。
  • 用@Retention批注注釋創(chuàng)建的批注類型,并將其值設(shè)置為RetentionPolicy.RUNTIME 。 這意味著@CommonConstraint批注在運(yùn)行時(shí)可用,并且可以通過反射來讀取。
  • 使用@Constraint批注對創(chuàng)建的批注類型進(jìn)行批注,并設(shè)置其validatedBy屬性的值。 此屬性的值指定用于驗(yàn)證用@CommonConstraint注釋注釋的類的類。
  • 用@Documented注釋對類進(jìn)行注釋。 這意味著@CommonConstraint注釋在所有帶有注釋的類的Javadoc文檔中都是可見的。
  • 將消息屬性添加到注釋類型。 該屬性的類型為String ,其默認(rèn)值為'CommonConstraint'。
  • 將組屬性添加到注釋類型。 該屬性的類型為Class <?>類型的數(shù)組,其默認(rèn)值為空數(shù)組。 此屬性允許創(chuàng)建驗(yàn)證組 。
  • 將有效負(fù)載屬性添加到注釋類型。 此屬性的類型是Class <?類型的數(shù)組。 擴(kuò)展Payload> ,其默認(rèn)值為空數(shù)組。 Bean驗(yàn)證API不會(huì)使用此屬性,但是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 {}; }

    讓我們繼續(xù)前進(jìn),了解如何創(chuàng)建@PasswordsNotEmpty和@PasswordNotEqual批注。 首先,我們必須創(chuàng)建@PasswordsNotEmpty批注。 我們可以按照以下步驟進(jìn)行操作:

  • 請遵循前面介紹的常見步驟,并對創(chuàng)建的注釋進(jìn)行以下更改:
  • 將注釋類型重命名為PasswordsNotEmpty 。
  • 將@Constraint批注的validatedBy屬性的值設(shè)置為PasswordsNotEmptyValidator.class 。
  • 將triggerFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認(rèn)值為空字符串。 此屬性指定字段的名稱,如果其值為null,則觸發(fā)我們的自定義約束。
  • 將passwordFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認(rèn)值為空字符串。 此屬性指定包含用戶密碼的字段的名稱。
  • 將passwordVerificationFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認(rèn)值為空字符串。 此屬性指定包含用戶密碼驗(yàn)證的字段的名稱。
  • @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 ""; }

    其次,我們必須創(chuàng)建@PasswordsNotEqual批注。 我們可以按照以下步驟進(jìn)行操作:

  • 請遵循前面介紹的常見步驟,并對創(chuàng)建的注釋進(jìn)行以下更改:
  • 將注釋類型重命名為PasswordsNotEqual 。
  • 將@Constraint批注的validatedBy屬性的值設(shè)置為PasswordsNotEqualValidator.class 。
  • 將passwordFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認(rèn)值為空字符串。 此屬性指定包含用戶密碼的字段的名稱。
  • 將passwordVerificationFieldName屬性添加到注釋類型。 此屬性的類型為String ,其默認(rèn)值為空字符串。 此屬性指定包含用戶密碼驗(yàn)證的字段的名稱。
  • @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 ""; }

    現(xiàn)在,我們已經(jīng)創(chuàng)建了約束注釋。 讓我們繼續(xù)來看一下在為自定義約束注釋實(shí)現(xiàn)驗(yàn)證器類時(shí)使用的實(shí)用程序類。

    創(chuàng)建驗(yàn)證實(shí)用程序類

    驗(yàn)證實(shí)用程序類提供了兩種靜態(tài)方法,下面將對其進(jìn)行介紹:

    • 第一種方法用于將驗(yàn)證錯(cuò)誤添加到已驗(yàn)證對象的字段。
    • 第二種方法返回所請求字段的值。

    我們可以按照以下步驟實(shí)現(xiàn)此類:

  • 創(chuàng)建一個(gè)名為ValidatorUtil的類。
  • 將一個(gè)addValidationError()方法添加到ValidatorUtil類。 此方法采用以下兩個(gè)參數(shù):
  • 第一個(gè)參數(shù)是字段的名稱。
  • 第二個(gè)參數(shù)是ConstraintValidatorContext對象。
  • 通過執(zhí)行以下步驟來實(shí)現(xiàn)addValidationError()方法:
  • 創(chuàng)建一個(gè)新的約束沖突消息,并確保在構(gòu)建約束沖突消息時(shí),將由約束注釋指定的消息用作前綴。
  • 將字段添加到約束驗(yàn)證錯(cuò)誤。
  • 創(chuàng)建約束驗(yàn)證錯(cuò)誤。
  • 將一個(gè)getFieldValue()方法添加到ValidatorUtil類。 此方法返回指定字段的字段值,并采用以下兩個(gè)參數(shù):
  • 第一個(gè)參數(shù)是包含請求字段的對象。
  • 第二個(gè)參數(shù)是請求字段的名稱。
  • 通過執(zhí)行以下步驟來實(shí)現(xiàn)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);} }

    現(xiàn)在,我們準(zhǔn)備實(shí)現(xiàn)驗(yàn)證器類。 讓我們看看如何完成。

    創(chuàng)建驗(yàn)證器類

    首先,我們必須創(chuàng)建驗(yàn)證器類,該類可以驗(yàn)證使用@PasswordsNotEmpty注釋注釋的類。 我們可以按照以下步驟進(jìn)行操作:

  • 創(chuàng)建一個(gè)PasswordsNotEmptyValidator類,并實(shí)現(xiàn)ConstraintValidator接口。 ConstraintValidator接口定義兩個(gè)類型參數(shù),下面將對其進(jìn)行描述:
  • 第一個(gè)類型參數(shù)是注釋類型。 將此類型參數(shù)的值設(shè)置為PasswordsNotEmpty 。
  • 第二個(gè)類型參數(shù)是可以由驗(yàn)證器驗(yàn)證的元素的類型。 將此類型參數(shù)的值設(shè)置為Object (我們可以將其設(shè)置為RegistrationForm,但是使用Object類型可確保我們的驗(yàn)證器不限于此示例應(yīng)用程序)。
  • 在創(chuàng)建的類中添加一個(gè)私人的validateTriggerFieldName字段,并將其類型設(shè)置為String 。
  • 在創(chuàng)建的類中添加一個(gè)專用的passwordFieldName字段,并將其類型設(shè)置為String 。
  • 在創(chuàng)建的類中添加一個(gè)專用的passwordVerificationFieldName字段,并將其類型設(shè)置為String 。
  • 將ConstraintValidator接口的initialize(PasswordsNotEmptyconstraintAnnotation)方法添加到驗(yàn)證器類,并通過以下步驟實(shí)現(xiàn)該方法:
  • 設(shè)置validationTriggerFieldName字段的值。
  • 設(shè)置passwordFieldName字段的值。
  • 設(shè)置passwordVerificationFieldName字段的值。
  • 將私有的isNullOrEmpty(String field)方法添加到創(chuàng)建的類。 如果作為方法參數(shù)給出的String為null或?yàn)榭?#xff0c;則此方法返回true。 否則,此方法返回false。
  • 向創(chuàng)建的類添加一個(gè)專用的passwordsAreValid(Object value,ConstraintValidatorContext context)方法。 如果密碼字段有效,則此方法返回true,否則返回false。 此方法采用以下兩個(gè)方法參數(shù):
  • 第一個(gè)方法參數(shù)是經(jīng)過驗(yàn)證的對象。
  • 第二個(gè)方法參數(shù)是ConstraintValidatorContext對象。
  • 通過執(zhí)行以下步驟來實(shí)現(xiàn)passwordsAreValid()方法:
  • 通過調(diào)用ValidatorUtil類的getFieldValue()方法獲取密碼字段的值。 將驗(yàn)證的對象和密碼字段的名稱作為方法參數(shù)傳遞。
  • 如果密碼字段的值為空或?yàn)閚ull,請通過調(diào)用ValidatorUtil類的addValidationError()方法來添加驗(yàn)證錯(cuò)誤。 將密碼字段的名稱和ConstraintValidatorContext對象作為方法參數(shù)傳遞。
  • 通過調(diào)用ValidatorUtil類的getFieldValue()方法獲取passwordVerification字段的值。 將驗(yàn)證的對象和密碼驗(yàn)證字段的名稱作為方法參數(shù)傳遞。
  • 如果密碼驗(yàn)證字段的值為空或?yàn)閚ull,請通過調(diào)用ValidatorUtil類的addValidationError()方法來添加驗(yàn)證錯(cuò)誤。 將密碼驗(yàn)證字段的名稱和ConstraintValidatorContext對象作為方法參數(shù)傳遞。
  • 如果發(fā)現(xiàn)驗(yàn)證錯(cuò)誤,則返回false。 否則返回true。
  • 將ConstraintValidator接口的isValid(Object value,ConstraintValidatorContext上下文)方法添加到驗(yàn)證器類,并通過以下步驟實(shí)現(xiàn)它:
  • 通過調(diào)用ConstraintValidatorContext接口的disableDefaultConstraintViolation()方法來禁用默認(rèn)錯(cuò)誤消息。
  • 向該方法添加一個(gè)try-catch結(jié)構(gòu),并捕獲所有檢查的異常。 如果引發(fā)了檢查的異常,請捕獲該異常并將其包裝在RuntimeException中 。 這是必需的,因?yàn)镃onstraintValidator接口的isValid()方法無法引發(fā)已檢查的異常,請按照以下步驟實(shí)現(xiàn)try塊:
  • 通過調(diào)用ValidatorUtil類的getFieldValue()方法來獲取驗(yàn)證觸發(fā)器字段的值。 將驗(yàn)證對象和驗(yàn)證觸發(fā)器字段的名稱作為方法參數(shù)傳遞。
  • 如果驗(yàn)證觸發(fā)器字段的值為null,則調(diào)用passwordFieldsAreValid()方法并將驗(yàn)證對象和ConstraintValidatorContext對象作為方法參數(shù)傳遞。 返回此方法返回的布爾值。
  • 如果驗(yàn)證觸發(fā)器字段的值不為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();} }

    其次,我們必須創(chuàng)建驗(yàn)證器類,以驗(yàn)證帶有@PasswordsNotEqual注釋的類。 我們可以按照以下步驟進(jìn)行操作:

  • 創(chuàng)建一個(gè)PasswordsNotEqualValidator類,并實(shí)現(xiàn)ConstraintValidator接口。 ConstraintValidator接口定義兩個(gè)類型參數(shù),下面將對其進(jìn)行描述:
  • 第一個(gè)類型參數(shù)是注釋類型。 將此類型參數(shù)的值設(shè)置為PasswordsNotEqual 。
  • 第二個(gè)類型參數(shù)是可以由驗(yàn)證器驗(yàn)證的元素的類型。 將此類型參數(shù)的值設(shè)置為Object (我們可以將其設(shè)置為RegistrationForm,但是使用Object類型可確保我們的驗(yàn)證器不限于此示例應(yīng)用程序)。
  • 在創(chuàng)建的類中添加一個(gè)專用的passwordFieldName字段,并將其類型設(shè)置為String 。
  • 在創(chuàng)建的類中添加一個(gè)專用的passwordVerificationFieldName字段,并將其類型設(shè)置為String 。
  • 將ConstraintValidator接口的initialize(PasswordsNotEqual ConsulantAnnotation)方法添加到驗(yàn)證器類,并通過以下步驟實(shí)現(xiàn)該方法:
  • 設(shè)置passwordFieldName字段的值。
  • 設(shè)置passwordVerificationFieldName字段的值。
  • 向創(chuàng)建的類添加一個(gè)專用的passwordsAreNotEqual(String password,String passwordVerification)方法。 如果作為方法參數(shù)給出的密碼和密碼驗(yàn)證不相等,則此方法返回true。 否則,此方法返回false。
  • 將ConstraintValidator接口的isValid(Object value,ConstraintValidatorContext上下文)方法添加到驗(yàn)證器類,并通過以下步驟實(shí)現(xiàn)它:
  • 通過調(diào)用ConstraintValidatorContext接口的disableDefaultConstraintViolation()方法來禁用默認(rèn)錯(cuò)誤消息。
  • 向該方法添加一個(gè)try-catch結(jié)構(gòu),并捕獲所有檢查的異常。 如果引發(fā)了檢查的異常,請捕獲該異常并將其包裝在RuntimeException中 。 這是必需的,因?yàn)镃onstraintValidator接口的isValid()方法無法引發(fā)已檢查的異常,請按照以下步驟實(shí)現(xiàn)try塊:
  • 通過調(diào)用ValidatorUtil類的getFieldValue()方法來獲取密碼字段的值。 將驗(yàn)證的對象和密碼字段的名稱作為方法參數(shù)傳遞。
  • 通過調(diào)用ValidatorUtil類的getFieldValue()方法來獲取密碼驗(yàn)證字段的值。 將驗(yàn)證的對象和密碼驗(yàn)證字段的名稱作為方法參數(shù)傳遞。
  • 通過調(diào)用passwordsAreNotEqual()方法來檢查密碼是否不相等。 將密碼和密碼驗(yàn)證作為方法參數(shù)傳遞。
  • 如果密碼和密碼驗(yàn)證不相等,請通過調(diào)用ValidatorUtil類的addValidationError()方法將驗(yàn)證錯(cuò)誤添加到密碼和密碼驗(yàn)證字段中。 返回false。
  • 如果密碼和密碼驗(yàn)證分別是,則返回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));} }

    這就對了。 現(xiàn)在,我們已經(jīng)實(shí)現(xiàn)了自定義驗(yàn)證約束。 讓我們找出如何呈現(xiàn)注冊頁面。

    渲染注冊頁面

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

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

    將用戶重定向到注冊頁面

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

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

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

    我們可以按照以下步驟實(shí)現(xiàn)此控制器:

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

    讓我們繼續(xù)前進(jìn),找出如何實(shí)現(xiàn)呈現(xiàn)注冊頁面的controller方法。

    實(shí)施控制器方法

    呈現(xiàn)注冊頁面的控制器方法具有一項(xiàng)重要職責(zé):創(chuàng)建表單對象并預(yù)填充其字段。 如果用戶正在創(chuàng)建“普通”用戶帳戶,則此控制器方法將創(chuàng)建一個(gè)空的表單對象。 另一方面,如果用戶正在使用社交登錄來創(chuàng)建用戶帳戶,則此控制器方法將使用所使用的SaaS API提供程序提供的信息來設(shè)置表單對象的字段值。 我們可以通過執(zhí)行以下步驟來實(shí)現(xiàn)呈現(xiàn)注冊頁面的controller方法:

  • 創(chuàng)建控制器類,并使用@Controller注釋對其進(jìn)行注釋。
  • 用@SessionAttributes批注注釋該類,并將其值設(shè)置為'user'。 我們使用此注釋來確保將名為“用戶”(我們的表單對象)的模型屬性存儲(chǔ)到會(huì)話中。
  • 將私有createRegistrationDTO()方法添加到該類。 此方法將Connection對象作為方法參數(shù),并返回RegistrationForm對象。 我們可以通過執(zhí)行以下步驟來實(shí)現(xiàn)此方法:
  • 創(chuàng)建一個(gè)新的RegistrationForm對象。
  • 如果作為方法參數(shù)給出的Connection對象不為null,則用戶正在使用社交登錄創(chuàng)建新的用戶帳戶。在這種情況下,我們必須
  • 通過調(diào)用Connection類的fetchUserProfile()方法來獲取UserProfile對象。 該對象包含SaaS API提供程序返回的用戶信息。
  • 將電子郵件,名字和姓氏設(shè)置為表單對象。 我們可以通過調(diào)用UserProfile類的方法來獲取此信息。
  • 通過調(diào)用Connection類的getKey()方法來獲取ConnectionKey對象。 該對象包含使用的社交登錄提供者的ID和提供者特定的用戶ID。
  • 通過執(zhí)行以下步驟,將登錄提供程序設(shè)置為表單對象:
  • 通過調(diào)用ConnectionKey類的getProviderId()方法獲取登錄提供程序。
  • 將getProviderId()方法返回的String轉(zhuǎn)換為大寫。
  • 通過調(diào)用社會(huì)媒體服務(wù)枚舉的nameOf()方法來獲取正確的值。 將登錄提供程序(大寫)作為方法參數(shù)傳遞(這意味著SocialMediaService枚舉的值取決于登錄提供程序ID)。
  • 將返回值設(shè)置為表單對象。
  • 返回表單對象。
  • 呈現(xiàn)注冊頁面的控制器方法稱為showRegistrationForm() 。 將此方法添加到控制器類并通過以下步驟實(shí)現(xiàn)它:
  • 使用@RequestMapping注釋對方法進(jìn)行注釋,并確保控制器方法處理發(fā)送到URL'/ user / register'的GET請求。
  • 添加一個(gè)WebRequest對象作為方法參數(shù)。 我們使用WebRequest作為方法參數(shù),因?yàn)樗刮覀兛梢暂p松訪問請求元數(shù)據(jù)。
  • 添加一個(gè)Model對象作為方法參數(shù)。
  • 通過調(diào)用ProviderSignInUtils類的靜態(tài)getConnection()方法來獲取Connection對象。 將WebRequest對象作為方法參數(shù)傳遞。 如果WebRequest對象不包含SaaS API提供程序元數(shù)據(jù)(表示用戶正在創(chuàng)建普通用戶帳戶),則此方法返回null。 如果找到元數(shù)據(jù),則此方法使用該信息創(chuàng)建Connection對象,并返回創(chuàng)建的對象。
  • 通過調(diào)用私有的createRegistrationDTO()方法來獲取表單對象。 將Connection對象作為方法參數(shù)傳遞。
  • 將表單對象設(shè)置為要建模的模型屬性,稱為“用戶”。
  • 返回注冊表單視圖的名稱(“ user / registrationForm”)。
  • UserController類的相關(guān)部分如下所示:

    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;} }

    接下來要做的就是創(chuàng)建JSP頁面。 讓我們繼續(xù)前進(jìn),找出實(shí)現(xiàn)方法。

    創(chuàng)建JSP頁面

    通過執(zhí)行以下步驟,我們可以創(chuàng)建包含注冊表單的JSP頁面:

  • 確保僅向匿名用戶顯示注冊表單。 我們可以按照以下步驟進(jìn)行操作:
  • 在Spring Security標(biāo)簽庫的authorize標(biāo)簽內(nèi)包裝登錄表單和社會(huì)登錄按鈕。
  • 將訪問屬性的值設(shè)置為isAnonymous() 。
  • 通過執(zhí)行以下步驟來實(shí)施注冊表:
  • 確保在提交注冊表后,將POST請求發(fā)送到URL'/ user / register'。
  • 將CSRF令牌添加到請求中 。 這是必需的,因?yàn)槲覀冊诒窘坛痰牡谝徊糠种袉⒂昧薙pring Security的CSRF保護(hù)。
  • 如果從表單對象中找到登錄提供程序,請將其作為隱藏字段添加到表單中。
  • 在表單中添加一個(gè)firstName字段,并確保顯示與firstName字段有關(guān)的驗(yàn)證錯(cuò)誤。
  • 在表單中添加一個(gè)lastName字段,并確保顯示與lastName字段有關(guān)的驗(yàn)證錯(cuò)誤。
  • 將電子郵件字段添加到表單,并確保顯示有關(guān)電子郵件字段的驗(yàn)證錯(cuò)誤。
  • 如果用戶正在創(chuàng)建普通用戶帳戶(表單對象的signInProvider字段的值為null),請按照下列步驟操作:
  • 在表單中添加一個(gè)密碼字段,并確保顯示有關(guān)密碼字段的驗(yàn)證錯(cuò)誤。
  • 將passwordVerification字段添加到表單,并確保顯示與passwordVerification字段有關(guān)的驗(yàn)證錯(cuò)誤。
  • 將提交按鈕添加到表單
  • 如果經(jīng)過身份驗(yàn)證的用戶訪問注冊頁面,請確保顯示幫助消息。 我們可以按照以下步驟進(jìn)行操作:
  • 將錯(cuò)誤消息區(qū)域包裝在Spring Security標(biāo)簽庫的authorize標(biāo)簽內(nèi) 。
  • 將訪問屬性的值設(shè)置為isAuthenticated() 。
  • 通過使用Spring標(biāo)記庫的message標(biāo)記獲取本地化的錯(cuò)誤消息。
  • 注意: Spring 3.2參考手冊提供了有關(guān)Spring JSP標(biāo)簽庫的form標(biāo)簽的更多信息。

    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. 我們可以按照以下步驟進(jìn)行操作:

  • 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. 通過執(zhí)行以下步驟來實(shí)現(xiàn)此方法:
  • 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. 我們可以通過執(zhí)行以下步驟來實(shí)現(xiàn)此方法:

  • 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. 我們可以按照以下步驟實(shí)現(xiàn)此類:

  • 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. 通過執(zhí)行以下步驟來實(shí)現(xiàn)構(gòu)造函數(shù):
  • 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 . 通過執(zhí)行以下步驟來實(shí)現(xiàn)此方法:
  • 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. 通過執(zhí)行以下步驟來實(shí)現(xiàn)此方法:
  • 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. 讓我們找出如何做到這一點(diǎn)。

    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:

    這篇博客文章告訴我們以下內(nèi)容:

    • 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

    創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    av中文字幕网址 | 狠狠色丁香久久婷婷综合五月 | 草久草久 | 久草男人天堂 | 日韩高清精品免费观看 | 成人在线观看资源 | 99色在线| 天天综合亚洲 | 在线亚洲欧美视频 | 看v片| 国产免费成人av | 超碰97人人干 | 九九九免费视频 | 色97在线| 丁香婷婷激情啪啪 | 六月丁香激情综合 | 亚洲精品福利在线 | 黄网站a| 丁香伊人网 | 精品视频久久 | 免费一级片观看 | 成人免费ⅴa | 日韩国产高清在线 | 极品久久久 | 五月天六月丁香 | 中文字幕在线观看你懂的 | 亚洲精品永久免费视频 | 深爱婷婷激情 | 天天综合网久久综合网 | 婷婷六月网 | 欧美激情视频一区二区三区免费 | 中文区中文字幕免费看 | 一区二区三区在线免费观看 | 久久你懂得| 国产亚洲欧美精品久久久久久 | 欧美日韩高清免费 | 激情六月婷婷久久 | 中文一区在线观看 | 国产中文字幕一区二区 | 成人欧美日韩国产 | 国产精品一区二区三区在线免费观看 | 免费色视频网站 | 国产精品岛国久久久久久久久红粉 | 精品久久久久久久久久久久久 | 色网av | 亚洲精品中文在线观看 | 欧美日韩在线电影 | 欧美黄色成人 | 最新中文字幕在线观看视频 | 免费日韩 精品中文字幕视频在线 | 成人欧美一区二区三区在线观看 | 91传媒在线播放 | 天天操天天玩 | 久久在线视频在线 | 五月综合激情婷婷 | 99福利片| 91在线视频观看 | 美女精品久久久 | 天天天干天天射天天天操 | 国内成人精品视频 | 欧美性生爱 | 久久久久久久久久网 | 日本公乱妇视频 | 亚洲国产精品小视频 | 狠狠干 狠狠操 | 国产精品二区三区 | 免费久草视频 | 激情五月色播五月 | 国产黄在线免费观看 | 欧美一级视频在线观看 | 久久久久久久影视 | 中文字幕高清av | 蜜臀久久99精品久久久酒店新书 | 成人性生交大片免费观看网站 | 中文字幕第一页在线 | 日日干日日 | 日本中文字幕在线视频 | 国产又粗又猛又黄视频 | 国产色综合 | 91精品婷婷国产综合久久蝌蚪 | 日韩和的一区二在线 | 国产99免费 | 免费在线观看av | 夜夜躁日日躁 | 亚洲激情六月 | 69国产盗摄一区二区三区五区 | 欧美在线观看视频一区二区 | 热久在线| 欧美动漫一区二区三区 | 国产99色| 国产精品免费小视频 | 久久久久激情视频 | 国产成人精品在线播放 | 国产九九九精品视频 | www黄免费| 天天爽夜夜爽人人爽一区二区 | 国产亚洲精品久久久久久久久久久久 | 精品久久久久久久久久久院品网 | 亚洲欧美日韩精品久久久 | 婷婷激情小说网 | 99国产精品 | 久热免费在线观看 | 少妇bbr搡bbb搡bbb| 久久激情综合 | 在线成人性视频 | 亚洲黄电影| 国产在线观看你懂的 | 黄色三级免费网址 | 天天插夜夜操 | 国产一区成人 | 国产一区 在线播放 | 国产91国语对白在线 | 人人天天夜夜 | 四虎在线免费观看 | 国产一区二区视频在线播放 | 免费国产一区二区视频 | 亚洲情感电影大片 | 国模精品一区二区三区 | 精品一区二区在线看 | 一级一片免费看 | 在线视频日韩一区 | 狠狠狠色丁香婷婷综合久久88 | 久草在线视频资源 | 日韩中文字幕网站 | 国产精品视频专区 | 麻花豆传媒一二三产区 | 中文字幕有码在线观看 | 一区二区三区不卡在线 | 亚洲综合丁香 | 国产女人40精品一区毛片视频 | 丁香5月婷婷久久 | 五月综合激情网 | av中文字幕在线看 | 久久久www成人免费毛片 | 久久久久久久久免费视频 | 欧美黑人xxxx猛性大交 | 最近中文字幕完整高清 | 久热免费 | 欧美性色19p | 久久久久久国产精品美女 | av亚洲产国偷v产偷v自拍小说 | 国产不卡视频 | 精品自拍av | 黄色avwww | 婷婷成人在线 | 日韩黄色免费电影 | 精品免费一区二区三区 | 日韩视频三区 | 国产高清免费观看 | 久久精品女人毛片国产 | 国产成人一区二区三区在线观看 | 亚洲国产精品va在线看黑人 | 激情 婷婷 | 欧美日韩中文国产一区发布 | 最新在线你懂的 | 91成年人视频 | 中文字幕中文字幕在线中文字幕三区 | 久草免费在线 | 免费在线激情视频 | 欧美黄网站 | 成人免费在线播放视频 | 91精品久久久久久综合乱菊 | 国产丝袜制服在线 | 久久在线免费观看视频 | 亚洲一区二区精品视频 | 国产高清不卡av | av免费线看 | av中文字幕剧情 | 中文字幕不卡在线88 | 99精品久久只有精品 | 一区二区在线影院 | 丁香婷婷深情五月亚洲 | av中文国产| 99精品国产兔费观看久久99 | 亚洲色图 校园春色 | 91一区二区三区久久久久国产乱 | 中文字幕影片免费在线观看 | 色婷婷丁香 | 久久精品成人热国产成 | 亚洲永久精品国产 | 国产色秀视频 | 精品国产一区二区三区日日嗨 | 日韩丝袜视频 | 成人av一级片 | 日韩3区 | 97精品国产97久久久久久粉红 | 中文免费 | 人人超碰免费 | 亚洲视频第一页 | 国产免费嫩草影院 | 97人人模人人爽人人喊网 | 久久精品中文字幕一区二区三区 | 77国产精品| 亚洲国产精品成人综合 | 黄网站app在线观看免费视频 | 天堂在线视频中文网 | 91豆花在线 | 亚洲国产精品视频在线观看 | 日韩,精品电影 | 一区二区三区免费在线 | 麻豆91网站 | 久久伦理电影 | 999国内精品永久免费视频 | 九九99| 色播激情五月 | 欧美一级免费黄色片 | 西西大胆免费视频 | 国产精品综合在线 | 久久超级碰 | 8x成人在线 | 天天爽天天碰狠狠添 | 国产色在线视频 | 欧美在线视频精品 | 美女网站在线免费观看 | 中文字幕xxxx | 97超碰资源网 | 久久免费观看视频 | 国产婷婷一区二区 | 国产黄影院色大全免费 | 亚洲国产成人精品在线 | 日本久久免费视频 | 国产一区视频在线观看免费 | 亚洲妇女av | 亚洲精品国精品久久99热一 | 国产最新视频在线 | 日韩二区三区在线 | 久久久免费电影 | 亚洲国内精品 | 国产高清精品在线 | 狠狠的日日 | 丰满少妇高潮在线观看 | 天天干,夜夜爽 | 蜜桃视频日韩 | 99久久99久久精品免费 | 久久男人影院 | 激情伊人五月天久久综合 | 色国产精品一区在线观看 | 久久官网 | 亚洲综合最新在线 | 国产青春久久久国产毛片 | 久久国产免费视频 | 亚洲三级精品 | 91大片成人网 | 黄色三级免费网址 | 国产精品久久电影观看 | 香蕉视频在线免费看 | 欧美十八 | 精品美女在线观看 | 婷婷中文字幕 | www99精品| av天天干 | av在线看片| 午夜国产影院 | 麻豆视频免费网站 | 国产美女精彩久久 | 在线免费av网 | 中文字幕免费在线看 | 国产精品福利小视频 | 91精品国产高清自在线观看 | 欧美性另类 | 久久九九国产视频 | 欧美地下肉体性派对 | 欧美激情xxxx | 国产一级二级在线观看 | 日韩成人中文字幕 | 久草网站在线观看 | 久久久久免费观看 | 国产 欧美 在线 | 免费一级毛毛片 | 久草在线官网 | 国产精品精品久久久久久 | 日韩系列在线观看 | 中文字幕免费成人 | 欧美激情视频一二三区 | 久久不色 | 一本一道久久a久久综合蜜桃 | 亚洲综合色av | 免费成人黄色 | 99精品在线 | 黄色www| 福利视频区 | 亚洲午夜av久久乱码 | 欧美激情第八页 | 国产精品女人久久久久久 | 99精品免费在线 | 亚洲精品国产精品国自产观看 | av中文字幕亚洲 | av中文在线 | 91天堂素人约啪 | 国产成人精品亚洲精品 | 一区 二区电影免费在线观看 | 69视频永久免费观看 | 久久激情视频免费观看 | 色婷婷av在线 | 国精产品999国精产品岳 | 97国产一区二区 | 成人动态视频 | 欧美日韩电影在线播放 | 亚洲播放一区 | 日韩高清免费电影 | 亚洲成人999| 西西4444www大胆视频 | 又爽又黄又无遮挡网站动态图 | 高清视频一区 | 精品高清视频 | 91麻豆传媒| 久久久免费毛片 | 国产二区视频在线观看 | 久久久精品日本 | 91片在线观看 | 激情综合站 | 99国产精品一区二区 | 国产不卡在线观看 | 欧美视频网址 | 精产嫩模国品一二三区 | 国产美腿白丝袜足在线av | 日韩毛片一区 | 亚洲精品在线二区 | 欧美日韩有码 | 91视频三区 | 国产精品免费看久久久8精臀av | 色婷婷婷| 丁香5月婷婷久久 | 日韩在线视频国产 | 精品亚洲在线 | 国产精品久久久久久久久费观看 | 久久在线观看视频 | 黄色免费网站 | 日本黄色免费电影网站 | 摸阴视频| 狠狠的操| 91免费网站在线观看 | 一个色综合网站 | 在线播放亚洲 | 五月天久久婷婷 | 婷五月天激情 | 成年人免费在线观看网站 | 欧美在线视频a | 天天搞天天 | 欧美激情视频一区二区三区 | 日韩在线字幕 | 黄色国产成人 | 一区二区三区日韩视频在线观看 | 午夜精品一区二区三区免费视频 | 国产精品6999成人免费视频 | 日韩欧美网站 | 欧美日韩国产一区二区在线观看 | 国产一级片久久 | 国产精品成人一区二区三区 | 欧美另类巨大 | 国产精品免费观看视频 | 成人少妇影院yyyy | 国产综合福利在线 | 一区二区三区国 | 国产成人av | 中文字幕在线观看免费观看 | 能在线看的av | 色偷偷888欧美精品久久久 | 一区二区三区在线电影 | 国产成人综合在线观看 | 国产色啪| 久久国产精品视频 | 国产 日韩 中文字幕 | 国产色a在线观看 | 欧美人交a欧美精品 | 91福利视频免费观看 | 欧美色婷 | 免费人人干| 中文字幕色综合网 | 久久国产精品色婷婷 | 制服丝袜亚洲 | 欧美在线观看视频 | 久久成人在线 | 欧美性生活一级片 | www久久九 | 国产成人不卡 | 亚洲一区视频免费观看 | 日本 在线 视频 中文 有码 | 国产精品综合久久 | 久草爱| 久久黄页 | 国产视频在线看 | 欧美高清成人 | 在线国产视频一区 | 在线观看亚洲成人 | 深夜福利视频在线观看 | 少妇自拍av | 日韩精品无码一区二区三区 | 国产精品久久影院 | 黄色网址在线播放 | 天天做日日做天天爽视频免费 | 97韩国电影 | 18久久久 | 麻豆观看 | 91九色porny蝌蚪主页 | 超碰夜夜| 99视频在线精品免费观看2 | 又黄又爽又刺激的视频 | 操久久网| 99久久99视频 | 少妇性色午夜淫片aaaze | 欧美日韩一区二区三区在线免费观看 | av女优中文字幕在线观看 | 毛片久久久 | 97在线观看视频国产 | 人人看黄色| 日韩午夜在线观看 | 黄色av电影一级片 | 国产精品美女久久久久久久网站 | 欧美黄网站 | jizz欧美性9 国产一区高清在线观看 | 久久久久久久久久免费视频 | 亚洲黄色片在线 | 国产精品久久久久久久久久免费 | 天天av在线播放 | 久久久久国产一区二区三区 | 99精品视频在线观看 | 天天操天天色综合 | av解说在线 | 欧美一级在线观看视频 | 国产资源在线免费观看 | 国产二区视频在线观看 | 免费视频久久久久久久 | 亚洲成a人片在线www | 欧美日韩不卡在线观看 | 色97在线| 欧美中文字幕久久 | 日韩在线第一 | 久草电影在线观看 | 手机成人av | 91视频91自拍 | 日韩欧美在线播放 | 亚洲精品黄网站 | 精品在线二区 | 亚洲精品久久久久久中文传媒 | 国产精品久久二区 | 亚洲影视资源 | 午夜性盈盈| 午夜视频在线观看欧美 | 在线免费观看欧美日韩 | 国产精品第一页在线 | 亚洲精品国精品久久99热一 | 激情伊人 | 黄色在线观看污 | 日韩中字在线观看 | 亚洲精品成人av在线 | 欧美精品久久久久久久久久白贞 | 成人久久久久久久久 | 日韩欧美国产激情在线播放 | 国产色婷婷在线 | 五月天婷亚洲天综合网精品偷 | 黄色小说视频网站 | 久久久亚洲国产精品麻豆综合天堂 | 日韩视频精品在线 | 91九色蝌蚪视频 | 91精品啪在线观看国产81旧版 | 久草在线视频精品 | 久久久影院一区二区三区 | 久久久久久毛片精品免费不卡 | 中文在线√天堂 | 国产91在线播放 | 久久夜色精品国产欧美一区麻豆 | 国产一区二区三区 在线 | 激情 一区二区 | 欧美日韩1区 | 成人超碰在线 | 亚洲国产精品人久久电影 | 天天射天天爱天天干 | 又色又爽又黄高潮的免费视频 | 精品久久久久久国产偷窥 | 亚洲天堂激情 | 黄色的视频网站 | 国产精品综合av一区二区国产馆 | 最新不卡av | 热久在线| 久久精品三级 | 欧美久久久久久久久久 | 亚洲精品电影在线 | 亚洲午夜av久久乱码 | 99中文字幕| 日韩毛片在线免费观看 | 国产精品第一页在线 | 国产精品麻豆一区二区三区 | 日韩字幕在线观看 | 涩涩网站在线 | 中文永久免费观看 | 就要干b| 在线播放第一页 | 欧美成人xxxxx | 久久久精品国产一区二区电影四季 | 久久久夜色 | 中文字幕制服丝袜av久久 | av在线播放快速免费阴 | 国产精品久久久久久久久久久不卡 | 精品人人人人 | 人人模人人爽 | 婷婷丁香在线 | 97电院网手机版 | 久久久久久综合 | 免费看国产视频 | 五月综合色婷婷 | 中文字幕视频 | 久久欧洲视频 | 久久老司机精品视频 | 韩日三级av | 免费看黄视频 | 亚洲 欧美 综合 在线 精品 | 亚洲最大在线视频 | 欧美一二区在线 | 在线观看日韩一区 | 国产精品1区2区3区在线观看 | 日韩午夜精品 | 国产精品成人免费 | 少妇bbr搡bbb搡bbb | 国产精品麻豆果冻传媒在线播放 | 日韩av成人| 国产在线观看中文字幕 | 久久这里只有精品首页 | 99久久久| 欧美日韩视频在线观看免费 | 免费福利在线视频 | 久久这里只有精品1 | 在线天堂中文www视软件 | 欧美日韩国产精品爽爽 | 久久av伊人 | 日韩在线精品 | 免费在线黄网 | 国产亚洲aⅴaaaaaa毛片 | 国产做a爱一级久久 | 麻花豆传媒一二三产区 | 亚洲永久精品在线观看 | 免费三级黄色片 | 99视频网址| 久久久亚洲电影 | 国产精品久久久久久久久久久久 | 一区二区国产精品 | 国产视频手机在线 | 国产精品久久中文字幕 | 91精品久久久久久久99蜜桃 | 亚洲免费不卡 | 亚洲永久国产精品 | 热久久99这里有精品 | 黄色电影小说 | 精品久久福利 | 91精品天码美女少妇 | 成人亚洲免费 | 国产一区二区高清不卡 | 福利一区在线 | 亚洲日本激情 | 日韩欧美国产精品 | av在线网站观看 | 日韩av手机在线观看 | 成人h视频在线播放 | 在线视频 你懂得 | 国产无遮挡又黄又爽在线观看 | 久久你懂的 | 国产1区2 | a视频免费 | 日韩免费视频观看 | 婷婷久操| 日韩二区在线 | 天天操人人要 | 二区三区在线视频 | 精品在线观看一区二区 | 免费日韩 精品中文字幕视频在线 | 98超碰人人 | 久久久久久久久黄色 | 热久久这里只有精品 | 天堂av观看 | 一区二区三区日韩在线观看 | 91av视频在线播放 | 五月婷婷六月丁香激情 | 精品久久久久久久久中文字幕 | 亚洲一区二区视频在线播放 | 久久久www成人免费毛片 | 日韩美在线观看 | av网站有哪些 | 亚洲一区网 | 91亚洲精品久久久中文字幕 | 欧美性精品 | 91一区在线观看 | 在线小视频你懂的 | 中文字幕九九 | 精品免费国产一区二区三区四区 | 国产午夜精品一区二区三区嫩草 | 婷婷精品 | www在线观看视频 | 国产色网站 | 在线网站黄 | 欧美影院久久 | 欧美精品久久久久 | av成人资源| 亚洲欧洲在线视频 | 免费观看午夜视频 | 欧美网址在线观看 | 天天狠狠干 | 97国产精品亚洲精品 | 成人黄色资源 | 久久国产精品免费一区 | 免费 在线 中文 日本 | 日韩精品一区二区三区第95 | 天天搞天天干 | 香蕉久草 | 久久久久久久久久影视 | 久久国产电影院 | 久久资源在线 | 精品视频一区在线观看 | 免费看污的网站 | 丁香五月亚洲综合在线 | 亚洲欧美视频在线播放 | 99国产在线视频 | 久久国产精品久久久久 | 国产剧情一区 | 91av亚洲 | 久久人人爽人人片av | 91九色国产在线 | 97超碰免费在线观看 | 在线观看成人网 | 亚洲欧美视频在线播放 | 欧美精品久久久久久久免费 | 在线91网 | 高潮毛片无遮挡高清免费 | 日韩高清免费无专码区 | 国产欧美精品一区二区三区 | 国产中文字幕视频在线观看 | 不卡视频国产 | 亚洲成av人片一区二区梦乃 | 成年人在线| 99色视频 | 99欧美视频 | 一本一道久久a久久精品蜜桃 | 天天操天天射天天插 | 国产中文字幕网 | 月丁香婷婷| 色99之美女主播在线视频 | 国产免费观看视频 | 午夜18视频在线观看 | 久久久久国产一区二区 | 激情一区二区三区欧美 | 久久久国产一区二区三区四区小说 | 一级免费黄视频 | 爱情影院aqdy鲁丝片二区 | 日本中文字幕系列 | 日本精品视频在线观看 | 国产专区一 | 91黄视频在线 | 久久这里 | 欧美日韩中文字幕视频 | 91色在线观看 | 亚洲最新精品 | 免费看黄色毛片 | 最新午夜电影 | 久久久视频在线 | 伊人伊成久久人综合网小说 | 成人久久亚洲 | 免费在线观看av片 | 亚洲日本韩国一区二区 | 人人澡超碰碰 | 久草在线99 | 久久久久久免费 | 午夜精品久久久久久久久久久久久久 | 日日干 天天干 | 国产高清久久 | 日韩精品一区二区电影 | 黄色av网站在线观看免费 | 成人av一区二区在线观看 | 久久精品欧美视频 | 国产高清视频免费在线观看 | 欧美日韩免费在线观看视频 | 国产高清视频免费最新在线 | 亚洲精品456在线播放 | 日本h视频在线观看 | 97日日| 成人黄色影片在线 | 国产裸体永久免费视频网站 | 国产成人精品电影久久久 | 日韩在线播放欧美字幕 | 福利视频精品 | 日韩三级免费 | 亚洲精品国产成人av在线 | 欧美久久久一区二区三区 | 日韩在线观看免费 | 久久久久久久久久久影视 | 欧美日韩一区二区三区在线免费观看 | 国产主播大尺度精品福利免费 | 国产成人精品一区二三区 | 国产成人综合精品 | 九九视频这里只有精品 | 欧美日韩一区二区视频在线观看 | 成人亚洲免费 | 久久99日韩 | 日本精品久久久久中文字幕5 | 超碰在线观看99 | 99 精品 在线 | 欧美精品视 | 成人黄色电影在线观看 | 亚洲性视频 | 免费高清看电视网站 | av成人在线观看 | 日韩亚洲国产中文字幕 | 人人澡超碰碰97碰碰碰软件 | 久久都是精品 | 久久久久国产一区二区三区 | 麻豆综合网 | 黄色软件网站在线观看 | 成人网444ppp | 视频一区二区三区视频 | 国产高清久久久 | 亚洲高清激情 | 一级黄色片在线播放 | 国产人免费人成免费视频 | 日韩高清国产精品 | 国产午夜影院 | 日韩一级电影在线 | 视频国产在线 | 青青河边草免费直播 | 日韩一区二区三区高清在线观看 | 国产黄在线观看 | 99久久综合精品五月天 | 精品久久久久久国产偷窥 | 国产一区二区精品在线 | 国产精品1区2区在线观看 | 国产福利在线 | 日韩免费观看一区二区三区 | 日韩精品影视 | 久久免费视频在线观看30 | 激情偷乱人伦小说视频在线观看 | 最近中文字幕在线中文高清版 | 久久99热久久99精品 | 九九热免费精品视频 | 久久九九精品 | 亚洲春色奇米影视 | 国产123av| 国产黄色免费 | 五月天激情开心 | 亚洲最大免费成人网 | 91麻豆精品国产91久久久使用方法 | 免费福利片2019潦草影视午夜 | 日本特黄一级 | 国产九九热视频 | 国产小视频免费观看 | 国产免费一区二区三区最新6 | 欧美日韩一区二区三区在线免费观看 | 久久精品视频中文字幕 | 久久综合射 | 国产免费观看久久 | 国产成人亚洲在线观看 | 成人午夜免费剧场 | 成人电影毛片 | 永久免费毛片 | 91av资源网 | 久久久高清视频 | 日韩欧美xxxx | 日韩中文在线电影 | japanesefreesex中国少妇 | 欧美性生活免费 | 在线免费av电影 | 欧美日韩在线播放 | 视频在线观看国产 | 亚洲午夜精品一区 | 男女靠逼app | 国产一级免费视频 | 婷婷色资源 | 97超碰总站 | av综合av | 狠狠躁18三区二区一区ai明星 | 日本久热 | 久久国产精品二国产精品中国洋人 | 夜色资源站wwwcom | 中文字幕日韩精品有码视频 | 亚洲国内精品在线 | 在线播放一区二区三区 | 国产精品美女久久久网av | 97人人超碰在线 | 欧美精品乱码久久久久久按摩 | 久一网站| 国产精品剧情 | 免费影视大全推荐 | 91视频这里只有精品 | 国产在线 一区二区三区 | 亚洲视频axxx | 久草在线国产 | 日本激情动作片免费看 | 国产日产精品一区二区三区四区的观看方式 | 久久精品视频在线观看免费 | 亚洲国产偷 | 天天插夜夜操 | 伊人色综合久久天天网 | 国产色小视频 | 天天爱天天草 | 99久久精品无码一区二区毛片 | 波多野结衣在线播放一区 | 国产精品久久久久久久久久久杏吧 | 激情久久五月 | 伊人色综合久久天天网 | 国产精品 日韩 欧美 | 精品国产乱码久久久久久浪潮 | 日韩理论在线观看 | 91精品国产综合久久福利不卡 | 99热在线国产精品 | 久久精品视频免费播放 | 国产成人精品一区一区一区 | 中国一级片在线观看 | 国产在线自 | 黄色99视频 | 免费av在线播放 | www天天操| 成年人网站免费在线观看 | 婷婷丁香色 | 激情综合色图 | 成人午夜免费剧场 | 日韩丝袜 | 亚洲精选视频在线 | 日韩一区在线播放 | 国产精品久久久av久久久 | 亚洲精品国产品国语在线 | 国产aaa免费视频 | 午夜视频欧美 | 色综合咪咪久久网 | 麻豆 videos | 国产成人精品在线观看 | 综合网五月天 | 中文在线字幕免 | 日本黄色免费观看 | 五月综合婷| 成人 国产 在线 | 在线a亚洲视频播放在线观看 | 91香蕉视频好色先生 | 国产做aⅴ在线视频播放 | av成人在线电影 | 久久超碰免费 | 99热官网 | 日韩欧美中文 | 亚洲1区在线 | 久久天天躁 | 18久久久 | 91免费的视频在线播放 | 97精品国产 | 免费黄色在线网站 | 五月综合色婷婷 | 精品亚洲免费视频 | 欧美精品你懂的 | 国产 视频 高清 免费 | 日韩精品高清不卡 | 黄色性av | 一区二区三区在线播放 | av免费黄色 | 亚洲动漫在线观看 | 一 级 黄 色 片免费看的 | 久草精品视频在线播放 | 国产一区免费视频 | 国产精品一区久久久久 | 国内外成人在线视频 | 免费观看福利视频 | 日日夜夜精品 | 国产99视频在线观看 | 久久99视频| 精品国产一区二区三区不卡 | 欧美一级小视频 | 欧美最新另类人妖 | 日韩毛片在线免费观看 | 久久国产亚洲精品 | 亚洲人成人在线 | 黄色大片日本免费大片 | 国产又粗又硬又爽视频 | 国产精品国产三级国产专区53 | 最近中文字幕mv免费高清在线 | 91麻豆精品国产91久久久使用方法 | 日日夜夜精品免费观看 | 又色又爽的网站 | 久久精品视频观看 | 麻豆91精品视频 | 人人爽人人爽人人爽人人爽 | 97av在线视频 | 日韩精品一区二区三区免费观看 | 成人av影视观看 | 国产四虎影院 | 99视频这里有精品 | 香蕉网站在线观看 | 亚洲婷婷综合色高清在线 | 一区二区三区观看 | 91最新中文字幕 | 国产精品九九九九九九 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 国产韩国日本高清视频 | а天堂中文最新一区二区三区 | 国产69精品久久app免费版 | 久久久国产精品久久久 | 免费性网站 | 97碰碰精品嫩模在线播放 | 亚洲欧洲精品视频 | 久久精品播放 | 一区 二区电影免费在线观看 | 成人网在线免费视频 | 亚洲第一久久久 | 国产精品大片 | 欧美日本中文字幕 | 激情综合网五月激情 | 久久欧美综合 | 久草在线视频看看 | 国产在线视频导航 | 精品久久久久一区二区国产 | 91av视屏| 91超级碰| 久久久久久久18 | 黄色com| 欧美性色黄 | 一级黄色免费网站 | 六月婷婷久香在线视频 | 国产精品美乳一区二区免费 | 天天综合网天天 | 国产精品一区二区三区在线免费观看 | 成人a级免费视频 | 人人藻人人澡人人爽 | 黄色小网站在线观看 | 精品久久久久久久久久久久久久久久久久 | 国产成人精品综合久久久 | 在线视频 国产 日韩 | 免费激情在线电影 | 国产在线免费av | 久久久久久国产精品 | 国产美女免费观看 | 国产一级二级av | 天天拍天天干 | 天天干视频在线 | 日韩精品欧美视频 | 99精品国自产在线 | 亚洲激情在线观看 | 三级免费黄| 国产成人精品一二三区 | 夜夜夜精品 | 天天干中文字幕 | 亚洲精品久久久久久国 | 中文字幕一区二区三区在线观看 | 美女视频黄网站 | 视频在线精品 | 韩日精品在线 | 午夜视频欧美 | 黄色免费看片网站 | 91成人在线网站 | 在线观看免费 | 久久综合99 | av网站地址 | 在线观看中文字幕一区 | 久久在视频| 黄色av影视 | 婷婷色网站 | 操操色| 国产精品中文字幕在线观看 | 成年人黄色大片在线 | 婷婷丁香社区 | 9色在线视频 | 二区三区视频 | 久久久久久久久艹 | 国产视频一区精品 | 国产香蕉久久 | 波多在线视频 | 天天操天天摸天天射 | 亚洲小视频在线观看 | 伊人夜夜 | 国产午夜精品一区二区三区欧美 | 久久你懂的 | 国产成人久久久77777 | 激情综合网婷婷 | 亚洲视频 视频在线 | 欧美一区二区三区在线看 | 亚洲一区网站 | 成人免费视频免费观看 | 色婷五月天 | 四虎国产永久在线精品 | 久久久这里有精品 | 91喷水| 亚洲免费不卡 | 91精品国产欧美一区二区成人 | 99久久久久 | 91丨九色丨高潮丰满 | 日韩一区二区三区观看 | 在线观看你懂的网址 | 久av在线 | 黄色www在线观看 | 91精品国产乱码在线观看 | 麻豆久久精品 | 国产小视频在线观看免费 | 国产人成一区二区三区影院 | 精品一区二区av | 久久精品—区二区三区 | 欧美国产精品久久久久久免费 | 欧美黑吊大战白妞欧美 | 欧美性视频网站 | 99精品影视| 91黄在线看 | 99产精品成人啪免费网站 | 成人av电影免费在线观看 | 国产精品日韩在线观看 | 91精品在线播放 | 成人avav| 成人av一二三区 | 日躁夜躁狠狠躁2001 |