<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java 程序員如何使用 Shiro 框架

          共 23279字,需瀏覽 47分鐘

           ·

          2020-07-24 13:11



          作者:冷豪來(lái)自:www.cnblogs.com/learnhow/p/5694876.html

          一、架構(gòu)

          要學(xué)習(xí)如何使用Shiro必須先從它的架構(gòu)談起,作為一款安全框架Shiro的設(shè)計(jì)相當(dāng)精妙。Shiro的應(yīng)用不依賴任何容器,它也可以在JavaSE下使用。但是最常用的環(huán)境還是JavaEE。下面以用戶登錄為例:


          7f3b22e89cc6f82d416e14997f728a62.webp


          1、使用用戶的登錄信息創(chuàng)建令牌
          UsernamePasswordToken token = new UsernamePasswordToken(username, password);

          token可以理解為用戶令牌,登錄的過(guò)程被抽象為Shiro驗(yàn)證令牌是否具有合法身份以及相關(guān)權(quán)限。


          2、執(zhí)行登陸動(dòng)作
          SecurityUtils.setSecurityManager(securityManager); // 注入SecurityManager
          Subject subject = SecurityUtils.getSubject(); // 獲取Subject單例對(duì)象
          subject.login(token); // 登陸

          Shiro的核心部分是SecurityManager,它負(fù)責(zé)安全認(rèn)證與授權(quán)。Shiro本身已經(jīng)實(shí)現(xiàn)了所有的細(xì)節(jié),用戶可以完全把它當(dāng)做一個(gè)黑盒來(lái)使用。SecurityUtils對(duì)象,本質(zhì)上就是一個(gè)工廠類似Spring中的ApplicationContext。


          Subject是初學(xué)者比較難于理解的對(duì)象,很多人以為它可以等同于User,其實(shí)不然。Subject中文翻譯:項(xiàng)目,而正確的理解也恰恰如此。它是你目前所設(shè)計(jì)的需要通過(guò)Shiro保護(hù)的項(xiàng)目的一個(gè)抽象概念。通過(guò)令牌(token)與項(xiàng)目(subject)的登陸(login)關(guān)系,Shiro保證了項(xiàng)目整體的安全。


          3、判斷用戶


          Shiro本身無(wú)法知道所持有令牌的用戶是否合法,因?yàn)槌隧?xiàng)目的設(shè)計(jì)人員恐怕誰(shuí)都無(wú)法得知。因此Realm是整個(gè)框架中為數(shù)不多的必須由設(shè)計(jì)者自行實(shí)現(xiàn)的模塊,當(dāng)然Shiro提供了多種實(shí)現(xiàn)的途徑,本文只介紹最常見(jiàn)也最重要的一種實(shí)現(xiàn)方式——數(shù)據(jù)庫(kù)查詢。


          4、兩條重要的英文


          我在學(xué)習(xí)Shiro的過(guò)程中遇到的第一個(gè)障礙就是這兩個(gè)對(duì)象的英文名稱:AuthorizationInfo,AuthenticationInfo。不用懷疑自己的眼睛,它們確實(shí)長(zhǎng)的很像,不但長(zhǎng)的像,就連意思都十分近似。


          在解釋它們前首先必須要描述一下Shiro對(duì)于安全用戶的界定:和大多數(shù)操作系統(tǒng)一樣。用戶具有角色和權(quán)限兩種最基本的屬性。例如,我的Windows登陸名稱是learnhow,它的角色是administrator,而administrator具有所有系統(tǒng)權(quán)限。這樣learnhow自然就擁有了所有系統(tǒng)權(quán)限。那么其他人需要登錄我的電腦怎么辦,我可以開(kāi)放一個(gè)guest角色,任何無(wú)法提供正確用戶名與密碼的未知用戶都可以通過(guò)guest來(lái)登錄,而系統(tǒng)對(duì)于guest角色開(kāi)放的權(quán)限極其有限。


          同理,Shiro對(duì)用戶的約束也采用了這樣的方式。AuthenticationInfo代表了用戶的角色信息集合,AuthorizationInfo代表了角色的權(quán)限信息集合。如此一來(lái),當(dāng)設(shè)計(jì)人員對(duì)項(xiàng)目中的某一個(gè)url路徑設(shè)置了只允許某個(gè)角色或具有某種權(quán)限才可以訪問(wèn)的控制約束的時(shí)候,Shiro就可以通過(guò)以上兩個(gè)對(duì)象來(lái)判斷。說(shuō)到這里,大家可能還比較困惑。先不要著急,繼續(xù)往后看就自然會(huì)明白了。


          二、實(shí)現(xiàn)Realm


          如何實(shí)現(xiàn)Realm是本文的重頭戲,也是比較費(fèi)事的部分。這里大家會(huì)接觸到幾個(gè)新鮮的概念:緩存機(jī)制、散列算法、加密算法。由于本文不會(huì)專門(mén)介紹這些概念,所以這里僅僅拋磚引玉的談幾點(diǎn),能幫助大家更好的理解Shiro即可。


          1、緩存機(jī)制


          Ehcache是很多Java項(xiàng)目中使用的緩存框架,Hibernate就是其中之一。它的本質(zhì)就是將原本只能存儲(chǔ)在內(nèi)存中的數(shù)據(jù)通過(guò)算法保存到硬盤(pán)上,再根據(jù)需求依次取出。你可以把Ehcache理解為一個(gè)Map 對(duì)象,通過(guò)put保存對(duì)象,再通過(guò)get取回對(duì)象。
          xml version="1.0" encoding="UTF-8"?>
          <ehcache name="shirocache">
             <diskStore path="java.io.tmpdir" />
             
             <cache name="passwordRetryCache"
                    maxEntriesLocalHeap="2000"
                    eternal="false"
                    timeToIdleSeconds="1800"
                    timeToLiveSeconds="0"
                    overflowToDisk="false"
                    statistics="true">

             cache>

          ehcache>
          以上是ehcache.xml文件的基礎(chǔ)配置,timeToLiveSeconds為緩存的最大生存時(shí)間,timeToIdleSeconds為緩存的最大空閑時(shí)間,當(dāng)eternal為false時(shí)ttl和tti才可以生效。更多配置的含義大家可以去網(wǎng)上查詢。


          2、散列算法與加密算法


          md5是本文會(huì)使用的散列算法,加密算法本文不會(huì)涉及。散列和加密本質(zhì)上都是將一個(gè)Object變成一串無(wú)意義的字符串,不同點(diǎn)是經(jīng)過(guò)散列的對(duì)象無(wú)法復(fù)原,是一個(gè)單向的過(guò)程。例如,對(duì)密碼的加密通常就是使用散列算法,因此用戶如果忘記密碼只能通過(guò)修改而無(wú)法獲取原始密碼。但是對(duì)于信息的加密則是正規(guī)的加密算法,經(jīng)過(guò)加密的信息是可以通過(guò)秘鑰解密和還原。


          3、用戶注冊(cè)


          請(qǐng)注意,雖然我們一直在談?wù)撚脩舻卿浀陌踩詥?wèn)題,但是說(shuō)到用戶登錄首先就是用戶注冊(cè)。如何保證用戶注冊(cè)的信息不丟失,不泄密也是項(xiàng)目設(shè)計(jì)的重點(diǎn)。

          public class PasswordHelper {
             private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
             private String algorithmName = "md5";
             private final int hashIterations = 2;

             public void encryptPassword(User user) {
                 // User對(duì)象包含最基本的字段Username和Password
                 user.setSalt(randomNumberGenerator.nextBytes().toHex());
                 // 將用戶的注冊(cè)密碼經(jīng)過(guò)散列算法替換成一個(gè)不可逆的新密碼保存進(jìn)數(shù)據(jù),散列過(guò)程使用了鹽
                 String newPassword = new SimpleHash(algorithmName, user.getPassword(),
                         ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex();
                 user.setPassword(newPassword);
             }
          }

          如果你不清楚什么叫加鹽可以忽略散列的過(guò)程,只要明白存儲(chǔ)在數(shù)據(jù)庫(kù)中的密碼是根據(jù)戶注冊(cè)時(shí)填寫(xiě)的密碼所產(chǎn)生的一個(gè)新字符串就可以了。經(jīng)過(guò)散列后的密碼替換用戶注冊(cè)時(shí)的密碼,然后將User保存進(jìn)數(shù)據(jù)庫(kù)。剩下的工作就丟給UserService來(lái)處理。


          那么這樣就帶來(lái)了一個(gè)新問(wèn)題,既然散列算法是無(wú)法復(fù)原的,當(dāng)用戶登錄的時(shí)候使用當(dāng)初注冊(cè)時(shí)的密碼,我們又應(yīng)該如何判斷?答案就是需要對(duì)用戶密碼再次以相同的算法散列運(yùn)算一次,再同數(shù)據(jù)庫(kù)中保存的字符串比較。


          4、匹配


          CredentialsMatcher是一個(gè)接口,功能就是用來(lái)匹配用戶登錄使用的令牌和數(shù)據(jù)庫(kù)中保存的用戶信息是否匹配。當(dāng)然它的功能不僅如此。本文要介紹的是這個(gè)接口的一個(gè)實(shí)現(xiàn)類:HashedCredentialsMatcher

          public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
             // 聲明一個(gè)緩存接口,這個(gè)接口是Shiro緩存管理的一部分,它的具體實(shí)現(xiàn)可以通過(guò)外部容器注入
             private Cache passwordRetryCache;

              public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
                 passwordRetryCache = cacheManager.getCache( "passwordRetryCache");
             }

              @Override
              public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
                 String username = (String) token.getPrincipal();
                 AtomicInteger retryCount = passwordRetryCache.get(username);
                  if (retryCount == null) {
                     retryCount = new AtomicInteger( 0);
                     passwordRetryCache.put(username, retryCount);
                 }
                  // 自定義一個(gè)驗(yàn)證過(guò)程:當(dāng)用戶連續(xù)輸入密碼錯(cuò)誤5次以上禁止用戶登錄一段時(shí)間
                  if (retryCount.incrementAndGet() > 5) {
                      throw  new ExcessiveAttemptsException();
                 }
                  boolean match = super.doCredentialsMatch(token, info);
                  if (match) {
                     passwordRetryCache.remove(username);
                 }
                  return match;
             }
          }


          可以看到,這個(gè)實(shí)現(xiàn)里設(shè)計(jì)人員僅僅是增加了一個(gè)不允許連續(xù)錯(cuò)誤登錄的判斷。真正匹配的過(guò)程還是交給它的直接父類去完成。連續(xù)登錄錯(cuò)誤的判斷依靠Ehcache緩存來(lái)實(shí)現(xiàn)。顯然match返回true為匹配成功。


          5、獲取用戶的角色和權(quán)限信息


          說(shuō)了這么多才到我們的重點(diǎn)Realm,如果你已經(jīng)理解了Shiro對(duì)于用戶匹配和注冊(cè)加密的全過(guò)程,真正理解Realm的實(shí)現(xiàn)反而比較簡(jiǎn)單。我們還得回到上文提及的兩個(gè)非常類似的對(duì)象AuthorizationInfo和AuthenticationInfo。因?yàn)镽ealm就是提供這兩個(gè)對(duì)象的地方。

          public class UserRealm extends AuthorizingRealm {
             // 用戶對(duì)應(yīng)的角色信息與權(quán)限信息都保存在數(shù)據(jù)庫(kù)中,通過(guò)UserService獲取數(shù)據(jù)
             private UserService userService = new UserServiceImpl();

             /**
              * 提供用戶信息返回權(quán)限信息
              */

             @Override
             protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
                 String username = (String) principals.getPrimaryPrincipal();
                 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
                 // 根據(jù)用戶名查詢當(dāng)前用戶擁有的角色
                 Set roles = userService.findRoles(username);
                 Set< String> roleNames = new HashSet< String>();
                  for (Role role : roles) {
                     roleNames.add(role.getRole());
                 }
                  // 將角色名稱提供給info
                 authorizationInfo.setRoles(roleNames);
                  // 根據(jù)用戶名查詢當(dāng)前用戶權(quán)限
                 Set permissions = userService.findPermissions(username);
                 Set< String> permissionNames = new HashSet< String>();
                  for (Permission permission : permissions) {
                     permissionNames.add(permission.getPermission());
                 }
                  // 將權(quán)限名稱提供給info
                 authorizationInfo.setStringPermissions(permissionNames);

                  return authorizationInfo;
             }

              /**
              * 提供賬戶信息返回認(rèn)證信息
              */

              @Override
              protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
                  String username = ( String) token.getPrincipal();
                 User user = userService.findByUsername(username);
                  if (user == null) {
                      // 用戶名不存在拋出異常
                      throw  new UnknownAccountException();
                 }
                  if (user.getLocked() == 0) {
                      // 用戶被管理員鎖定拋出異常
                      throw  new LockedAccountException();
                 }
                 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
                         user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
                  return authenticationInfo;
             }
          }


          根據(jù)Shiro的設(shè)計(jì)思路,用戶與角色之前的關(guān)系為多對(duì)多,角色與權(quán)限之間的關(guān)系也是多對(duì)多。在數(shù)據(jù)庫(kù)中需要因此建立5張表,分別是:


          • 用戶表(存儲(chǔ)用戶名,密碼,鹽等)
          • 角色表(角色名稱,相關(guān)描述等)
          • 權(quán)限表(權(quán)限名稱,相關(guān)描述等)
          • 用戶-角色對(duì)應(yīng)中間表(以用戶ID和角色I(xiàn)D作為聯(lián)合主鍵)
          • 角色-權(quán)限對(duì)應(yīng)中間表(以角色I(xiàn)D和權(quán)限ID作為聯(lián)合主鍵)

          具體dao與service的實(shí)現(xiàn)本文不提供??傊Y(jié)論就是,Shiro需要根據(jù)用戶名和密碼首先判斷登錄的用戶是否合法,然后再對(duì)合法用戶授權(quán)。而這個(gè)過(guò)程就是Realm的實(shí)現(xiàn)過(guò)程。
          6、會(huì)話
          用戶的一次登錄即為一次會(huì)話,Shiro也可以代替Tomcat等容器管理會(huì)話。目的是當(dāng)用戶停留在某個(gè)頁(yè)面長(zhǎng)時(shí)間無(wú)動(dòng)作的時(shí)候,再次對(duì)任何鏈接的訪問(wèn)都會(huì)被重定向到登錄頁(yè)面要求重新輸入用戶名和密碼而不需要程序員在Servlet中不停的判斷Session中是否包含User對(duì)象。
          啟用Shiro會(huì)話管理的另一個(gè)用途是可以針對(duì)不同的模塊采取不同的會(huì)話處理。以淘寶為例,用戶注冊(cè)淘寶以后可以選擇記住用戶名和密碼。之后再次訪問(wèn)就無(wú)需登陸。但是如果你要訪問(wèn)支付寶或購(gòu)物車等鏈接依然需要用戶確認(rèn)身份。當(dāng)然,Shiro也可以創(chuàng)建使用容器提供的Session最為實(shí)現(xiàn)。
          三、與SpringMVC集成
          有了注冊(cè)模塊和Realm模塊的支持,下面就是如何與SpringMVC集成開(kāi)發(fā)。有過(guò)框架集成經(jīng)驗(yàn)的同學(xué)一定知道,所謂的集成基本都是一堆xml文件的配置,Shiro也不例外。
          1、配置前端過(guò)濾器
          先說(shuō)一個(gè)題外話,F(xiàn)ilter是過(guò)濾器,interceptor是攔截器。前者基于回調(diào)函數(shù)實(shí)現(xiàn),必須依靠容器支持。因?yàn)樾枰萜餮b配好整條FilterChain并逐個(gè)調(diào)用。后者基于代理實(shí)現(xiàn),屬于AOP的范疇。
          如果希望在WEB環(huán)境中使用Shiro必須首先在web.xml文件中配置
          <?xml version="1.0" encoding="UTF-8"?>
          <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://java.sun.com/xml/ns/javaee"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             id="WebApp_ID" version="3.0">

             <display-name>Shiro_Projectdisplay-name>

             <welcome-file-list>
                 <welcome-file>index.jspwelcome-file>
             welcome-file-list>
             <servlet>
                 <servlet-name>SpringMVCservlet-name>
                 <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
                 <init-param>
                     <param-name>contextConfigLocationparam-name>
                     <param-value>classpath:springmvc.xmlparam-value>
                 init-param>
                 <load-on-startup>1load-on-startup>
                 <async-supported>trueasync-supported>
             servlet>
             <servlet-mapping>
                 <servlet-name>SpringMVCservlet-name>
                 <url-pattern>/url-pattern>
             servlet-mapping>
             <listener>
                 <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
             listener>
             <listener>
                 <listener-class>org.springframework.web.util.Log4jConfigListenerlistener-class>
             listener>
             <context-param>
                 <param-name>contextConfigLocationparam-name>
                 
                 <param-value>classpath:spring.xml,classpath:spring-shiro-web.xmlparam-value>
             context-param>
             <context-param>
                 <param-name>log4jConfigLoactionparam-name>
                 <param-value>classpath:log4j.propertiesparam-value>
             context-param>
             
             <filter>
                 <filter-name>shiroFilterfilter-name>
                 <filter-class>org.springframework.web.filter.DelegatingFilterProxyfilter-class>
                 <async-supported>trueasync-supported>
                 <init-param>
                     <param-name>targetFilterLifecycleparam-name>
                     <param-value>trueparam-value>
                 init-param>
             filter>
             <filter-mapping>
                 <filter-name>shiroFilterfilter-name>
                 <url-pattern>/*url-pattern>
             filter-mapping>
             
          web-app>
          熟悉Spring配置的同學(xué)可以重點(diǎn)看有綠字注釋的部分,這里是使Shiro生效的關(guān)鍵。由于項(xiàng)目通過(guò)Spring管理,因此所有的配置原則上都是交給Spring。DelegatingFilterProxy的功能是通知Spring將所有的Filter交給ShiroFilter管理。
          接著在classpath路徑下配置spring-shiro-web.xml文件
          <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:mvc="http://www.springframework.org/schema/mvc"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
                                 http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
                                 http://www.springframework.org/schema/context
                                 http://www.springframework.org/schema/context/spring-context-3.1.xsd
                                 http://www.springframework.org/schema/mvc
                                 http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"
          >


             
             <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
                 <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
             bean>


             
             <bean id="credentialsMatcher" class="utils.RetryLimitHashedCredentialsMatcher">
                 <constructor-arg ref="cacheManager" />
                 <property name="hashAlgorithmName" value="md5" />
                 <property name="hashIterations" value="2" />
                 <property name="storedCredentialsHexEncoded" value="true" />
             bean>

             
             <bean id="userRealm" class="utils.UserRealm">
                 <property name="credentialsMatcher" ref="credentialsMatcher" />
             bean>

             
             <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
                 <property name="realm" ref="userRealm" />
             bean>

             
             <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
                 <property name="securityManager" ref="securityManager" />
                 <property name="loginUrl" value="/" />
                 <property name="unauthorizedUrl" value="/" />
                 <property name="filterChainDefinitions">
                     <value>
                         /authc/admin = roles[admin]
                         /authc/** = authc
                         /** = anon
                     value>
                 property>
             bean>

             <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
          beans>


          需要注意filterChainDefinitions過(guò)濾器中對(duì)于路徑的配置是有順序的,當(dāng)找到匹配的條目之后容器不會(huì)再繼續(xù)尋找。因此帶有通配符的路徑要放在后面。三條配置的含義是:
          •  /authc/admin需要用戶有用admin權(quán)限
          • /authc/**用戶必須登錄才能訪問(wèn)
          • /**其他所有路徑任何人都可以訪問(wèn)
          說(shuō)了這么多,大家一定關(guān)心在Spring中引入Shiro之后到底如何編寫(xiě)登錄代碼呢。
          @Controller
          public class LoginController {
             @Autowired
             private UserService userService;

             @RequestMapping("login")
             public ModelAndView login(@RequestParam("username") String username, @RequestParam("password") String password) {
                 UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                 Subject subject = SecurityUtils.getSubject();
                 try {
                     subject.login(token);
                 } catch (IncorrectCredentialsException ice) {
                     // 捕獲密碼錯(cuò)誤異常
                     ModelAndView mv = new ModelAndView("error");
                     mv.addObject("message", "password error!");
                     return mv;
                 } catch (UnknownAccountException uae) {
                     // 捕獲未知用戶名異常
                     ModelAndView mv = new ModelAndView("error");
                     mv.addObject("message", "username error!");
                     return mv;
                 } catch (ExcessiveAttemptsException eae) {
                     // 捕獲錯(cuò)誤登錄過(guò)多的異常
                     ModelAndView mv = new ModelAndView("error");
                     mv.addObject("message", "times error");
                     return mv;
                 }
                 User user = userService.findByUsername(username);
                 subject.getSession().setAttribute("user", user);
                 return new ModelAndView("success");
             }
          }


          登錄完成以后,當(dāng)前用戶信息被保存進(jìn)Session。這個(gè)Session是通過(guò)Shiro管理的會(huì)話對(duì)象,要獲取依然必須通過(guò)Shiro。傳統(tǒng)的Session中不存在User對(duì)象。
          @Controller
          @RequestMapping("authc")
          public class AuthcController {
             // /authc/** = authc 任何通過(guò)表單登錄的用戶都可以訪問(wèn)
             @RequestMapping("anyuser")
             public ModelAndView anyuser() {
                 Subject subject = SecurityUtils.getSubject();
                 User user = (User) subject.getSession().getAttribute("user");
                 System.out.println(user);
                 return new ModelAndView("inner");
             }

             // /authc/admin = user[admin] 只有具備admin角色的用戶才可以訪問(wèn),否則請(qǐng)求將被重定向至登錄界面
             @RequestMapping("admin")
             public ModelAndView admin() {
                 Subject subject = SecurityUtils.getSubject();
                 User user = (User) subject.getSession().getAttribute("user");
                 System.out.println(user);
                 return new ModelAndView("inner");
             }
          }

          最后免費(fèi)給大家分享50個(gè)Java項(xiàng)目實(shí)戰(zhàn)資料,涵蓋入門(mén)、進(jìn)階各個(gè)階段學(xué)習(xí)內(nèi)容,可以說(shuō)非常全面了。大部分視頻還附帶源碼,學(xué)起來(lái)還不費(fèi)勁!


          附上截圖。(下面有下載方式)。

          b61a77d93ec148ac24a48fa12aee5a0e.webp

          e57da98ad58bccbe06c3a873709594ee.webp

          0cd7621131f0e0a49a108cfac0f8d74d.webp

          6286320544432baac1e373cc8fc1e4be.webp

          c22355c6a47c1bac0cea85e7d224f217.webp


          項(xiàng)目領(lǐng)取方式:

          掃描下方公眾號(hào)回復(fù):50,

          可獲取下載鏈接

          ???

          ?長(zhǎng)按上方二維碼 2 秒回復(fù)「50」即可獲取資料

          點(diǎn)贊是最大的支持 006b08774870d1fdf813579c73cae17b.webp

          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚州在线无码视频 | 秋霞午夜成人无码精品 | 中文字幕一区二区三区乱码视频 | 亚洲精品久久久久久久久蜜桃 | 日韩美女操逼网 |