国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|JavaScript|易語言|

服務器之家 - 編程語言 - Java教程 - Spring Security架構以及源碼詳析

Spring Security架構以及源碼詳析

2021-05-08 10:54JadePeng Java教程

這篇文章主要給大家介紹了關于Spring Security架構以及源碼的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

前言

現在流行的通用授權框架有apache的shiro和spring家族的spring security,在涉及今天的微服務鑒權時,需要利用我們的授權框架搭建自己的鑒權服務,今天總理了spring security。

spring security 主要實現了authentication(認證,解決who are you? ) 和 access control(訪問控制,也就是what are you allowed to do?,也稱為authorization)。spring security在架構上將認證與授權分離,并提供了擴展點。

核心對象

主要代碼在spring-security-core包下面。要了解spring security,需要先關注里面的核心對象。

securitycontextholder, securitycontext 和 authentication

securitycontextholder 是 securitycontext的存放容器,默認使用threadlocal 存儲,意味securitycontext在相同線程中的方法都可用。

securitycontext主要是存儲應用的principal信息,在spring security中用authentication 來表示。

獲取principal:

?
1
2
3
4
5
6
7
object principal = securitycontextholder.getcontext().getauthentication().getprincipal();
 
if (principal instanceof userdetails) {
string username = ((userdetails)principal).getusername();
} else {
string username = principal.tostring();
}

在spring security中,可以看一下authentication定義:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface authentication extends principal, serializable {
 collection<? extends grantedauthority> getauthorities();
 /**
 * 通常是密碼
 */
 object getcredentials();
 /**
 * stores additional details about the authentication request. these might be an ip
 * address, certificate serial number etc.
 */
 object getdetails();
 
 /**
 * 用來標識是否已認證,如果使用用戶名和密碼登錄,通常是用戶名
 */
 object getprincipal();
 /**
 * 是否已認證
 */
 boolean isauthenticated();
 void setauthenticated(boolean isauthenticated) throws illegalargumentexception;
}

在實際應用中,通常使用usernamepasswordauthenticationtoken:

?
1
2
3
4
5
public abstract class abstractauthenticationtoken implements authentication,
 credentialscontainer {
 }
public class usernamepasswordauthenticationtoken extends abstractauthenticationtoken {
}

一個常見的認證過程通常是這樣的,創建一個usernamepasswordauthenticationtoken,然后交給authenticationmanager認證(后面詳細說明),認證通過則通過securitycontextholder存放authentication信息。

?
1
2
3
4
5
usernamepasswordauthenticationtoken authenticationtoken =
 new usernamepasswordauthenticationtoken(loginvm.getusername(), loginvm.getpassword());
 
authentication authentication = this.authenticationmanager.authenticate(authenticationtoken);
securitycontextholder.getcontext().setauthentication(authentication);

userdetails與userdetailsservice

userdetails 是spring security里的一個關鍵接口,他用來表示一個principal。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface userdetails extends serializable {
 /**
 * 用戶的授權信息,可以理解為角色
 */
 collection<? extends grantedauthority> getauthorities();
 /**
 * 用戶密碼
 *
 * @return the password
 */
 string getpassword();
 /**
 * 用戶名
 * */
 string getusername();
 boolean isaccountnonexpired();
 boolean isaccountnonlocked();
 boolean iscredentialsnonexpired();
 boolean isenabled();
}

userdetails提供了認證所需的必要信息,在實際使用里,可以自己實現userdetails,并增加額外的信息,比如email、mobile等信息。

在authentication中的principal通常是用戶名,我們可以通過userdetailsservice來通過principal獲取userdetails:

?
1
2
3
public interface userdetailsservice {
 userdetails loaduserbyusername(string username) throws usernamenotfoundexception;
}

grantedauthority

在userdetails里說了,grantedauthority可以理解為角色,例如 role_administrator or role_hr_supervisor。

小結

  • securitycontextholder, 用來訪問 securitycontext.
  • securitycontext, 用來存儲authentication .
  • authentication, 代表憑證.
  • grantedauthority, 代表權限.
  • userdetails, 用戶信息.
  • userdetailsservice,獲取用戶信息.

authentication認證

authenticationmanager

實現認證主要是通過authenticationmanager接口,它只包含了一個方法:

?
1
2
3
4
public interface authenticationmanager {
 authentication authenticate(authentication authentication)
 throws authenticationexception;
}

authenticate()方法主要做三件事:

  • 如果驗證通過,返回authentication(通常帶上authenticated=true)。
  • 認證失敗拋出authenticationexception
  • 如果無法確定,則返回null

