Warning | ||
---|---|---|
| ||
CAS documentation has moved over to apereo.github.io/cas, starting with CAS version 4.x. The wiki will no longer be maintained. For the most recent version of the documentation, please refer to the aforementioned link. |
What is CAS Technically?
CAS is an Enterprise Java solution to web application authentication that also provides the benefit of Single Sign On (SSO). Technically, SSO can be achieved because the authentication can be removed from the web application and handled centrally. And, when this authentication is handled by a single service, access to many services can be granted once and "remembered" for the life of the web session or even longer though not recommended. Remember too that the SSO feature does not have to be employed and yet CAS authentication still provides a quality authentication mechanism.
...
CAS is written to execute within a standard Java 1.5 Runtime Environment. Versions previous to 3.1 only required Java 1.4. As CAS aims to operate in a self-contained manner, there is no need to place any special configuration files into your Java Runtime Environment. The only exception to this is if you are using a Java Authenticaton Authentication and Authorization Service (JAAS) policy file.
CAS provides an example user interface that's designed to run in any container that supports the Servlet 2.4 specification. A good container is the Tomcat 5.x or 6.x series, though any 2.4 servlet container will work. CAS will run on earlier versions of Tomcat e.g. 4.x if the older style JSTL tags are used. It would be easier to use the newer Tomcat version.
...
Access to your application begins when the user calls for a CAS login including the application URL or service needed. This looks very much like this.
Code Block |
---|
http(s)://cas_server/cas/login?service=http(s)://other_server/application1
|
When the CAS server receives the request, CAS programmatically forms a new URL request (redirection) and calls application1 and adds a unique one-time-only random ticket (String) as a request parameter. Remember that the user does not see any of this. Also, please note that HTTPS is marked with parentheses. It is critical that you understand how HTTPS can be used. CAS will make no assumptions outside of the authentication security mechanism. HTTPS recommendations and practices will be discussed in separation documentation.
Code Block |
---|
http(s)://other_server/application1?ticket=ST-8670-123buTvFFjo980
|
The other server receives this request through a CAS client servlet filter that's been configured with application1. It parses the ticket and starts a new HTTPS connection with the CAS server. This new request is formed programmatically by the CAS filter Java code and it's commonly called the "service validation" step. It receives an SSL certificate after it sends a request to the CAS server. By protocol and Java standard, the certificate is compared by Java to it's collection of trusted certificates. If the certificate is "trusted", the HTTPS communication will occur.
CAS receives this secure request and prompts the user for his password. This is where the CAS server authenticates the user that's requesting access to application1. Remember that all this is occurring and the user has only made a single request to access application1. CAS will also verify that application1 has been loaded into it's persistent store of CAS registered services. It is, so CAS presents an HTML login screen to the user. The user enters his credentials and CAS will do the verification.
Once the user has been authenticated, the CAS server fulfills the HTTPS request by the application server authenticates the user either via prompting the user to login or by checking to see if they already have an active SSO session with CAS (Ticket Granting Ticket, TGT). If authentication is successful, CAS verifies that application1 is an allowed application by checking its persistent store of CAS registered services. If also successful, CAS generates a service ticket (ST) and programmatically forms a new URL request (redirection) back to application1 with the ST as a request parameter in the query string. Remember that the user does not see any of this. Also, please note that HTTPS is marked with parentheses. It is critical that you understand how HTTPS can be used. CAS will make no assumptions outside of the authentication security mechanism. HTTPS recommendations and practices will be discussed in separate documentation.
Code Block |
---|
http(s)://other_server/application1?ticket=ST-8670-123buTvFFjo980
|
The other server receives this request through a CAS client (either a servlet filter or a module) that's been configured with application1. It parses the ticket and starts a new HTTPS connection with the CAS server. This new request is formed programmatically by the CAS filter Java code and it's commonly called the "service validation" step.
CAS receives and validates this secure server-to-server request, then fulfills the application server's HTTPS CAS client request and returns an XML message of "success" and along with the authenticated username included. The CAS filter can now allow . Remember that all of this is occurring whilst the user has only made a single request to access application1. The original request then exits the CAS filter/module and allows application1 to serve content to this newly authenticated user.
...
The user first makes a request to the CAS server for login and access to one of the CAS registered services.
Code Block |
---|
http://cas_server/cas/login?service=http://other_server/application1
|
...
Code Block | ||||
---|---|---|---|---|
| ||||
public final class GenerateServiceTicketAction extends AbstractAction {
/** Instance of CentralAuthenticationService. */
@NotNull
private CentralAuthenticationService centralAuthenticationService;
protected Event doExecute(final RequestContext context) {
final Service service = WebUtils.getService(context);
final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
try {
final String serviceTicketId = this.centralAuthenticationService
.grantServiceTicket(ticketGrantingTicket,
service);
WebUtils.putServiceTicketInRequestScope(context,
serviceTicketId);
return success();
} catch (final TicketException e) {
if (isGatewayPresent(context)) {
return result("gateway");
}
}
return error();
}
...
|
...
Code Block | ||||
---|---|---|---|---|
| ||||
public final class DynamicRedirectViewSelector implements ViewSelector {
public ViewSelection makeRefreshSelection(final RequestContext context) {
return makeEntrySelection(context);
}
public boolean isEntrySelectionRenderable(final RequestContext request) {
return false;
}
public ViewSelection makeEntrySelection(final RequestContext request) {
final WebApplicationService service = WebUtils.getService(request);
final String ticket = WebUtils.getServiceTicketFromRequestScope(request);
final Response serviceResponse = service.getResponse(ticket);
switch (serviceResponse.getResponseType()) {
case POST:
final Map<String, Object> model = new HashMap<String, Object>();
model.put("parameters", serviceResponse.getAttributes());
model.put("originalUrl", service.getId());
return new ApplicationView("postResponseView", model);
default:
return new ExternalRedirect(service.getResponse(ticket).getUrl());
}
}
}
|
...
Code Block | ||||
---|---|---|---|---|
| ||||
...
private String getAuthenticatedUser(HttpServletRequest request)
throws ServletException {
ProxyTicketValidator pv = null;
try {
pv = new ProxyTicketValidator();
pv.setCasValidateUrl(casValidate);
pv.setServiceTicket(request.getParameter("ticket"));
pv.setService(getService(request));
pv.setRenew(Boolean.valueOf(casRenew).booleanValue());
pv.validate();
if (!pv.isAuthenticationSuccesful())
throw new ServletException(
"CAS authentication error: " + pv.getErrorCode() + ": " + pv.getErrorMessage());
if (pv.getProxyList().size() != 0) {
// ticket was proxied
if (casAuthorizedProxy == null) {
throw new ServletException("this page does not accept proxied tickets");
} else {
boolean authorized = false;
String proxy = (String)pv.getProxyList().get(0);
StringTokenizer casProxies =
new StringTokenizer(casAuthorizedProxy);
while (casProxies.hasMoreTokens()) {
if (proxy.equals(casProxies.nextToken())) {
authorized = true;
break;
}
}
if (!authorized) {
throw new ServletException(
"unauthorized top-level proxy: '"
+ pv.getProxyList().get(0)
+ "'");
}
}
}
return pv.getUser();
} catch (SAXException ex) {
String xmlResponse = "";
if (pv != null)
xmlResponse = pv.getResponse();
throw new ServletException(ex + " " + xmlResponse);
} catch (ParserConfigurationException ex) {
throw new ServletException(ex);
} catch (IOException ex) {
throw new ServletException(ex);
}
}
...
|
...
Code Block | ||||
---|---|---|---|---|
| ||||
...
public void validate()
throws IOException, SAXException, ParserConfigurationException {
if (casValidateUrl == null || st == null)
throw new IllegalStateException("must set validation URL and ticket");
clear();
attemptedAuthentication = true;
StringBuffer sb = new StringBuffer();
sb.append(casValidateUrl);
if (casValidateUrl.indexOf('?') == -1)
sb.append('?');
else
sb.append('&');
sb.append("service=" + service + "&ticket=" + st);
if (proxyCallbackUrl != null)
sb.append("&pgtUrl=" + proxyCallbackUrl);
if (renew)
sb.append("&renew=true");
String url = sb.toString();
String response = SecureURL.retrieve(url);
this.entireResponse = response;
// parse the response and set appropriate properties
if (response != null) {
XMLReader r =
SAXParserFactory.newInstance().newSAXParser().getXMLReader();
r.setFeature("http://xml.org/sax/features/namespaces", false);
r.setContentHandler(newHandler());
r.parse(new InputSource(new StringReader(response)));
}
}
...
|
...