2014-01-20 43 views
3

我想通過Android登錄到CAS系統,我不知道如何處理這個問題。Android上的Web CAS認證

This stackoverflow鏈接談論類似的東西,但我無法理解問題的解決方案。我在身份驗證協議和HTTP方面沒有任何經驗。我會感謝任何和所有的幫助!

編輯:我能夠在GitHub上找到適用於Android的CAS客戶端,並試圖使用它來查看我是否可以正確進行身份驗證。不幸的是,我仍然有問題。當我執行登錄()命令我得到以下錯誤:

01-20 16:47:19.322: D/CASCLIENT(22682): Ready to get LT from https://www.purdue.edu/apps/account/cas/login?service=http://watcher.rcac.purdue.edu/nagios 
01-20 16:47:21.825: D/CASCLIENT(22682): Response = HTTP/1.1 200 OK 
01-20 16:47:21.875: D/CASCLIENT(22682): LT=LT-137794-1UkrL1jXJGPMZfuuVDn4RXbcQ3kfCQ 
01-20 16:47:21.875: D/CASCLIENT(22682): POST https://www.purdue.edu/apps/account/cas/login?service=http://watcher.rcac.purdue.edu/nagios 
01-20 16:47:23.186: D/CASCLIENT(22682): POST RESPONSE STATUS=200 : HTTP/1.1 200 OK 
01-20 16:47:23.186: I/CASCLIENT(22682): Authentication to service 'http://watcher.rcac.purdue.edu/nagios' unsuccessul for username . 

這裏是CAS客戶端代碼:

public class CasClient 
{ 
    private static final String TAG = "CASCLIENT"; 
    private static final String CAS_LOGIN_URL_PART = "login"; 
    private static final String CAS_LOGOUT_URL_PART = "logout"; 
    private static final String CAS_SERVICE_VALIDATE_URL_PART = "serviceValidate"; 
    private static final String CAS_TICKET_BEGIN = "ticket="; 
    private static final String CAS_LT_BEGIN = "name=\"lt\" value=\""; 
    private static final String CAS_USER_BEGIN = "<cas:user>"; 
    private static final String CAS_USER_END = "</cas:user>"; 

    /** 
    * An HTTP client (browser replacement) that will interact with the CAS server. 
    * Usually provided by the user, as it is this client that will be "logged in" to 
    * the CAS server. 
    */ 
    private HttpClient httpClient; 
    /** 
    * This is the "base url", or the root URL of the CAS server that is will be 
    * providing authentication services. If you use <code>http://x.y.z/a/login</code> to login 
    * to your CAS, then the base URL is <code>http://x.y.z/a/"</code>. 
    */ 
    private String casBaseURL; 

    /** 
    * Construct a new CasClient which uses the specified HttpClient 
    * for its HTTP calls. If the CAS authentication is successful, it is the supplied HttpClient to 
    * which the acquired credentials are attached. 
    * 
    * @param httpClient The HTTP client ("browser replacement") that will 
    *   attempt to "login" to the CAS. 
    * @param casBaseUrl The base URL of the CAS service to be used. If you use 
    *   <code>http://x.y.z/a/login</code> to login to your CAS, then the base URL 
    *   is <code>http://x.y.z/a/"</code>. 
    */ 
    public CasClient (HttpClient httpClient, String casBaseUrl) 
    { 
     this.httpClient = httpClient; 
     this.casBaseURL = casBaseUrl; 
    } 

