人人IT網

人人IT網

當前位置: 主頁 > 服務器軟件 > Websphere >

在 Worklight 中開發基於适配器的安全驗證

時間:2012-12-07 00:05來源:Internet 作者:Internet 點擊:
免費下載:IBM® Worklight Mobile Platform下載更多的 IBM 軟件試用版,並加入 IBM 軟件下載與技術交流群組,参與在線交流。 Workligh
免費下載:IBM® Worklight Mobile Platform
下載更多的 IBM 軟件試用版,並加入 IBM 軟件下載與技術交流群組,参與在線交流。

Worklight 安全驗證簡介

安全驗證是應用開發中的一個重要的組成部分,對移動應用開發亦是如此,Worklight 作为一個業界領先的移動應用開發和管理平台,提供了完善的安全驗證框架。

Worklight 安全驗證框架中的基本概念

安全驗證用於防止對某些特定資源的未經許可的訪問,在 Worklight 中,我們使用其驗證框架來保護 Worklight 中的 實體(entity),如 應用适配器 以及 靜態資源

Worklight 提供了聲明式的方式來配置保護規則,來實現對實體的未經授權的訪問保護,聲明規則由下面兩個概念組成:

  • security test: 由一個或多個 ream 組成,用於保護 Worklight 的實體。
  • realm: realm 定義了處理用戶驗證的業務邏輯,它又由以下幾個概念組成。
    • challenge handler:客戶端組件,用於偵測服務器端是不是在發送一個需要安全驗證的請求,如果是, 則收集用戶身份信息,並發送至服務器端。
    • authenticator:服務器端組件,用於收集客戶端發來的用戶身份信息。
    • login module:服務器端組件,用於接收 authenticator 收集到的用戶身份信息,驗證該身份並創建用戶身份對像。

Worklight 服務器端可以定義一個或多個 authenticator,authenticator 又分为下面三種類型:

  • 基於表單的 authenticator:用於基於表單的安全驗證請求。默認實現为 com.worklight.core.auth.ext.FormBasedAuthenticator。
  • 基於适配器的 authenticator:用於使用适配器過程(procedure,類似於 Java 方法或函數的概念)來 搜集和驗證 用戶身份。默認實現为 com.worklight.integration.auth.AdapterAuthenticator。
  • 基於 HTTP 頭的 authenticator: 同上述 authenticator 不一样,該 authenticator 並不需要由客戶端提交用戶身份信息,而是直接從 HTTP 頭中檢查相關的屬性值。

我們在多數情況下,僅需直接使用 Worklight 提供的 authenticator,如有复雜的情況,我們也可以使用 Java 來實現自己所需的 authenticator。在本例中,我們將使用基於适配器的 authenticator 的默認實現,下面接着介紹基於适配器的安全驗證。

基於适配器的安全驗證簡介

在 Worklight 中,适配器用於 獲取信息和執行動作,它可以連接多種類型的後台系統,如數據庫﹑ Cast Iron 等,Worklight 可以通過調用适配器過程來連接第三方系統,並可執行相應的操作,圖一为适配器請求流程示意圖:


圖 1. 适配器請求流程示意圖
圖1  适配器請求流程示意圖

開發适配器主要就是開發适配器中的過程(procedure),過程是适配器中的核心組成部分,它提供了連接第三方系統並執行所需業務邏輯的能力。在開發時,我們需要在适配器的 XML 聲明文件中聲明該适配器中的過程,然後在 JavaScript 文件中來實現定義的過程,當然我們也可以使用 Java 來實現過程。在本例中,我們使用了 JavaScript 來實現過程。

在實際項目中,由於身份信息可能來源於第三方系統,如數據庫﹑ LDAP 服務器等,此時,我們便希望能夠使用适配器來連接第三方系統,正如前面的章節所述,Worklight 提供了基於适配器的安全驗證能力。

需要注意的是,當使用基於适配器的安全驗證方式時,我們可以在适配器中實現完整的身份驗證邏輯,而此時,login module 並不是必須的,任何其他聲明的 login module 將作为一個額外的身份驗證邏輯在适配器執行後被調用。

同其他方式的安全驗證流程類似,Worklight 在攔截到一個訪問受保護資源的請求後,會首先檢查該請求是否含有合法的用戶身份,如果是,則给予訪問權限,並返回請求的數據;如果沒有合法的用戶身份,則启動驗證流程,只有在驗證流程成功後,才能授予訪問權限,返回請求的數據。如圖 2 所示。


