电竞比分网-中国电竞赛事及体育赛事平台

分享

JA-SIG(CAS)學(xué)習(xí)筆記

 Colin收藏 2012-07-11
JA-SIG(CAS)學(xué)習(xí)筆記 
2012年01月13日 星期五 下午 2:49
技術(shù)背景知識(shí):
  JA-SIG CAS服務(wù)環(huán)境搭建,請(qǐng)參考 :JA-SIG(CAS)學(xué)習(xí)筆記1 
  JA-SIG CAS業(yè)務(wù)架構(gòu)介紹,請(qǐng)參考 :JA-SIG(CAS)學(xué)習(xí)筆記2
  HTTPS所涉及的Java安全證書知識(shí),請(qǐng)參考 :Java  keytool 安全證書學(xué)習(xí)筆記

CAS技術(shù)框架

  CAS  Server
    目前,我們使用的CAS Server 3.1.1的是基于Spring Framework編寫的,因此在CAS服務(wù)器端的配置管理中,絕大多數(shù)是Spring式的Java Bean XML配置。CAS 的服務(wù)器提供了一套易于定制的用戶認(rèn)證器接口,用戶可以根據(jù)自身企業(yè)的在線系統(tǒng)的認(rèn)證方式,來(lái)定制自己的認(rèn)證邏輯。不論是傳統(tǒng)的用戶名/密碼方式,還是基 于安全證書的方式;是基于關(guān)系數(shù)據(jù)庫(kù)的存儲(chǔ),還是采用LDAP服務(wù)器,CAS  Server給我們提供了這些常用的驗(yàn)證器模板代碼,只要稍作修改,便可靈活使用了。
    對(duì)于廣大的中國(guó)企業(yè)用戶而言,另一個(gè)需要定制的功能莫過(guò)于全中文、企業(yè)特色的用戶身份認(rèn)證頁(yè)面了。CAS Server提供了兩套系統(tǒng)界面,一套是默認(rèn)的CAS英文標(biāo)準(zhǔn)頁(yè)面,另一套則是專門提供給用戶來(lái)定制修改的。(PS:老外們做事情就是人性化啊~~)那么 對(duì)CAS Server端的后續(xù)學(xué)習(xí),我們將圍繞著身份認(rèn)證模塊定制和界面定制這兩方面展開(kāi)。

  CAS  Client
    客戶端我們使用的是CAS Client 2.1.1。雖然在官方網(wǎng)站上已出現(xiàn)了3.1.0版本的下載,但該版本地代碼已經(jīng)完全重寫,使用的package和類名同2.1.1大相徑庭了,最關(guān)鍵的 是,該版本暫時(shí)沒(méi)有對(duì)應(yīng)的API說(shuō)明文檔。雖然咖啡我對(duì)程序版本懷有極大的“喜新厭舊”的心態(tài),但安全起見(jiàn),還是先2.1.1吧,相信3.1.0的文檔耶 魯大學(xué)的大牛們已經(jīng)在整理了,期待中……
CAS Client2.1.1.jar中的代碼是相當(dāng)精煉的,有興趣的朋友建議閱讀一下源碼。Jar包中的代碼分成三個(gè)大部分
  1. edu.yale.its.tp.cas.util 包,其中只有一個(gè)工具類 SecureURL.java 用來(lái)訪問(wèn)HTTPS URL
  2. edu.yale.its.tp.cas.proxy包,用來(lái)處理Proxy Authentication代理認(rèn)證的3個(gè)類,其中ProxyTicketReceptor.java是 接收PGT回調(diào)的servlet,在下文中我們會(huì)提及。
  3. edu.yale.its.tp.cas.client包,其中包含了CAS Filter ,Tag Library等主要的認(rèn)證客戶端工具類,我們?cè)诤竺鏁?huì)進(jìn)行重點(diǎn)介紹。
針對(duì)CAS Client的學(xué)習(xí),我們的重點(diǎn)將放在CAS Filter 和ProxyTicketReceptor 的配置以及在Java SE環(huán)境下,直接使用 ServiceTicketValidator進(jìn)行Ticket認(rèn)證實(shí)現(xiàn)上。