authenticationexception是運行時異常,它通常由應用程序按通用方式處理,用戶代碼通常不用特意被捕獲和處理這個異常。

authenticationmanager的默認實現是providermanager,它委托一組authenticationprovider實例來實現認證。
authenticationprovider和authenticationmanager類似,都包含authenticate,但它有一個額外的方法supports,以允許查詢調用方是否支持給定authentication類型:

?
1
2
3
4
5
public interface authenticationprovider {
 authentication authenticate(authentication authentication)
 throws authenticationexception;
 boolean supports(class<?> authentication);
}

providermanager包含一組authenticationprovider,執行authenticate時,遍歷providers,然后調用supports,如果支持,則執行遍歷當前provider的authenticate方法,如果一個provider認證成功,則break。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public authentication authenticate(authentication authentication)
 throws authenticationexception {
 class<? extends authentication> totest = authentication.getclass();
 authenticationexception lastexception = null;
 authentication result = null;
 boolean debug = logger.isdebugenabled();
 
 for (authenticationprovider provider : getproviders()) {
 if (!provider.supports(totest)) {
 continue;
 }
 
 if (debug) {
 logger.debug("authentication attempt using "
  + provider.getclass().getname());
 }
 
 try {
 result = provider.authenticate(authentication);
 
 if (result != null) {
  copydetails(authentication, result);
  break;
 }
 }
 catch (accountstatusexception e) {
 prepareexception(e, authentication);
 // sec-546: avoid polling additional providers if auth failure is due to
 // invalid account status
 throw e;
 }
 catch (internalauthenticationserviceexception e) {
 prepareexception(e, authentication);
 throw e;
 }
 catch (authenticationexception e) {
 lastexception = e;
 }
 }
 
 if (result == null && parent != null) {
 // allow the parent to try.
 try {
 result = parent.authenticate(authentication);
 }
 catch (providernotfoundexception e) {
 // ignore as we will throw below if no other exception occurred prior to
 // calling parent and the parent
 // may throw providernotfound even though a provider in the child already
 // handled the request
 }
 catch (authenticationexception e) {
 lastexception = e;
 }
 }
 
 if (result != null) {
 if (erasecredentialsafterauthentication
  && (result instanceof credentialscontainer)) {
 // authentication is complete. remove credentials and other secret data
 // from authentication
 ((credentialscontainer) result).erasecredentials();
 }
 eventpublisher.publishauthenticationsuccess(result);
 return result;
 }
 
 // parent was null, or didn't authenticate (or throw an exception).
 if (lastexception == null) {
 lastexception = new providernotfoundexception(messages.getmessage(
  "providermanager.providernotfound",
  new object[] { totest.getname() },
  "no authenticationprovider found for {0}"));
 }
 prepareexception(lastexception, authentication);
 throw lastexception;
 }

從上面的代碼可以看出, providermanager有一個可選parent,如果parent不為空,則調用parent.authenticate(authentication)

authenticationprovider

authenticationprovider有多種實現,大家最關注的通常是daoauthenticationprovider,繼承于abstractuserdetailsauthenticationprovider,核心是通過userdetails來實現認證,daoauthenticationprovider默認會自動加載,不用手動配。

先來看abstractuserdetailsauthenticationprovider,看最核心的authenticate:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public authentication authenticate(authentication authentication)
 throws authenticationexception {
 // 必須是usernamepasswordauthenticationtoken
 assert.isinstanceof(usernamepasswordauthenticationtoken.class, authentication,
 messages.getmessage(
  "abstractuserdetailsauthenticationprovider.onlysupports",
  "only usernamepasswordauthenticationtoken is supported"));
 
 // 獲取用戶名
 string username = (authentication.getprincipal() == null) ? "none_provided"
 : authentication.getname();
 
 boolean cachewasused = true;
 // 從緩存獲取
 userdetails user = this.usercache.getuserfromcache(username);
 
 if (user == null) {
 cachewasused = false;
 
 try {
 // retrieveuser 抽象方法,獲取用戶
 user = retrieveuser(username,
  (usernamepasswordauthenticationtoken) authentication);
 }
 catch (usernamenotfoundexception notfound) {
 logger.debug("user '" + username + "' not found");
 
 if (hideusernotfoundexceptions) {
  throw new badcredentialsexception(messages.getmessage(
  "abstractuserdetailsauthenticationprovider.badcredentials",
  "bad credentials"));
 }
 else {
  throw notfound;
 }
 }
 
 assert.notnull(user,
  "retrieveuser returned null - a violation of the interface contract");
 }
 
 try {
 // 預先檢查,defaultpreauthenticationchecks,檢查用戶是否被lock或者賬號是否可用
 preauthenticationchecks.check(user);
 
 // 抽象方法,自定義檢驗
 additionalauthenticationchecks(user,
  (usernamepasswordauthenticationtoken) authentication);
 }
 catch (authenticationexception exception) {
 if (cachewasused) {
 // there was a problem, so try again after checking
 // we're using latest data (i.e. not from the cache)
 cachewasused = false;
 user = retrieveuser(username,
  (usernamepasswordauthenticationtoken) authentication);
 preauthenticationchecks.check(user);
 additionalauthenticationchecks(user,
  (usernamepasswordauthenticationtoken) authentication);
 }
 else {
 throw exception;
 }
 }
 
 // 后置檢查 defaultpostauthenticationchecks,檢查iscredentialsnonexpired
 postauthenticationchecks.check(user);
 if (!cachewasused) {
 this.usercache.putuserincache(user);
 }
 
 object principaltoreturn = user;
 if (forceprincipalasstring) {
 principaltoreturn = user.getusername();
 }
 
 return createsuccessauthentication(principaltoreturn, authentication, user);
 }