圖 2. 基於适配器的安全驗證處理流程圖
圖 2 基於适配器的安全驗證處理流程圖

示例介紹

基於适配器的安全驗證方式是實際項目中常會使用的一種安全驗證方式,本文给出了一個基於适配器的安全驗證的示例,並使用該安全策略來保護适配器中的過程,讀者也可以根據自己的需要使用安全策略來保護應用和靜態資源。

本文的示例分为兩個部分:适配器和客戶端應用,圖 3 给出了示例的請求處理流程,該處理流程同圖 2 所示的標准請求處理流程類似:


圖 3. 示例請求處理流程
圖 3 示例請求處理流程
  1. 請求訪問客戶端應用中的頁面 AdapterAuthentication.html
  2. 頁面中的 JavaScript 調用受保護的過程 protectResource
  3. 安全驗證框架被觸發,調用适配器中的 onAuthRequired
  4. 客戶端頁面中的 JavaScript 檢查 authRequired 屬性,判斷是否需要驗證
  5. 如已驗證,顯示請求頁面,顯示相應的 DIV 內容,並隱藏特定的 DIV 內容,如登錄輸入框。至此,請求處理流程結束。
  6. 如需要驗證,則顯示請求頁面,顯示相應的 DIV 內容,如登錄輸入框,並隱藏特定的 DIV 內容。
  7. 用戶輸入相應身份信息,並觸發身份驗證請求,相應的适配器中的過程 doLogin 將被觸發,用於驗證身份信息。
  8. 如果驗證成功,則轉至第 5 步。
  9. 如果驗證失敗,則轉至第 6 步,並顯示相應的錯誤消息。

從開發角度看,本例的開發主要分为适配器的開發和客戶端應用的開發,由於只是用於演示用途,本例並未設計复雜的業務邏輯。在适配器中,只有幾個簡單的過程;在客戶端應用中,頁面中主要分成兩塊,一塊是在驗證後才會顯示的,另一塊則在沒有驗證的時候顯示。下面我們將介紹如何開發本例及相應的 Worklight 安全驗證框架中的要點。


示例開發

配置服務器端的安全驗證策略

當安全驗證策略確定後,我們就可以着手配置安全策略,但在配置之前,我們首先需要創建一個 Worklight 項目,本示例選擇創建一個混合應用(Hybrid Application)類型的項目,在向導窗口,輸入應用名为 AdapterAuthentication,在下面的小節“開發客戶端應用”中,我們將基於這裏生成的應用做修改。


圖 4. 創建 Worklight 項目
圖 4 創建 Worklight 項目

項目創建成功後,我們可以在項目視圖中看到已經生成好的文件,打開 server->conf,我們即可找到用於安全配置的文件 authenticationConfig.xml,如圖 5 所示。


圖 5. 安全配置文件 authenticationConfig.xml
圖 5 安全配置文件 authenticationConfig.xml

正如本文在第一小節提到的,服務器端的安全配置需要配置 security test 和 realm,在服務器端,realm 由 login module 和 authenticator 組成,下面我們首先配置 login module,打開文件 authenticationConfig.xml,在節點 loginModules 加入清單 1 所示的代碼片段。


清單 1. login module 代碼片段
				  
 <loginModule name="StrongDummy"> 
 <className>com.worklight.core.auth.ext.NonValidatingLoginModule</className> 
 </loginModule> 

由於示例使用适配器來執行安全驗證邏輯,我們並不需要有額外的安全驗證邏輯,所以這裏使用了 NonValidatingLoginModule,如果這裏定義了其他的 login module,該 login module 將在适配器中的邏輯執行後被執行。

配置好了 login module,便可在同一個文件中加入 realm 的配置,找到節點 realms,並加入清單 2 所示的代碼片段。


清單 2. realm 代碼片段
				  
 <realm loginModule="StrongDummy" name="AdapterAuthRealm"> 
 <className>com.worklight.integration.auth.AdapterAuthenticator</className> 
 <parameter name="login-function" value="AuthAdapter.onAuthRequired"/> 
 <parameter name="logout-function" value="AuthAdapter.onLogout"/> 
 </realm> 