    /** 
    * Authenticate the specified user credentials and request a service ticket for the 
    * specified service. If no service is specified, user credentials are checks but no 
    * service ticket is generated (returns null). 
    * 
    * @param serviceUrl The service to login for, yielding a service ticket that can be 
    *   presented to the service for validation. May be null, in which case the 
    *   user credentials are validated, but no service ticket is returned by this method. 
    * @param username 
    * @param password 
    * @return A valid service ticket, if the specified service URL is not null and the 
    *   (login; password) pair is accepted by the CAS server 
    * @throws CasAuthenticationException if the (login; password) pair is not accepted 
    *   by the CAS server. 
    * @throws CasProtocolException if there is an error communicating with the CAS server 
    */ 
    public String login (String serviceUrl, String username, String password) throws CasAuthenticationException, CasProtocolException 
    { 
     String serviceTicket = null; 
     // The login method simulates the posting of the CAS login form. The login form contains a unique identifier 
     // or "LT" that is only valid for 90s. The method getLTFromLoginForm requests the login form from the cAS 
     // and extracts the LT that we need. Note that the LT is _service specific_ : We need to use an identical 
     // serviceUrl when retrieving and posting the login form. 
     String lt = getLTFromLoginForm (serviceUrl); 
     if (lt == null) 
     { 
      Log.d (TAG, "Cannot retrieve LT from CAS. Aborting authentication for '" + username + "'"); 
      throw new CasProtocolException ("Cannot retrieve LT from CAS. Aborting authentication for '" + username + "'"); 
     } 
     else 
     { 
      // Yes, it is necessary to include the serviceUrl as part of the query string. The URL must be 
      // identical to that used to get the LT. 
      Log.d(TAG,"POST " + casBaseURL + CAS_LOGIN_URL_PART + "?service=" + serviceUrl); 
      HttpPost httpPost = new HttpPost (casBaseURL + CAS_LOGIN_URL_PART + "?service=" + serviceUrl); 
      try 
      { 
       // Add form parameters to request body 
       List <NameValuePair> nvps = new ArrayList <NameValuePair>(); 
       nvps.add(new BasicNameValuePair ("_eventId", "submit")); 
       nvps.add(new BasicNameValuePair ("username", username)); 
       nvps.add(new BasicNameValuePair ("gateway", "true")); 
       nvps.add(new BasicNameValuePair ("password", password)); 
       nvps.add(new BasicNameValuePair ("lt", lt)); 
       httpPost.setEntity(new UrlEncodedFormEntity(nvps)); 

       // execute post method  
       HttpResponse response = httpClient.execute(httpPost); 
       Log.d (TAG, "POST RESPONSE STATUS=" + response.getStatusLine().getStatusCode() + " : " + response.getStatusLine().toString()); 

       //TODO It would seem that when the client is already authenticated, the CAS server 
       // redirects transparently to the service URL! 
       // Success if CAS replies with a 302 HTTP status code and a Location header 
       // We assume that if a valid ticket is provided in the Location header, that it is also a 302 HTTP STATUS 
       Header headers[] = response.getHeaders("Location"); 
       if (headers != null && headers.length > 0) 
        serviceTicket = extractServiceTicket (headers[0].getValue()); 
       HttpEntity entity = response.getEntity(); 
       entity.consumeContent(); 

       if (serviceTicket == null) 
       { 
        Log.i (TAG, "Authentication to service '" + serviceUrl + "' unsuccessul for username '" + username + "'."); 
        throw new CasAuthenticationException ("Authentication to service '" + serviceUrl + "' unsuccessul for username '" + username + "'."); 
       } 
       else 
        Log.i (TAG, "Authentication to service '" + serviceUrl + "' successul for username '" + username + "'."); 
      } 
      catch (IOException e) 
      { 
       Log.d (TAG, "IOException trying to login : " + e.getMessage()); 
       throw new CasProtocolException ("IOException trying to login : " + e.getMessage()); 
      } 
      return serviceTicket; 
     } 
    } 

    /** 
    * Logout from the CAS. This destroys all local authentication cookies 
    * and any tickets stored on the server. 
    * 
    * @return <code>true</false> if the logout is acknowledged by the CAS server 
    */ 
    public boolean logout() 
    { 
     boolean logoutSuccess = false; 
     HttpGet httpGet = new HttpGet (casBaseURL + CAS_LOGOUT_URL_PART); 
     try 
     { 
      HttpResponse response = httpClient.execute(httpGet); 
      logoutSuccess = (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK); 
      Log.d (TAG, response.getStatusLine().toString()); 
     } 
     catch (Exception e) 
     { 
      Log.d(TAG, "Exception trying to logout : " + e.getMessage()); 
      logoutSuccess = false; 
     } 
     return logoutSuccess; 
    } 