上面的檢驗主要基于userdetails實現,其中獲取用戶和檢驗邏輯由具體的類去實現,默認實現是daoauthenticationprovider,這個類的核心是讓開發者提供userdetailsservice來獲取userdetails以及 passwordencoder來檢驗密碼是否有效:

?
1
2
private userdetailsservice userdetailsservice;
private passwordencoder passwordencoder;

看具體的實現,retrieveuser,直接調用userdetailsservice獲取用戶:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
protected final userdetails retrieveuser(string username,
 usernamepasswordauthenticationtoken authentication)
 throws authenticationexception {
 userdetails loadeduser;
 
 try {
 loadeduser = this.getuserdetailsservice().loaduserbyusername(username);
 }
 catch (usernamenotfoundexception notfound) {
 if (authentication.getcredentials() != null) {
 string presentedpassword = authentication.getcredentials().tostring();
 passwordencoder.ispasswordvalid(usernotfoundencodedpassword,
  presentedpassword, null);
 }
 throw notfound;
 }
 catch (exception repositoryproblem) {
 throw new internalauthenticationserviceexception(
  repositoryproblem.getmessage(), repositoryproblem);
 }
 
 if (loadeduser == null) {
 throw new internalauthenticationserviceexception(
  "userdetailsservice returned null, which is an interface contract violation");
 }
 return loadeduser;
 }

再來看驗證:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected void additionalauthenticationchecks(userdetails userdetails,
 usernamepasswordauthenticationtoken authentication)
 throws authenticationexception {
 object salt = null;
 
 if (this.saltsource != null) {
 salt = this.saltsource.getsalt(userdetails);
 }
 
 if (authentication.getcredentials() == null) {
 logger.debug("authentication failed: no credentials provided");
 
 throw new badcredentialsexception(messages.getmessage(
  "abstractuserdetailsauthenticationprovider.badcredentials",
  "bad credentials"));
 }
 // 獲取用戶密碼
 string presentedpassword = authentication.getcredentials().tostring();
 // 比較passwordencoder后的密碼是否和userdetails的密碼一致
 if (!passwordencoder.ispasswordvalid(userdetails.getpassword(),
 presentedpassword, salt)) {
 logger.debug("authentication failed: password does not match stored value");
 
 throw new badcredentialsexception(messages.getmessage(
  "abstractuserdetailsauthenticationprovider.badcredentials",
  "bad credentials"));
 }
 }

小結:要自定義認證,使用daoauthenticationprovider,只需要為其提供passwordencoder和userdetailsservice就可以了。

定制 authentication managers

spring security提供了一個builder類authenticationmanagerbuilder,借助它可以快速實現自定義認證。

看官方源碼說明:

securitybuilder used to create an authenticationmanager . allows for easily building in memory authentication, ldap authentication, jdbc based authentication, adding userdetailsservice , and adding authenticationprovider's.

authenticationmanagerbuilder可以用來build一個authenticationmanager,可以創建基于內存的認證、ldap認證、 jdbc認證,以及添加userdetailsservice和authenticationprovider。

簡單使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@configuration
@enablewebsecurity
@enableglobalmethodsecurity(prepostenabled = true, securedenabled = true)
public class applicationsecurity extends websecurityconfigureradapter {
 public securityconfiguration(authenticationmanagerbuilder authenticationmanagerbuilder, userdetailsservice userdetailsservice,tokenprovider tokenprovider,corsfilter corsfilter, securityproblemsupport problemsupport) {
 this.authenticationmanagerbuilder = authenticationmanagerbuilder;
 this.userdetailsservice = userdetailsservice;
 this.tokenprovider = tokenprovider;
 this.corsfilter = corsfilter;
 this.problemsupport = problemsupport;
 }
 