com.worklight.integration.auth.AdapterAuthenticator 为 realmAdapterAuthRealm 的 authenticator 的實現,該 authenticator 表示我們將使用适配器過程來進行安全驗證,login-function 和 logout-function 是我們 realm 中的兩個重要屬性,當 Worklight 框架偵測到一個針對受保護資源的訪問請求,會調用 login-function 中所定義的方法,當獲得一個退出登錄請求時,則調用 logout-function 中所定義的方法,可以發現屬性值的格式为“适配器名 . 過程名”。清單 2 中已經给出了示例中所用到的适配器的兩個過程,它們將在下一個小節中介紹。

現在,我們就可以在同一個配置文件中加入 security test 的配置,如清單 3 所示。


清單 3. security test 配置代碼片段
				  
 <securityTests> 
        <customSecurityTest name="AdapterAuth-securityTest"> 
            <test isInternalUserID="true" realm="AdapterAuthRealm"/> 
        </customSecurityTest> 
 </securityTests> 

在文件中,我們可以看到已經有些被注釋掉的 security test 的配置,而其中的 security test 又可以分为下面三種:

  • webSecurityTest:用於包含 Web 安全相關的 realm。
  • mobileSecurityTest:用於包含移動安全相關的 realm。
  • customSecurityTest:用於包含自定義開發的 realm。

由於示例使用了适配器來保護對受保護資源的訪問,我們這裏使用 customSecurityTest,realm 則是上面所聲明的 AdapterAuthRealm。

到這裏,我們就已經配置好了安全驗證策略,在下面的适配器開發中,我們將會使用到本節聲明的 security test 和 realm。

開發适配器

Worklight 提供了三種類型的适配器:SQL 适配器﹑ HTTP 适配器以及 Cast Iron 适配器,本例使用了 HTTP 适配器,但由於我們的适配器比較簡單,實際上並不需要連接第三方支持 HTTP 協議的系統。

在項目瀏覽器視圖中,可以找到 adapter 文件夾,我們即可選中文件夾 adapters,右鍵選中 NewWorklight Adapter,如圖 4 所示。


圖 6. 創建适配器
圖 6 創建适配器

Fig6_CreateAdapter.jpg

在彈出的窗口中,選中 Adapter type 为 HTTP Adapter,並輸入 Adapter name 为 AuthAdapter。


圖 7. 輸入适配器信息
圖 7 輸入适配器信息

選擇“Finish”,即可看到項目視圖中新生成的文件夾 AuthAdapter,該文件夾包含下面三個文件,如圖 6 所示:

  • AuthAdapter.xml,适配器的配置和聲明文件,用於配置和第三方系統連接的相關屬性及聲明該适配器中對其他應用或适配器所公開的過程。
  • AuthAdapter-impl.js,用於實現上面 XML 文件中所聲明的過程。
  • filtered.xsl,用於定義所處理的數據的 schema,由於本例較为簡單,無需對該文件做任何修改。

圖 8. 适配器文件列表
圖 8 适配器文件列表

如前文所述,本例實際上並未使用适配器連接第三方系統,所以在适配器的配置和聲明文件中,我們並無需配置 connectivity 節點,使用生成的默認值即可,目前,Worklight 的 HTTP 适配器支持 REST 和 SOAP 調用。除了 connectivity 之外,我們還在配置和聲明文件中,聲明了兩個公開方法 doLogin 和 protectResource,清單 4 给出了配置和聲明文件片段。


清單 4 配置和聲明文件片段
				  
 <connectivity> 
 <connectionPolicy xsi:type="http:HTTPConnectionPolicyType"> 
 <protocol>http</protocol> 
 <domain>rss.cnn.com</domain> 
 <port>80</port> 
 </connectionPolicy> 
 <loadConstraints maxConcurrentConnectionsPerNode="2" /> 
 </connectivity> 

 <procedure name="doLogin"/> 

 <procedure name="protectResource" securityTest="AdapterAuth-securityTest"/> 

protectResource 为受保護的過程,當有請求調用該過程時,相應的 security test 將會被調用,doLogin 則是一個公開的未受保護的過程,該過程將被客戶端頁面調用,用於驗證用戶身份。

配置好了 XML 文件之後,我們便可在 JavaScript 文件中實現所配置的公開方法,同時可以添加其他內部方法,清單 2 给出了 doLogin 方法的實現。


