Warning |
---|
title | New CAS documentation site |
---|
|
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.
...
The CAS web server application uses Spring Web Flow and the URL handler ultimately sends the login request to GenerateServiceTicketAction. This is where the service ticket is placed in Request scope to be sent to the CAS client filter.
Code Block |
---|
borderStyle | solid |
---|
title | GenerateServiceTicketAction.java (CAS Server) |
---|
borderStyle | solid |
---|
|
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();
}
...
|
When the ticket has been placed in the request scope, CAS will now redirect the user request to the application using the service URL and the service ticket generated. The default in the switch-case block would handle the redirected request now to the application server.
Code Block |
---|
borderStyle | solid |
---|
title | DynamicRedirectViewSelector.java (CAS Server) |
---|
borderStyle | solid |
---|
|
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());
}
}
}
|
<STOP!> This is a critical timepoint. The CAS server has processed the user's request and the result of this redirection would be the expected application content. In the interim, before this request is processed, the application server using CAS client code will send an HTTPS request to the CAS server and have it authenticate the requesting user sharing no connection or resources with the original CAS user request. The CAS client servlet filter here sends the HTTPS request for user authentication. Ignore the proxy list for now, but notice the proxy validator gathers the service and the ticket. This method returns the user returned by the CAS server over HTTPS and within the CAS returning XML message.
Code Block |
---|
borderStyle | solid |
---|
title | CASFilter.java (CAS Client) |
---|
borderStyle | solid |
---|
|
...
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);
}
}
...
|
The return pv.getUser() method comes from the class ServiceTicketValidator. ProxyTicketValidator (pv) extends ServiceTicketValidator. The validate() method will populate the user member if the CAS XML message comes back successfully. This method is where the separate HTTPS request is created to validate the questioning user.
Code Block |
borderStyle |
---|
solid | title | ServiceTicketValidator.java (CAS Client) |
---|
borderStyle | solid |
---|
|
...
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)));
}
}
...
|
...