CAS服務(wù)器端應(yīng)用
定制適合你的身份認(rèn)證程序
    通過(guò)前面的學(xué)習(xí),我們了解了CAS具有一個(gè)良好而強(qiáng)大的SSO功能框架。接下來(lái),我們要學(xué)習(xí)如何將實(shí)際企業(yè)應(yīng)用中的身份認(rèn)證同CAS進(jìn)行整合。
    簡(jiǎn)單的說(shuō),要將現(xiàn)有企業(yè)應(yīng)用中的認(rèn)證集成到CAS Server中,只要實(shí)現(xiàn)一個(gè)名為AuthenticationHandler的一個(gè)認(rèn)證處理Java接口就行。以下是該接口的源代碼:
Java代碼  
  1. public interface AuthenticationHandler {  
  2. /** 
  3. * 該方法決定一個(gè)受支持的credentials是否是可用的, 
  4. * 如果可用,該方法返回true,則說(shuō)明身份認(rèn)證通過(guò) 
  5. */  
  6. boolean  authenticate(Credentials credentials)  throws  AuthenticationException;  
  7. /** 
  8. * 該方法決定一個(gè)credentials是否是當(dāng)前的handle所支持的 
  9. */  
  10. boolean  supports(Credentials credentials);  
  11. }  

這里我們要說(shuō)明一下Credentials這個(gè)CAS的概念。所謂Credentials是由外界提供給CAS來(lái)證明自身身份的信息,簡(jiǎn)單的如一 個(gè)用戶名/密碼對(duì)就是一個(gè)Credentials,或者一個(gè)經(jīng)過(guò)某種加密算法生成的密文證書也可以是一個(gè)Credentials。在程序的實(shí)現(xiàn) 上,Credentials被聲明為一個(gè)可序列化的接口,僅僅起著標(biāo)識(shí)作用,源代碼如下:
Java代碼  
  1. public interface Credentials extends Serializable {  
  2.     // marker interface contains no methods  
  3. }  

CAS的API中,已經(jīng)為我們提供了一個(gè)最常用的實(shí)現(xiàn)UsernamePasswordCredentials  用戶名/密碼憑證,代碼如下:
Java代碼  
  1. public class UsernamePasswordCredentials implements Credentials {  
  2.     /** Unique ID for serialization. */  
  3.     private static final long serialVersionUID = -8343864967200862794L;  
  4.     /** The username. */  
  5.     private String username;  
  6.     /** The password. */  
  7.     private String password;  
  8.     public final String getPassword() {  
  9.         return this.password;  
  10.     }  
  11.     public final void setPassword(final String password) {  
  12.         this.password = password;  
  13.     }  
  14.     public final String getUsername() {  
  15.         return this.username;  
  16.     }  
  17.     public final void setUsername(final String userName) {  
  18.         this.username = userName;  
  19.     }  
  20.     public String toString() {  
  21.         return this.username;  
  22.     }  
  23.     public boolean equals(final Object obj) {  
  24.         if (obj == null || !obj.getClass().equals(this.getClass())) {  
  25.             return false;  
  26.         }  
  27.         final UsernamePasswordCredentials c = (UsernamePasswordCredentials) obj;  
  28.         return this.username.equals(c.getUsername())  
  29.             && this.password.equals(c.getPassword());  
  30.     }  
  31.     public int hashCode() {  
  32.         return this.username.hashCode() ^ this.password.hashCode();  
  33.     }  
  34. }  