 @postconstruct
 public void init() {
 try {
 authenticationmanagerbuilder
 .userdetailsservice(userdetailsservice)
 .passwordencoder(passwordencoder());
 } catch (exception e) {
 throw new beaninitializationexception("security configuration failed", e);
 }
 }
 
 @override
 protected void configure(httpsecurity http) throws exception {
 http
 .addfilterbefore(corsfilter, usernamepasswordauthenticationfilter.class)
 .exceptionhandling()
 .authenticationentrypoint(problemsupport)
 .accessdeniedhandler(problemsupport)
 .and()
 .csrf()
 .disable()
 .headers()
 .frameoptions()
 .disable()
 .and()
 .sessionmanagement()
 .sessioncreationpolicy(sessioncreationpolicy.stateless)
 .and()
 .authorizerequests()
 .antmatchers("/api/register").permitall()
 .antmatchers("/api/activate").permitall()
 .antmatchers("/api/authenticate").permitall()
 .antmatchers("/api/account/reset-password/init").permitall()
 .antmatchers("/api/account/reset-password/finish").permitall()
 .antmatchers("/api/profile-info").permitall()
 .antmatchers("/api/**").authenticated()
 .antmatchers("/management/health").permitall()
 .antmatchers("/management/**").hasauthority(authoritiesconstants.admin)
 .antmatchers("/v2/api-docs/**").permitall()
 .antmatchers("/swagger-resources/configuration/ui").permitall()
 .antmatchers("/swagger-ui/index.html").hasauthority(authoritiesconstants.admin)
 .and()
 .apply(securityconfigureradapter());
 }
}

授權與訪問控制

一旦認證成功,我們可以繼續進行授權,授權是通過accessdecisionmanager來實現的。框架有三種實現,默認是affirmativebased,通過accessdecisionvoter決策,有點像providermanager委托給authenticationproviders來認證。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void decide(authentication authentication, object object,
 collection<configattribute> configattributes) throws accessdeniedexception {
 int deny = 0;
 // 遍歷decisionvoter
 for (accessdecisionvoter voter : getdecisionvoters()) {
 // 投票
 int result = voter.vote(authentication, object, configattributes);
 
 if (logger.isdebugenabled()) {
 logger.debug("voter: " + voter + ", returned: " + result);
 }
 
 switch (result) {
 case accessdecisionvoter.access_granted:
 return;
 
 case accessdecisionvoter.access_denied:
 deny++;
 
 break;
 
 default:
 break;
 }
 }
 
 // 一票否決
 if (deny > 0) {
 throw new accessdeniedexception(messages.getmessage(
  "abstractaccessdecisionmanager.accessdenied", "access is denied"));
 }
 
 // to get this far, every accessdecisionvoter abstained
 checkallowifallabstaindecisions();
 }

來看accessdecisionvoter:

?
1
2
3
4
boolean supports(configattribute attribute);
boolean supports(class<?> clazz);
int vote(authentication authentication, s object,
 collection<configattribute> attributes);

object是用戶要訪問的資源,configattribute則是訪問object要滿足的條件,通常payload是字符串,比如role_admin 。所以我們來看下rolevoter的實現,其核心就是從authentication提取出grantedauthority,然后和configattribute比較是否滿足條件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public boolean supports(configattribute attribute) {
 if ((attribute.getattribute() != null)
 && attribute.getattribute().startswith(getroleprefix())) {
 return true;
 }
 else {
 return false;
 }
 }
 
public boolean supports(class<?> clazz) {
 return true;
 }
 
public int vote(authentication authentication, object object,
 collection<configattribute> attributes) {
 if(authentication == null) {
 return access_denied;
 }
 int result = access_abstain;
 
 // 獲取grantedauthority信息
 collection<? extends grantedauthority> authorities = extractauthorities(authentication);
 
 for (configattribute attribute : attributes) {
 if (this.supports(attribute)) {
 // 默認拒絕訪問
 result = access_denied;
 
 // attempt to find a matching granted authority
 for (grantedauthority authority : authorities) {
  // 判斷是否有匹配的 authority
  if (attribute.getattribute().equals(authority.getauthority())) {
  // 可訪問
  return access_granted;
  }
 }
 }
 }
 return result;
 }

這里要疑問,configattribute哪來的?其實就是上面applicationsecurity的configure里的。

web security 如何實現

web層中的spring security(用于ui和http后端)基于servlet filters,下圖顯示了單個http請求的處理程序的典型分層。