清單 5. doLogin 方法的實現
				  
 function doLogin(username, password){ 

 var userIdentity = { 
 userId: username, 
 displayName: username, 
 loginName: username, 
 attributes: { 
 foo: "bar"
 }, 

 isUserAuthenticated: '1'
 }; 

 //Dummy verify logic, just for demo purpose 
 if (username == password) { 
 WL.Server.setActiveUser("AdapterAuthRealm", userIdentity); 

 return { 
 authRequired: false 
 }; 
 } else { 
 return onAuthRequired(null, "User name or password is wrong."); 
 } 

 } 

示例邏輯比較簡單,當輸入的用戶名和密碼一样時,我們便認为用戶身份信息正確,同時將生成的身份對象設置为當前的活動用戶,並返回 authRequired 为 false,注意,第一個参數为上一小節安全配置文件中所聲明的 realm。如果用戶身份驗證失敗,則將調用方法 onAuthRequired,該方法亦會在 Worklight 偵測到有訪問受保護資源請求時由 Worklight 框架自動調用(見上一小節 realm 的配置),清單 6 给出了 onAuthRequired 和剩下的 2 個方法代碼。


清單 6. 适配器中的其他方法
				  
 function onAuthRequired(headers, errorMessage){ 
 errorMessage = errorMessage ? errorMessage : "Auth Required"; 

 return { 
 authRequired: true, 
 errorMessage: errorMessage 
 }; 
 } 

 function protectResource() { 

 //No function code required, this method is just used to 
 //make sure the user is authenticated 
 //Otherwise, login page will be showed. 

 } 


 function onLogout() { 
 //Put any additional log out/clean up logic here 
 //For demo purpose, no code is needed 

 } 

通過上面代碼片段,可以發現方法 protectResource 和 onLogout 是兩個空方法,在清單 4 中我們將方法 protectResource 定義为受保護的方法,所以盡管該方法中沒有邏輯,但在調用方法前,依然會進行安全檢查;onLogout 方法为 realm 的 logout-function 的屬性值,所以該方法會在用戶退出登錄時被調用,用於清除用戶數據等,出於演示用途,本例未添加任何代碼。

至此,我們的适配器就開發完畢,下面我們就可以開發客戶端應用來驗證我們所做的安全配置和适配器。

開發客戶端應用

示例的客戶端應用主要为一個 HTML 頁面,應用为登錄前或登錄後的用戶顯示同一個頁面,但頁面的內容並不一样。由於只是演示用途,我們直接基於創建項目時自動生成的應用上做定制修改,雖然創建項目時自動創建了數個文件,而我們只需要修改其中的一個 JS(AdapterAuthentication.js)文件和 HTML(AdapterAuthentication.html)文件,如圖 9 所示。


圖 9. 客戶端應用文件結構圖
圖 9 客戶端應用文件結構圖

在整個客戶端應用的開發過程中,我們主要需要做下面的工作:

  1. 修改 HTML 頁面,對已驗證的用戶和未驗證的用戶添加不同的顯示內容。
  2. 修改 JavaScript wlCommonInit 方法,用於調用受保護的過程。
  3. 在 JavaScript 文件中,增加客戶端的用戶身份信息收集和驗證請求提交代碼,其核心为 challenge handler。
  4. 在 JavaScript 文件中,增加一個新的方法 checkUser,用於檢查用戶是否已經過驗證。

HTML 頁面比較簡單,主要目的是能夠對已驗證的和未驗證的用戶提供不同的顯示內容,並能夠给未驗證用戶提供登錄框供用戶輸入用戶名和密碼。清單 7 为頁面 HTML 代碼片段。


清單 7. 頁面 HTML 代碼片段
				  
 <body onload="WL.Client.init({})" id="content" style='display: none'> 
        <div id="AppDiv"> 
 <div class="header" style="display:none"> 
 <h1>Adapter Authentication Demo</h1> 
 </div> 
            <input type="button" value="Check User" 
            onclick="checkUser()" /> 
            <input type="button" value="Logout" 
            onclick="WL.Client.logout('AdapterAuthRealm',
            {onSuccess:WL.Client.reloadApp})" /> 

        </div> 
        
        <div id="AuthDiv" style="display:none"> 
        <p id="AuthInfo"></p> 
        <hr /> 
       <input type="text" placeholder="Enter username"
        id="AuthUsername"/><br /> 
       <input type="password" placeholder="Enter password" 
       id="AuthPassword"/><br /> 
       <input type="button" value="Submit" id="AuthSubmitButton" /> 
        </div> 

 <script src="js/initOptions.js"></script> 
 <script src="js/AdapterAuthentication.js"></script> 
 <script src="js/messages.js"></script> 
 </body> 