很簡(jiǎn)單不是嗎?就是存儲(chǔ)一個(gè)用戶名和密碼的java bean而已。
      接下來(lái),我們將一個(gè)Credentials傳給一個(gè)AuthenticationHandler進(jìn)行認(rèn)證,首先調(diào)用boolean  supports(Credentials credentials)方法察看當(dāng)前傳入的Credentials實(shí)例,AuthenticationHandler實(shí)例現(xiàn)是否支持它?如果支持,再調(diào) 用boolean  authenticate(Credentials credentials)方法進(jìn)行認(rèn)證。由于用戶名/密碼方式是最常用的認(rèn)證方法,因此CAS為我們提供了一個(gè)現(xiàn)成的基于該方式的抽象認(rèn)證處理類 AbstractUsernamePasswordAuthenticationHandler。通常我們只需要繼承該類,并實(shí)現(xiàn)其中的    authenticateUsernamePasswordInternal方法即可。下面我們給出一個(gè)Demo的實(shí)現(xiàn)類,它的校驗(yàn)邏輯很簡(jiǎn)單——僅校驗(yàn) 用戶名的字符長(zhǎng)度是否與密碼的相等(這里密碼是一個(gè)表示長(zhǎng)度的整數(shù)),如果相等則認(rèn)為認(rèn)證通過(guò),請(qǐng)看代碼:
