There's a variety of clients for CAS. The Java-based clients (JA-SIG, Yale, see JA-SIG website) typically handle the browser-based client interaction with CAS very well through ServletFilter implementations.
Now what about programmatic authentication, i.e. achieving authentication through non-browser based applications? There exists a CAS .NET client but I did not manage to find the appropriate Java implementation. So here goes - it is based on the Apache HttpClient.
In case I missed any existing implementation achieving the same purpose, let's look at the bright side: at least now I understand the CAS protocol :-)
My CAS client works within any application. It uses the HttpClient and behaves like a browser client as CAS requires cookie support.
Here's the code:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
import
org.apache.commons.httpclient.Header;
import
org.apache.commons.httpclient.HttpClient;
import
org.apache.commons.httpclient.HttpMethod;
import
org.apache.commons.httpclient.HttpStatus;
import
org.apache.commons.httpclient.methods.GetMethod;
import
org.apache.commons.httpclient.methods.PostMethod;
import
org.apache.log4j.Logger;
/**
* The CasClient allows users to programmatically login
* to CAS protected services based on the CAS 2 protocol.
* This client behaves like a browser-client in terms of
* cookie handling.<br>
*
*
@author
Mathias Richter
*/
public
class
CasClient
{
public
static
Logger LOG
=
Logger.getLogger( CasClient.
class
);
public
static
final
String LOGIN_URL_PART
=
"
login
"
;
public
static
final
String SERVICE_VALIDATE_URL_PART
=
"
serviceValidate
"
;
public
static
final
String TICKET_BEGIN
=
"
ticket=
"
;
private
static
final
String LT_BEGIN
=
"
name=
"
lt
"
value=
""
;
public
static
final
String CAS_USER_BEGIN
=
"
<cas:user>
"
;
public
static
final
String CAS_USER_END
=
"
</cas:user>
"
;
private
HttpClient fClient;
private
String fCasUrl;
/**
* Construct a new CasClient.
*
*
@param
casUrl The base URL of the CAS service to be used.
*/
public
CasClient( String casBaseUrl )
{
this
(
new
HttpClient(), casBaseUrl );
}
/**
* Construct a new CasClient which uses the specified HttpClient
* for its HTTP calls.
*
*
@param
client
*
@param
casBaseUrl
*/
public
CasClient( HttpClient client, String casBaseUrl )
{
fClient
=
client;
fCasUrl
=
casBaseUrl;
}
/**
* Authenticate the specified username with the specified password.
* This will not yield any ticket, as no service is authenticated
* against. This wil just set the CAS cookie in this client upon
* successful authentication.
*
*
@param
username
*
@param
password
*/
public
void
authenticate( String username, String password )
{
authenticate(
null
, username, password );
}
/**
* Validate the specified service ticket against the specified service.
* If the ticket is valid, this will yield the clear text user name
* of the autenticated user.<br>
* Note that each service ticket issued by CAS can be used exactly once
* to validate.
*
*
@param
serviceUrl
*
@param
serviceTicket
*
*
@return
Clear text username of the authenticated user.
*/
public
String validate( String serviceUrl, String serviceTicket )
{
String result
=
null
;
PostMethod method
=
new
PostMethod( fCasUrl
+
SERVICE_VALIDATE_URL_PART );
method.setParameter(
"
service
"
, serviceUrl );
method.setParameter(
"
ticket
"
, serviceTicket );
try
{
int
statusCode
=
fClient.executeMethod(method);
if
(statusCode
!=
HttpStatus.SC_OK)
{
LOG.error(
"
Could not validate:
"
+
method.getStatusLine() );
method.releaseConnection();
}
else
{
result
=
extractUser(
new
String( method.getResponseBody() ) );
}
}
catch
( Exception x )
{
LOG.error(
"
Could not validate:
"
+
x.toString () );
x.printStackTrace();
}
method.releaseConnection();
return
result;
}
/**
* Authenticate the specified user with the specified password against the
* specified service.
*
*
@param
serviceUrl May be null. If a url is specified, the authentication will happen against this service, yielding a service ticket which can be validated.
*
@param
username
*
@param
password
*
@return
A valid service ticket, if and only if the specified service URL is not null.
*/
public
String authenticate( String serviceUrl, String username, String password )
{
String lt
=
getLt( serviceUrl );
if
( lt
==
null
)
{
LOG.error(
"
Cannot retrieve LT from CAS. Aborting authentication for '
"
+
username
+
"
'
"
);
return
null
;
}
String result
=
null
;
PostMethod method
=
new
PostMethod( fCasUrl
+
LOGIN_URL_PART );
if
( serviceUrl
!=
null
)
//
optional
method.setParameter(
"
service
"
, serviceUrl );
method.setParameter(
"
_eventId
"
,
"
submit
"
);
method.setParameter(
"
username
"
, username );
method.setParameter(
"
password
"
, password );
method.setParameter(
"
lt
"
, lt );
method.setParameter(
"
gateway
"
,
"
true
"
);
try
{
fClient.executeMethod(method);
if
( serviceUrl
==
null
)
{
if
( extractLt(
new
String( method.getResponseBody() ) )
!=
null
)
//
if CAS does not return a login page with an LT authentication was successful
{
LOG.error(
"
Authentication for '
"
+
username
+
"
' unsuccessful
"
);
if
( LOG.isDebugEnabled() )
LOG.debug(
"
Authentication for '
"
+
username
+
"
' unsuccessful.
"
);
}
else
{
if
( LOG.isDebugEnabled() )
LOG.debug(
"
Authentication for '
"
+
username
+
"
' unsuccessful.
"
);
}
}
else
{
Header h
=
method.getResponseHeader(
"
Location
"
);
if
( h
!=
null
)
result
=
extractServiceTicket( h.getValue() );
if
( result
==
null
)
LOG.error(
"
Authentication for '
"
+
username
+
"
' unsuccessful.
"
);
}
}
catch
( Exception x )
{
LOG.error(
"
Could not authenticate'
"
+
username
+
"
':
"
+
x.toString () );
}
method.releaseConnection();
return
result;
}
/**
* 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( String data )
{
String user
=
null
;
int
start
=
data.indexOf( CAS_USER_BEGIN );
if
( start
>=
0
)
{
start
+=
CAS_USER_BEGIN.length();
int
end
=
data.indexOf( CAS_USER_END );
if
( end
>
start )
user
=
data.substring( start, end );
else
LOG.warn(
"
Could not extract username from CAS validation response. Raw data is: '
"
+
data
+
"
'
"
);
}
else
{
LOG.warn(
"
Could not extract username from CAS validation response. Raw data is: '
"
+
data
+
"
'
"
);
}
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 )
{
String serviceTicket
=
null
;
int
start
=
data.indexOf( TICKET_BEGIN );
if
( start
>
0
)
{
start
+=
TICKET_BEGIN.length();
serviceTicket
=
data.substring( start );
}
return
serviceTicket;
}
/**
* Helper method to extract the LT from a login form from CAS.
*
*
@param
data Response data.
*
@return
The LT, if it could be extracted, null otherwise.
*/
protected
String extractLt( String data )
{
String token
=
null
;
int
start
=
data.indexOf( LT_BEGIN );
if
( start
<
0
)
{
LOG.error(
"
Could not obtain LT token from CAS: LT Token not found in response.
"
);
}
else
{
start
+=
LT_BEGIN.length();
int
end
=
data.indexOf(
"""
, start );
token
=
data.substring( start, end );
}
return
token;
}
/**
* 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.<br>
* If a service URL is provided (which is optional), this method
* will post the URL such that CAS authenticates against the
* specified service when a subsequent authentication request is
* sent.
*
*
@param
serviceUrl
*
@return
The LT token if it could be extracted from the CAS response.
*/
protected
String getLt( String serviceUrl )
{
String lt
=
null
;
HttpMethod method
=
null
;
if
( serviceUrl
==
null
)
method
=
new
GetMethod( fCasUrl
+
LOGIN_URL_PART );
else
{
method
=
new
PostMethod( fCasUrl
+
LOGIN_URL_PART );
( ( PostMethod ) method ).setParameter(
"
service
"
, serviceUrl );
}
try
{
int
statusCode
=
fClient.executeMethod(method);
if
(statusCode
!=
HttpStatus.SC_OK)
{
LOG.error(
"
Could not obtain LT token from CAS:
"
+
method.getStatusLine() );
method.releaseConnection();
}
else
{
Object o
=
method.getResponseHeaders() ;
return
extractLt(
new
String( method.getResponseBody() ) );
}
}
catch
( Exception x )
{
LOG.error(
"
Could not obtain LT token from CAS:
"
+
x.toString () );
}
method.releaseConnection();
return
lt;
}
}