在加載頁面的過程中,WL.Client.init({}) 會首先被調用,它將調用方法 wlCommonInit。AppDiv 區域用於提供已通過驗證的用戶所能看到的內容,AuthDiv 區域用戶顯示为登錄用戶所能看到的內容,challenge handler 的方法 handleChallenge 會設定這兩個 DIV 的 display 屬性。

wlCommonInit 方法位於文件 AdapterAuthentication.js 中,我們需要在該方法中調用前面所創建的适配器 AuthAdapter 中的受保護過程 protectResource,這样,在初始化客戶端應用頁面時,Worklight 的安全機制將會隨着受保護過程的調用而被觸發,清單 8 列出了方法 wlCommonInit。


清單 8. 方法 wlCommonInit
				  
 function wlCommonInit(){ 
 // Common initialization code goes here 
 var invocationData = { 
 adapter : "AuthAdapter", 
 procedure : "protectResource", 
 parameters : [] 
 }; 
 WL.Client.invokeProcedure(invocationData,{}); 
  
 } 

在 AdapterAuthentication.js 文件中,還需要添加核心的客戶端安全驗證代碼,其核心主要是獲取和使用 challenge handler


清單 9. 獲取和使用 challenge handler
				  
 var authChallengeHandler = WL.Client.createChallengeHandler("AdapterAuthRealm"); 

 authChallengeHandler.isCustomResponse = function(response) { 

 if (!response || !response.responseJSON|| response.responseText === null) { 
 return false; 
 } 
 if (typeof(response.responseJSON.authRequired) !== 'undefined'){ 
 return true; 
 } else { 
 return false; 
 } 

 }; 

 authChallengeHandler.handleChallenge = function(response){ 
 var authRequired = response.responseJSON.authRequired; 

 if (authRequired == true){ 
 $("#AppDiv").hide(); 
 $("#AuthDiv").show(); 
 $("#AuthPassword").empty(); 
 $("#AuthInfo").empty(); 

 if (response.responseJSON.errorMessage) 
    $("#AuthInfo").html(new Date() + " :: " + response.responseJSON.errorMessage); 

 } else if (authRequired == false){ 
 $("#AppDiv").show(); 
 $("#AuthDiv").hide(); 
 authChallengeHandler.submitSuccess(); 
 } 
 }; 

WL.Client.createChallengeHandler("AdapterAuthRealm") 用於獲取 challenge handler,参數为我們所要使用的 realm。Challenge handler 的方法 isCustomResponse 會在每次收到服務器端響應的時候被調用,用於檢查是不是需要對該響應做安全驗證,當該方法返回为 true 時,意味着 Worklight 框架需要對該響應做安全驗證,另一個方法 handleChallenge 隨即被調用,用於在需要安全驗證的情況下,顯示用戶身份信息輸入選項,並顯示相應的錯誤消息;當返回为 false 時,則意味着用戶已通過安全驗證,只需調用 submitSuccess 方法來通知 Worklight 框架,表示用戶已通過安全驗證。

那麼 authRequired 的初始值又是如何被設为 true 的呢?當受保護的資源被請求時,Worklight 安全機制隨即被觸發,realm 配置中所配置的 login-function 將被調用,如清單 6 所示,onAuthRequired 方法會將 authRequired 值設为 true,而在安全驗證成功後,該方法則不會繼續被調用。清單 10 给出了本例的安全配置片段,詳細內容會在下面的小節介紹。


清單 10. 安全配置片段
				  
 <realm loginModule="StrongDummy" name="AdapterAuthRealm"> 
 <className>com.worklight.integration.auth.AdapterAuthenticator</className> 
 <parameter name="login-function" value="AuthAdapter.onAuthRequired"/> 
 <parameter name="logout-function" value="AuthAdapter.onLogout"/> 
 </realm> 