Java代碼  
  1. public class UsernameLengthAuthnHandler  
  2.                        extends AbstractUsernamePasswordAuthenticationHandler {  
  3. protected boolean authenticateUsernamePasswordInternal( UsernamePasswordCredentials credentials)  throws AuthenticationException {  
  4. /*  
  5. * 這里我們完全可以用自己的認(rèn)證邏輯代替,比如將用戶名/密碼傳入一個(gè)SQL語(yǔ)句  
  6. * 向數(shù)據(jù)庫(kù)驗(yàn)證是否有對(duì)應(yīng)的用戶賬號(hào),這不是我們最經(jīng)常干的事么?  
  7. * 只需要將下面的程序替換掉就OK了??!So  easy,so  simple!  
  8. /  
  9.     String username = credentials.getUsername();  
  10.     String password = credentials.getPassword();  
  11.     String correctPassword = Integer.toString(username.length());  
  12.     return correctPassword.equals(password);  
  13. }  
  14. }  

介紹到這里,大家應(yīng)該清楚如何定制自己的AuthenticationHandler類了吧!這里要附帶說(shuō)明的是,在CAS Server的擴(kuò)展API中已經(jīng)提供了大量常用認(rèn)證形式的實(shí)現(xiàn)類,它們同CAS Server的war包一同分發(fā):
   cas-server-support-generic-3.1.1.jar ——使用Map記錄用戶認(rèn)證信息的實(shí)現(xiàn)
   cas-server-support-jdbc-3.1.1.jar —— 基于Spring JDBC的數(shù)據(jù)庫(kù)實(shí)現(xiàn)(我們常用的)
   cas-server-support-ldap-3.1.1.jar —— 基于LDAP的用戶認(rèn)證實(shí)現(xiàn)
更多其他形式的實(shí)現(xiàn)各位看官有興趣的,可以一一閱讀源碼。

配置你的身份認(rèn)證程序
     完成了定制認(rèn)證類的代碼編寫,接下來(lái)就是要讓CAS Server來(lái)調(diào)用它了。在CAS的框架中,對(duì)程序的配置都是使用Spring Framework的xml文件,這對(duì)于熟悉Spring的程序員而言算駕輕就熟了。
     配置文件位于應(yīng)用部署目錄的WEB-INF子目錄下——deployerConfigContext.xml。在bean id=authenticationManager 的 authenticationHandlers屬性中配置我們的AuthenticationHandlers:
引用

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www./schema/beans"
       xmlns:xsi="http://www./2001/XMLSchema-instance"
       xmlns:p="http://www./schema/p"
       xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-2.0.xsd">

<bean id="authenticationManager"
  class="org.jasig.cas.authentication.AuthenticationManagerImpl">
        。。。
        。。。
  <property name="authenticationHandlers">
   <list>
    <bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient" />
        <!—下面就是系統(tǒng)默認(rèn)的驗(yàn)證器配置,你可以替換它,或者增加一個(gè)新的handler -->
    <bean     class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
   </list>
  </property>
</bean>
。。。
。。。
</beans>

我們發(fā)現(xiàn)authenticationHandlers屬性是一個(gè)list,在這個(gè)list中可以配置多個(gè) AuthenticationHandlers。這些AuthenticationHandlers形成了一個(gè)驗(yàn)證器鏈,所有提交給CAS的 Credentials信息將通過(guò)這個(gè)驗(yàn)證器鏈的鏈?zhǔn)竭^(guò)濾,只要這鏈中有一個(gè)驗(yàn)證器通過(guò)了對(duì)Credentials的驗(yàn)證,就認(rèn)為這個(gè) Credentials是合法的。這樣的設(shè)計(jì)使得我們可以很輕松的整合不同驗(yàn)證體系的已有應(yīng)用到同一個(gè)CAS上,比如:A驗(yàn)證器負(fù)責(zé)校驗(yàn)alpha系統(tǒng)提 交的Credentials,它是基于LDAP服務(wù)器的;B驗(yàn)證器負(fù)責(zé)校驗(yàn)beta系統(tǒng)提交的Credentials,它是一個(gè)傳統(tǒng)的RDB用戶表認(rèn) 證;C驗(yàn)證器負(fù)責(zé)校驗(yàn)gamma系統(tǒng)提交的基于RSA證書加密的Credentials。3種完全不同的用戶身份認(rèn)證通過(guò)配置就可以統(tǒng)一在同一個(gè)CAS服 務(wù)內(nèi),很好很強(qiáng)大,不是嗎!!

定制身份驗(yàn)證登錄界面
      CAS Server在顯示界面層view使用了“主題Theme”的概念。在{project.home}/webapp/WEB-INF/view/jsp /目錄下,系統(tǒng)默認(rèn)提供了兩套得UI —— default和simple 。default方案使用了CSS等相對(duì)復(fù)雜得界面元素,而simple方案提供了最簡(jiǎn)化的界面表示方式。在整個(gè)的CAS Server服務(wù)器端,有四個(gè)界面是我們必須要實(shí)現(xiàn)的:
    casConfirmView.jsp —— 確認(rèn)信息(警告信息)頁(yè)面
    casGenericSuccess.jsp —— 登陸成功提示頁(yè)面
    casLoginView.jsp —— 登錄輸入頁(yè)面
    casLogoutView.jsp —— SSO登出提示頁(yè)面
這些都是標(biāo)準(zhǔn)的jsp頁(yè)面,如何實(shí)現(xiàn)他們,完全由您說(shuō)了算,除了名字不能改。

CAS為view的展示提供了3個(gè)級(jí)別的定制方式,讓我們從最直觀簡(jiǎn)單的開(kāi)始吧。

1. 采用文件覆蓋方式:直接修改default中的頁(yè)面或者將新寫好的四個(gè)jsp文件覆蓋到default目錄中。這種方式最直觀和簡(jiǎn)單,但咖啡建議各位在使用這種方式前將原有目錄中的文件備份一下,以備不時(shí)之需。

2. 修改UI配置文件,定位UI目錄:在CAS Server端/webapp/WEB-INF/classes/ 目錄下,有一個(gè)名為default_views.properties的屬性配置文件,你可以通過(guò)修改配置文件中的各個(gè)頁(yè)面文件位置,指向你新UI文件, 來(lái)達(dá)到修改頁(yè)面展示的目的。

3. 修改配置文件的配置文件,這話看起來(lái)有點(diǎn)別扭,其實(shí)一點(diǎn)不難理解。在方法2中的default_views.properties文件是一整套的UI頁(yè)面 配置。如果我想保存多套的UI頁(yè)面配置就可以寫多個(gè)的properties文件來(lái)保存這些配置。在CAS Server端/webapp/WEB-INF/目錄下有cas-servlet.xml和cas.properties兩個(gè)文件,cas- servlet.xml使用了cas.properties文件中的cas.viewResolver.basename屬性來(lái)定義view屬性文件的名 字,因此你可以選者直接修改cas-servlet.xml中的viewResolver 下的basenames屬性,或者修改cas.properties中的cas.viewResolver.basename屬性,指定新的 properties文件名,這樣可以輕松的替換全套UI。

CAS客戶端配置及API應(yīng)用
CASFilter的配置
     對(duì)于大部分web應(yīng)用而言,使用CAS集成統(tǒng)一認(rèn)證是相對(duì)簡(jiǎn)單的事,只要為需要認(rèn)證的URL配置 edu.yale.its.tp.cas.client.filter.CASFilter認(rèn)證過(guò)濾器。下面我們就針對(duì)過(guò)濾器的配置進(jìn)行說(shuō)明。首先參看一 下Filter的基本配置:
引用
<web-app>
  ...
  <filter>
<filter-name>CAS Filter</filter-name>
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
    <init-param>
       <param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
       <param-value>https://secure.its./cas/login<;/param-value>
    </init-param>
    <init-param>
       <param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
       <param-value>https://secure.its./cas/serviceValidate<;/param-value>
    </init-param>
    <init-param>
       <param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
       <param-value>your server name and port (e.g., www.:8080)</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>CAS Filter</filter-name>
    <url-pattern>/requires-cas-authetication/*</url-pattern>
</filter-mapping>
  ...
</web-app>

上述配置中的init-param是filter的3個(gè)必備的屬性,下面這張表則是filter全部屬性的詳細(xì)說(shuō)明:


ProxyTicketReceptor的配置
    大家還記得在前面我們說(shuō)過(guò)的Proxy Authentication中的call back URL嗎?ProxyTicketReceptor是部署在client端的一個(gè)servlet,提供server端回傳PGT和PGTIOU的。它的xml部署如下:
引用

<web-app>
  ...
  <servlet>
  <servlet-name>ProxyTicketReceptor</servlet-name>
  <servlet-class>edu.yale.its.tp.cas.proxy.ProxyTicketReceptor</servlet-class>
    <init-param>
       <param-name>edu.yale.its.tp.cas.proxyUrl</param-name>
       <param-value>https://secure.its./cas/proxy<;/param-value>
    </init-param>
  </servlet>

<servlet-mapping>
  <servlet-name>ProxyTicketReceptor</servlet-name>
  <url-pattern>/CasProxyServlet</url-pattern>
  </servlet-mapping>
  ...
</webapp>


這里要說(shuō)明的是它的參數(shù)edu.yale.its.tp.cas.proxyUrl。在服務(wù)端通過(guò)ProxyTicketReceptor將 PGT和PGTIOU傳給客戶端后,ProxyTicketReceptor在進(jìn)行Proxy Authentication的過(guò)程中需要向服務(wù)端請(qǐng)求一個(gè)ProxyTicket(PT),這個(gè)proxyUrl就是服務(wù)端的請(qǐng)求入口了。(關(guān)于 Proxy Authentication的運(yùn)作原理,參見(jiàn)JA-SIG(CAS)學(xué)習(xí)筆記2

CAS Client端的API應(yīng)用1.用戶可以通過(guò)以下兩種方式的任意一種,從JSP或servlet中獲取通過(guò)認(rèn)證的用戶名:
引用
String username = (String)session.getAttribute(CASFilter.CAS_FILTER_USER);
或者
String username = (String)session.getAttribute("edu.yale.its.tp.cas.client.filter.user");


2.獲得更完整的受認(rèn)證用戶信息對(duì)象CASReceipt Java Bean,可以使用以下語(yǔ)句的任一:
引用
CASReceipt  receipt = (CASReceipt )session.getAttribute(CASFilter.CAS_FILTER_RECEIPT);
或者
CASReceipt  receipt = (CASReceipt )session.getAttribute("edu.yale.its.tp.cas.client.filter.receipt");


3.手工編碼使用CAS Java Object進(jìn)行用戶驗(yàn)證,使用ServiceTicketValidator或者 ProxyTicketValidator(代理認(rèn)證模式下),在servlet中對(duì)用戶身份進(jìn)行驗(yàn)證。
3-1.ServiceTicketValidator
Java代碼  
  1. import edu.yale.its.tp.cas.client.*;   
  2.  ...  
  3.  String user = null;  
  4.  String errorCode = null;  
  5.  String errorMessage = null;  
  6.  String xmlResponse = null;  
  7.    
  8.  /* instantiate a new ServiceTicketValidator */  
  9.  ServiceTicketValidator sv = new ServiceTicketValidator();  
  10.    
  11.  /* set its parameters */  
  12.  sv.setCasValidateUrl("https://secure.its./cas/serviceValidate");  
  13.  sv.setService(urlOfThisService);  
  14.  sv.setServiceTicket(request.getParameter("ticket"));   
  15.    
  16.  String urlOfProxyCallbackServlet = "https://portal./CasProxyServlet";   
  17.  sv.setProxyCallbackUrl(urlOfProxyCallbackServlet);  
  18.    
  19.  /* contact CAS and validate */  
  20.  sv.validate();  
  21.    
  22.  /* if we want to look at the raw response, we can use getResponse() */  
  23.  xmlResponse = sv.getResponse();  
  24.    
  25.  if(sv.isAuthenticationSuccesful()) {  
  26.   user = sv.getUser();  
  27.  } else {  
  28.   errorCode = sv.getErrorCode();  
  29.   errorMessage = sv.getErrorMessage();  
  30.  }  
  31.   /* The user is now authenticated. */  
  32.   /* If we did set the proxy callback url, we can get proxy tickets with: */  
  33.   String urlOfTargetService = "http://hkg2.its./someApp/portalFeed";  
  34.   String proxyTicket = ProxyTicketReceptor.getProxyTicket( sv.getPgtIou() , urlOfTargetService);  


3-2.ProxyTicketValidator
Java代碼  
  1. import edu.yale.its.tp.cas.client.*;  
  2.  ...   
  3.  String user = null;  
  4.  String errorCode = null;  
  5.  String errorMessage = null;  
  6.  String xmlResponse = null;  
  7.  List proxyList = null;  
  8.    
  9.  /* instantiate a new ProxyTicketValidator */  
  10.  ProxyTicketValidator pv = new ProxyTicketValidator();   
  11.    
  12.  /* set its parameters */  
  13.  pv.setCasValidateUrl("https://secure.its./cas/proxyValidate");  
  14.  pv.setService(urlOfThisService);  
  15.  pv.setServiceTicket(request.getParameter("ticket"));   
  16.    
  17.  String urlOfProxyCallbackServlet = "https://portal./CasProxyServlet";  
  18.  pv.setProxyCallbackUrl(urlOfProxyCallbackServlet);   
  19.    
  20.  /* contact CAS and validate */  
  21.  pv.validate();  
  22.    
  23.  /* if we want to look at the raw response, we can use getResponse() */  
  24.  xmlResponse = pv.getResponse();   
  25.    
  26.  /* read the response */  
  27.  if(pv.isAuthenticationSuccesful()) {  
  28.   user = pv.getUser();  
  29.   proxyList = pv.getProxyList();  
  30.  } else {  
  31.   errorCode = pv.getErrorCode();  
  32.   errorMessage = pv.getErrorMessage();  
  33.   /* handle the error */  
  34.  }   
  35.  /* The user is now authenticated. */   
  36.  /* If we did set the proxy callback url, we can get proxy tickets with this method call: */   
  37.  String urlOfTargetService = "http://hkg2.its./someApp/portalFeed";   
  38.  String proxyTicket = ProxyTicketReceptor.getProxyTicket( pv.getPgtIou() , urlOfTargetService);  


在這里,我們假設(shè)上下文環(huán)境中的用戶已經(jīng)通過(guò)了CAS登錄認(rèn)證,被重定向到當(dāng)前的servlet下,我們?cè)趕ervlet中獲取ticket憑 證,servlet的URL對(duì)用戶身份進(jìn)行確認(rèn)。如果上下文參數(shù)中無(wú)法獲取ticket憑證,我們就認(rèn)為用戶尚未登錄,那么,該servlet必須負(fù)責(zé)將 用戶重定向到CAS的登錄頁(yè)面去。

初戰(zhàn)告捷
      到今天為止,我們已經(jīng)通過(guò)JA-SIG學(xué)習(xí)筆記的1-3部分,對(duì)CAS這個(gè)開(kāi)源SSO的框架有了個(gè)大體的了解和初步的掌握,希望這些知識(shí)能為各位步入CAS殿堂打開(kāi)一扇的大門??Х认M诮窈蟮墓ぷ鲬?yīng)用中,能同大家一塊共同探討,進(jìn)一步深入了解CAS。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多