Spring Security架構以及源碼詳析

spring security通過filterchainproxy作為單一的filter注冊到web層,proxy內部的filter。

Spring Security架構以及源碼詳析

filterchainproxy相當于一個filter的容器,通過virtualfilterchain來依次調用各個內部filter

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public void dofilter(servletrequest request, servletresponse response,
  filterchain chain) throws ioexception, servletexception {
 boolean clearcontext = request.getattribute(filter_applied) == null;
 if (clearcontext) {
  try {
  request.setattribute(filter_applied, boolean.true);
  dofilterinternal(request, response, chain);
  }
  finally {
  securitycontextholder.clearcontext();
  request.removeattribute(filter_applied);
  }
 }
 else {
  dofilterinternal(request, response, chain);
 }
 }
 
 private void dofilterinternal(servletrequest request, servletresponse response,
  filterchain chain) throws ioexception, servletexception {
 
 firewalledrequest fwrequest = firewall
  .getfirewalledrequest((httpservletrequest) request);
 httpservletresponse fwresponse = firewall
  .getfirewalledresponse((httpservletresponse) response);
 
 list<filter> filters = getfilters(fwrequest);
 
 if (filters == null || filters.size() == 0) {
  if (logger.isdebugenabled()) {
  logger.debug(urlutils.buildrequesturl(fwrequest)
   + (filters == null ? " has no matching filters"
    : " has an empty filter list"));
  }
 
  fwrequest.reset();
 
  chain.dofilter(fwrequest, fwresponse);
 
  return;
 }
 
 virtualfilterchain vfc = new virtualfilterchain(fwrequest, chain, filters);
 vfc.dofilter(fwrequest, fwresponse);
 }
 
 private static class virtualfilterchain implements filterchain {
 private final filterchain originalchain;
 private final list<filter> additionalfilters;
 private final firewalledrequest firewalledrequest;
 private final int size;
 private int currentposition = 0;
 
 private virtualfilterchain(firewalledrequest firewalledrequest,
  filterchain chain, list<filter> additionalfilters) {
  this.originalchain = chain;
  this.additionalfilters = additionalfilters;
  this.size = additionalfilters.size();
  this.firewalledrequest = firewalledrequest;
 }
 
 public void dofilter(servletrequest request, servletresponse response)
  throws ioexception, servletexception {
  if (currentposition == size) {
  if (logger.isdebugenabled()) {
   logger.debug(urlutils.buildrequesturl(firewalledrequest)
    + " reached end of additional filter chain; proceeding with original chain");
  }
 
  // deactivate path stripping as we exit the security filter chain
  this.firewalledrequest.reset();
 
  originalchain.dofilter(request, response);
  }
  else {
  currentposition++;
 
  filter nextfilter = additionalfilters.get(currentposition - 1);
 
  if (logger.isdebugenabled()) {
   logger.debug(urlutils.buildrequesturl(firewalledrequest)
    + " at position " + currentposition + " of " + size
    + " in additional filter chain; firing filter: '"
    + nextfilter.getclass().getsimplename() + "'");
  }
 
  nextfilter.dofilter(request, response, this);
  }
 }
 }

參考

https://spring.io/guides/topicals/spring-security-architecture/

https://docs.spring.io/spring-security/site/docs/5.0.5.release/reference/htmlsingle/#overall-architecture

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。

原文鏈接:http://www.cnblogs.com/xiaoqi/p/spring-security.html

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 亚洲欧美日韩精品久久亚洲区 | 亚洲国产精品自拍 | 久久99操 | 国产一区二区av | 成人午夜免费视频 | 免费在线黄视频 | 91无吗| 免费一级毛片网站 | av在线一区二区三区 | 婷婷久久综合 | 久久久一区二区三区 | 国产一级成人 | 操操操小说 | 香蕉福利视频 | 亚洲国产一区视频 | 成人亚洲精品 | 黄在线免费观看 | 欧美成人精品一区二区三区在线看 | 久久久性| 中文精品一区二区 | www.四虎.com | 九九热精品国产 | 中文字幕亚洲国产 | 可以免费看黄的网站 | 精品国产乱码久久久久久1区2区 | 香蕉久久精品视频 | 国产精品一区二 | 久久久综合网 | 91久久精品国产91久久 | av影音资源 | 精品国产一级 | 亚洲国产二区 | 色婷婷网| 国产美女精品一区二区三区 | 免费观看电视在线高清视频 | 欧美啪啪一区二区 | 综合伊人 | 精品国产一区二区三区日日嗨 | 成人中文网 | 一级毛片免费看 | 黄色网址在线免费 |