2014-01-20 3 views
3

Android를 통해 CAS 시스템에 로그인하려고하고 있는데 어떻게 접근해야할지 모르겠습니다.Android에서 웹 CAS 인증

This stackoverflow link 비슷한 것을 이야기하지만 문제의 해결책을 이해하지 못했습니다. 인증 프로토콜과 HTTP에 대한 사전 경험이 없습니다. 나는 모든 도움에 감사 할 것입니다!

EDIT : GitHub에서 안드로이드 용 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 인증 서블릿에 전달해야합니다. 티켓이 만료 될 수 있으며이 경우 CAS 서버에서 마지막 티켓과 현재 티켓을 덮어 써야하는 새 티켓을 보내드립니다.

link에는 CAS의 작동 방식 (기본적으로 워크 플로)과 here에 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가 제공됩니다.

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

: 다음 당신은 ch.boye.httpclientandroidlib 원래 저자가 제공하는 예제를 사용

킵에 의해 org.apache.http의 모든 수입을 대체하지만, 다른 옵션을 사용하여 세션 객체를 생성해야 이것은 나를 위해 일했다.

관련 문제