    /** 
    * Validate the specified service ticket against the specified service. 
    * If the ticket is valid, this will yield the clear text user name 
    * of the authenticated user. 
    * 
    * Note that each service ticket issued by CAS can be used exactly once 
    * to validate. 
    * 
    * @param serviceUrl The serviceUrl to validate against 
    * @param serviceTicket The service ticket (previously provided by the CAS) for the serviceUrl 
    * @return Clear text username of the authenticated user. 
    * @throws CasProtocolException if a protocol or communication error occurs 
    * @throws CasClientValidationException if the CAS server refuses the ticket for the service 
    */ 
    public String validate (String serviceUrl, String serviceTicket) throws CasAuthenticationException, CasProtocolException 
    { 
     HttpPost httpPost = new HttpPost (casBaseURL + CAS_SERVICE_VALIDATE_URL_PART); 
     Log.d(TAG, "VALIDATE : " + httpPost.getRequestLine()); 
     String username = null; 
     try 
     { 
      List <NameValuePair> nvps = new ArrayList <NameValuePair>(); 
      nvps.add(new BasicNameValuePair ("service", serviceUrl)); 
      nvps.add(new BasicNameValuePair ("ticket", serviceTicket)); 
      httpPost.setEntity (new UrlEncodedFormEntity(nvps)); 
      HttpResponse response = httpClient.execute (httpPost); 
      Log.d (TAG, "VALIDATE RESPONSE : " + response.getStatusLine().toString()); 
      int statusCode = response.getStatusLine().getStatusCode(); 
      if (statusCode != HttpStatus.SC_OK) 
      { 
       Log.d (TAG,"Could not validate: " + response.getStatusLine()); 
       throw new CasAuthenticationException("Could not validate service: " + response.getStatusLine()); 
      } 
      else 
      { 
       HttpEntity entity = response.getEntity(); 
       username = extractUser (entity.getContent()); 
       Log.d (TAG, "VALIDATE OK YOU ARE : " + username); 
       entity.consumeContent(); 
      } 
     } 
     catch (Exception e) 
     { 
      Log.d (TAG, "Could not validate: " + e.getMessage()); 
      throw new CasProtocolException ("Could not validate : " + e.getMessage()); 
     } 
     return username; 
    } 

    /** 
    * This method requests the original login form from CAS. 
    * This form contains an LT, an initial token that must be 
    * presented to CAS upon sending it an authentication request 
    * with credentials. 
    * 
    * If the (optional) service URL is provided, this method 
    * will construct the URL such that CAS will correctly authenticate 
    * against the specified service when a subsequent authentication request 
    * is sent (with the login method). 
    * 
    * @param serviceUrl 
    * @return The LT token if it could be extracted from the CAS response, else null. 
    */ 
    protected String getLTFromLoginForm (String serviceUrl) 
    { 
     HttpGet httpGet = new HttpGet (casBaseURL + CAS_LOGIN_URL_PART + "?service=" + serviceUrl); 

     String lt = null; 
     try 
     { 
      Log.d (TAG, "Ready to get LT from " + casBaseURL + CAS_LOGIN_URL_PART + "?service=" + serviceUrl); 
      HttpResponse response = httpClient.execute (httpGet); 
      Log.d (TAG, "Response = " + response.getStatusLine().toString()); 
      if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) 
      { 
       Log.d(TAG,"Could not obtain LT token from CAS: " + response.getStatusLine().getStatusCode() + "/" + response.getStatusLine()); 
      } 
      else 
      { 
       HttpEntity entity = response.getEntity(); 
       if (entity != null) lt = extractLt (entity.getContent()); 
       entity.consumeContent(); 
       Log.d (TAG, "LT=" + lt); 
      } 
     } 
     catch (ClientProtocolException e) 
     { 
      Log.d(TAG, "Getting LT client protocol exception", e); 
     } 
     catch (IOException e) 
     { 
      Log.d(TAG, "Getting LT io exception",e); 
     } 