本文的主要目的是介紹基於适配器的安全驗證,其核心點之一便是如何調用适配器做安全驗證,而調用代碼也需要添加在 AdapterAuthentication.js 中,清單 11 给出了相應的代碼片段。


清單 11. 調用适配器觸發驗證邏輯
				  
 $("#AuthSubmitButton").bind('click', function () { 
 var username = $("#AuthUsername").val(); 
 var password = $("#AuthPassword").val(); 

 var invocationData = { 
 adapter : "AuthAdapter", 
 procedure : "doLogin", 
 parameters : [ username, password ] 
 }; 

 authChallengeHandler.submitAdapterAuthentication(invocationData, {}); 
 }); 

上面的代碼主要做了三件事:

  1. 为按鍵的 click 事件绑定 JavaScript 方法,當用戶單擊頁面登錄按鍵時,绑定的 JavaScript 方法將被觸發。
  2. 獲取頁面中的用戶名和密碼信息
  3. 獲取适配器 AuthAdapter 中的 doLogin 對象,並通過執行 submitAdapterAuthentication 來調用适配器中的過程來執行驗證邏輯。

至此,我們已經完成了本例的開發工作,下面我們將部署和運行本例。


部署和運行示例

由於我們使用了适配器,所以我們首先需要部署适配置,在項目視圖中,右鍵單擊适配器的文件夾,選中“Run As->Deploy Worklight Adapter”,如圖 10 所示。


圖 10. 部署适配器
圖 10 部署适配器

在控制台視圖中,如果看到“Adapter deployed successfully”,則表示适配器已經部署成功,如圖 11 所示。


圖 11. 控制台視圖
圖 11 控制台視圖

同部署适配器類似,我們還需要部署客戶端應用,找到應用的文件夾,右鍵單擊,選中“Run As->Build All and Deploy”,如圖 12 所示。


圖 12. 部署客戶端應用
圖 12 部署客戶端應用

同样,如果控制台視圖提示“Application 'AdapterAuthentication' deployed successfully with all environments”,則表明應用已經部署成功,如圖 13 所示。


圖 13. 部署客戶端應用控制台
圖 13 部署客戶端應用控制台

最後,我們便可以通過控制台(localhost:8080/console)來預覽我們的示例,圖 14 为初次訪問的示例頁面。


圖 14. 示例初次訪問頁面
圖 14 示例初次訪問頁面

因为用戶還沒登錄,而我們在頁面初始化過程中已經調用了受保護的适配器過程,所以 onAuthRequired 方法被觸發,從而在頁面上部顯示提示消息,提示用戶需要登錄,當用戶輸入正確的用戶和密碼(這裏只需要用戶名等於密碼),即可顯示已驗證用戶所能看到的內容,如圖 14 所示。


圖 15. 授權用戶顯示頁面
圖 14 授權用戶顯示頁面

用戶即使再次刷新頁面,頁面內容也不會變化,因为我們在适配器過程 doLogin 已經將 authRequired 設为 false,這样 Worklight 框架會認为後續請求已經經過安全驗證,無需再次進行安全驗證,當點擊“退出”按鍵後,則會回到未驗證狀態,並顯示未驗證的頁面。


總結

Worklight 提供了多種安全驗證方式,本文選擇介紹了常用的一種方式:基於适配器的驗證方式,本文通過一個簡單的示例,介紹了基於适配器的安全驗證的基本概念,同時介紹了基本的開發步驟以及如何在 Eclipse 中做相關開發,我們在掌握了開發本文示例的基礎上,可以根據項目的需要,做進一步的定制開發。


参考資料

學習

獲得產品和技術

討論

作者簡介

嶽泓,就職於 IBM 中國開發中心,有多年 J2EE、BPM、SAP 項目開發和實施經驗,目前關注 SAP 和手機開發相關項目。

仲光慶,就職於 IBM 中國開發中心,有多年 J2EE 和 SAP 相關的項目開發和實施經驗,目前關注 SAP 和雲相關項目。

石廣,就職於 IBM 中國開發中心,有多年 J2EE 項目開發和實施經驗,目前關注 SAP 和手機開發相關項目。

頂一下
(0)
0%
踩一下
(0)
0%
------分隔線----------------------------
發表評論
請自覺遵守互聯網相關的政策法規,嚴禁發布色情、暴力、反動的言論。
評價:
表情:
驗證碼:點擊我更換圖片
欄目列表
推薦內容