2012年3月15日 星期四

Implement the RESTful CAS API

說明Jasig CAS如何實作RESTful CAS API,讓Application可透過程式向CAS Server取得認證,但這部份的認證不具備Single Sign On的功能,只是單純的帳號/密碼認證。

準備執行環境

除了Maven外其他為測試時會用到的環境
  • JDK (Java Development Kit) version 1.6+
  • Apache Maven 2.2.1+
  • Apache Tomcat 6+
  • JA-SIG CAS 3.4.2.1+

下載CAS integration RESTful Library
  • cas-server-integration-restlet-3.4.3.jar可在cas-server-3.4.3-release.zip解壓縮後的路徑cas-server-3.4.3\cas-server-integration-restlet下由maven build(mvn clean package -Dmaven.test.skip=true)取得,另外此路徑cas-server-3.4.3\modules下也可取得這個檔案,此測試由maven build取得。

下載RESTful API 相關 Library,以下jar可由maven.restlet.org取得
  • com.noelios.restlet.ext.servlet-1.1.10.jar
  • com.noelios.restlet.ext.spring-1.1.10.jar
  • com.noelios.restlet-1.1.10.jar
  • org.restlet.ext.spring-1.1.10.jar
  • org.restlet-1.1.10.jar

取得Code Generation Library,可能版本有衝突直接download的無法使用
  • net.sf.cglib.jar可在Restlet Framework下載restlet-jse-2.0.10.zip後解壓縮可在此restlet-jse-2.0.10\lib\net.sf.cglib_2.2路徑取得

將上述取得的.jar檔放到Tomcat6\webapps\cas\WEB-INF\lib,.jar檔有試圖使用其他版本但目前測試結果就這些版本可配置成功。




CAS Server web.xml config Restlet

    <!--
        CAS RESTful API is configured
    -->
    <servlet>
        <servlet-name>restlet</servlet-name>
        <servlet-class>com.noelios.restlet.ext.spring.RestletFrameworkServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>restlet</servlet-name>
        <url-pattern>/v1/*</url-pattern>
    </servlet-mapping>


配置完成則啟動Tomcat

Java REST Client Example

此範例由RESTful API提供,有做些微修改以利測試
import java.io.IOException;
import java.util.logging.Logger;
import java.util.logging.FileHandler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.methods.PostMethod;

/**
 * An example Java client to authenticate against CAS using REST services.
 * Please ensure you have followed the necessary setup found on the wiki.
 *
 * @author jesse lauren farinacci
 * @since 3.4.2
 */
public final class CASHttpclient
{
  private static final Logger LOG = Logger.getLogger(CASHttpclient.class.getName());

  private CASHttpclient()
  {
    // static-only access
  }

  public static String getTicket(final String server, final String username,
      final String password, final String service)
  {
    notNull(server, "server must not be null");
    notNull(username, "username must not be null");
    notNull(password, "password must not be null");
    notNull(service, "service must not be null");

    return getServiceTicket(server, getTicketGrantingTicket(server, username,
        password), service);
  }

  private static String getServiceTicket(final String server,
      final String ticketGrantingTicket, final String service)
  {
    if (ticketGrantingTicket == null)
      return null;

    final HttpClient client = new HttpClient();

    final PostMethod post = new PostMethod(server + "/" + ticketGrantingTicket);

    post.setRequestBody(new NameValuePair[] { new NameValuePair("service",
        service) });

    try
    {
      client.executeMethod(post);

      final String response = post.getResponseBodyAsString();

      switch (post.getStatusCode())
      {
        case 200:
          return response;

        default:
          LOG.warning("Invalid response code (" + post.getStatusCode()
              + ") from CAS server!");
          LOG.info("Response (1k): "
              + response.substring(0, Math.min(1024, response.length())));
          break;
      }
    }

    catch (final IOException e)
    {
      LOG.warning(e.getMessage());
    }

    finally
    {
      post.releaseConnection();
    }

    return null;
  }