     return lt; 
    } 

    /** 
    * Helper method to extract the user name from a "service validate" call to CAS. 
    * 
    * @param data Response data. 
    * @return The clear text username, if it could be extracted, null otherwise. 
    */ 
    protected String extractUser (InputStream dataStream) 
    { 
     BufferedReader reader = new BufferedReader (new InputStreamReader(dataStream)); 
     String user = null; 
     try 
     { 
      String line = reader.readLine(); 
      while (user == null && line != null) 
      { 
       int start = line.indexOf (CAS_USER_BEGIN); 
       if (start >= 0) 
       { 
        start += CAS_USER_BEGIN.length(); 
        int end = line.indexOf(CAS_USER_END, start); 
        user = line.substring (start, end); 
       } 
       line = reader.readLine(); 
      } 
     } 
     catch (IOException e) 
     { 
      Log.d (TAG, e.getLocalizedMessage()); 
     } 
     return user; 
    } 

    /** 
    * Helper method to extract the service ticket from a login call to CAS. 
    * 
    * @param data Response data. 
    * @return The service ticket, if it could be extracted, null otherwise. 
    */ 
    protected String extractServiceTicket (String data) 
    { 
     Log.i(TAG, "ST DATA: " +data); 
     String serviceTicket = null; 
     int start = data.indexOf(CAS_TICKET_BEGIN); 
     if (start > 0) 
     { 
      start += CAS_TICKET_BEGIN.length(); 
      serviceTicket = data.substring (start); 
     } 
     return serviceTicket; 
    } 


    /** 
    * Helper method to extract the LT from the login form received from CAS. 
    * 
    * @param data InputStream with HTTP response body. 
    * @return The LT, if it could be extracted, null otherwise. 
    */ 
    protected String extractLt (InputStream dataStream) 
    { 
     BufferedReader reader = new BufferedReader (new InputStreamReader(dataStream)); 
     String token = null; 
     try 
     { 
      String line = reader.readLine(); 
      while (token == null && line != null) 
      { 
       int start = line.indexOf (CAS_LT_BEGIN); 
       if (start >= 0) 
       { 
        start += CAS_LT_BEGIN.length(); 
        int end = line.indexOf("\"", start); 
        token = line.substring (start, end); 
       } 
       line = reader.readLine(); 
      } 
     } 
     catch (IOException e) 
     { 
      Log.d (TAG, e.getMessage()); 
     } 
     return token; 
    } 

} 

這裏是我的活動從我調用CAS客戶端。

public class MainActivity extends Activity { 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     HttpClient client = new DefaultHttpClient(); 


     StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build(); 

     StrictMode.setThreadPolicy(policy); 
     CasClient c = new CasClient(client,"https://www.purdue.edu/apps/account/cas/"); 
     try { 
      c.login("http://watcher.rcac.purdue.edu/nagios", "0025215948", "scholar1234"); 
     } catch (CasAuthenticationException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } catch (CasProtocolException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 
    } 


    @Override 
    public boolean onCreateOptionsMenu(Menu menu) { 
     // Inflate the menu; this adds items to the action bar if it is present. 
     getMenuInflater().inflate(R.menu.main, menu); 
     return true; 
    } 

} 

回答

2

CAS認證的思想本身並不困難,但實施它沒有HTTP經驗可能會使其複雜化。它基於票據,所以當你想在網站中進行身份驗證時,你會被重定向到CAS站點的登錄門戶,你必須輸入你的憑證並進行驗證。當然,如果他們不匹配,你會得到一個錯誤,否則生成一個TGT(Ticket Granting Ticket),並返回給你的客戶。因此,每次執行需要進行身份驗證的操作時,您都必須獲得此憑單並將其傳遞給CAS身份驗證Servlet。該票證可能會過期,在這種情況下,CAS服務器將向您發送一張新票據,該票據必須覆蓋最後一張,這是您需要提供的票據。

在這link你有一個關於CAS工作原理的詳細解釋(基本上是工作流程),並且你有一個Java示例和部分實現。

+0

謝謝你的解釋。我開始嘗試登錄並試圖獲得服務票。但是,我遇到了一些問題。我上面發佈了我的代碼。 – AndroidDev93

2

我得到了同樣的問題。 我認爲在github上找到的代碼是爲sdk的舊版本設計的,基本上,問題在於測試用戶是否真的登錄:CAS服務器使用302進行響應,其中包含,服務的位置。 但代碼

httpClient.execute(httpPost) 

遵循重定向和服務的狀態加入其200響應。這裏沒有更多的位置,並且代碼認爲登錄失敗...

編輯: 我發現獲取代碼的運行方式:

使用替代罐子,而不是捆綁(老)org.apache.http:http://code.google.com/p/httpclientandroidlib/,至少4.3版本。

他們發佈了jar格式的org.apache.http的最新穩定版本。 接下來,您必須通過使用ch.boye.httpclientandroidlib由原作者提供的示例

保持以取代org.apache.http全部進口,但是,不同的選項來創建HttpClient的:

HttpClient httpClient = HttpClientBuilder.create().setRedirectStrategy(new DefaultRedirectStrategy()).build(); 

這對我有效。