  private static String getTicketGrantingTicket(final String server,
      final String username, final String password)
  {
    final HttpClient client = new HttpClient();

    final PostMethod post = new PostMethod(server);

    post.setRequestBody(new NameValuePair[] {
        new NameValuePair("username", username),
        new NameValuePair("password", password) });

    try
    {
      client.executeMethod(post);

      final String response = post.getResponseBodyAsString();

      switch (post.getStatusCode())
      {
        case 201:
        {
          final Matcher matcher = Pattern.compile(".*action=\".*/(.*?)\".*")
              .matcher(response);

          if (matcher.matches())
            return matcher.group(1);

          LOG
              .warning("Successful ticket granting request, but no ticket found!");
          LOG.info("Response (1k): "
              + response.substring(0, Math.min(1024, response.length())));
          break;
        }

        default:
          LOG.warning("Invalid response code (" + post.getStatusCode()
              + ") from CAS server!");
          LOG.info("Response (1k): "
              + response.substring(0, Math.min(1024, response.length())));
          break;
      }
    }

    catch (final IOException e)
    {
      LOG.warning(e.getMessage());
    }

    finally
    {
      post.releaseConnection();
    }

    return null;
  }

  private static void notNull(final Object object, final String message)
  {
    if (object == null)
      throw new IllegalArgumentException(message);
  }

  public static void main(final String[] args)
  {
    final String server = "http://localhost:8080/cas/v1/tickets";
    final String username = "scott";
    final String password = "tiger";
    final String service = "http://localhost:8080/CASClient/secured/personal_info.jsp";

    try {
     FileHandler fileHandler = new FileHandler("CASHttpclient.log");
      LOG.addHandler(fileHandler);
    } catch (SecurityException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }

    LOG.info(getTicket(server, username, password, service));
  }
}

Compiler & Run CASHttpclient.java

CAS RESTful API驗證流程


測試結果

取得認證成功結果如下,PostMethod.getStatusCode()=200,並取得Ticket line-13
<?xml version="1.0" encoding="x-windows-950" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2012-03-15T20:01:24</date>
  <millis>1331812884249</millis>
  <sequence>0</sequence>
  <logger>CASHttpclient</logger>
  <level>INFO</level>
  <class>CASHttpclient</class>
  <method>main</method>
  <thread>10</thread>
  <message>ST-2-clxIV2vf9M0ylP7z3Pl2-cas</message>
</record>
</log>

另外認證成功後也有request service url,此url為需要CAS 認證的頁面


取得認證失敗結果如下
<?xml version="1.0" encoding="x-windows-950" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2012-03-15T20:09:07</date>
  <millis>1331813347545</millis>
  <sequence>0</sequence>
  <logger>CASHttpclient</logger>
  <level>WARNING</level>
  <class>CASHttpclient</class>
  <method>getTicketGrantingTicket</method>
  <thread>10</thread>
  <message>Invalid response code (400) from CAS server!</message>
</record>
<record>
  <date>2012-03-15T20:09:07</date>
  <millis>1331813347579</millis>
  <sequence>1</sequence>
  <logger>CASHttpclient</logger>
  <level>INFO</level>
  <class>CASHttpclient</class>
  <method>getTicketGrantingTicket</method>
  <thread>10</thread>
  <message>Response (1k): 
 <html>
<head>
   <title>Status page</title>
</head>
<body>
<h3>error.authentication.credentials.bad</h3><p>You can get technical details <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1">here</a>.<br>
Please continue your visit at our <a href="/">home page</a>.
</p>
</body>
</html>
</message>
</record>
<record>
  <date>2012-03-15T20:09:07</date>
  <millis>1331813347600</millis>
  <sequence>2</sequence>
  <logger>CASHttpclient</logger>
  <level>INFO</level>
  <class>CASHttpclient</class>
  <method>main</method>
  <thread>10</thread>
</record>
</log>


Get CAS User

Jasig CAS 提供以下方式透過Service Ticket 取得CAS User,service為CAS Client URL,ticket為Service Ticket
http://localhost:8080/cas/serviceValidate?service=http://localhost:8080/CASClient/secured/personal_info.jsp&ticket=ST-2-clxIV2vf9M0ylP7z3Pl2-cas

結果為
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
   <cas:authenticationSuccess>
      <cas:user>scott</cas:user>
   </cas:authenticationSuccess>
</cas:serviceResponse>



相關設定可參考:
CAS 之 集成RESTful API
CAS RESTful API 開發文件
RESTful API

沒有留言:

張貼留言