Zespół Uczelnianego Centrum Informatycznego UMK wybrał oprogramowanie uPortal jako implementację usług portalowych. To bezpłatne oprogramowanie jest rozwijane przez instytucje związane ze szkolnictwem wyższym. Zostało oparte na otwartych standardach, korzysta z Javy, XML-a, JSP. uPortal ma możliwość współpracy z bazą LDAP w celu uwierzytelnienia użytkowników, co w przypadku UMK jest ważne, gdyż ostatnio LDAP stał się tu podstawową bazą użytkowników.
Oprogramowanie portalowe organizuje dostęp do zasobów informacyjnych oraz do usług sieciowych. Coraz częściej zdarza się, że określone zasoby czy usługi wymagają autoryzowanego dostępu, gdyż nie każdy ma prawo z nich korzystać. Użytkownik portala zazwyczaj nie jest anonimowy - już w celu dopasowania wyglądu stron do własnych potrzeb musi dokonać uwierzytelnienia. To jednokrotne uwierzytelnienie, wykonane w chwili pierwszego kontaktu z portalem powinno dawać możliwość dostępu do wszystkich usług i zasobów, z których dany użytkownik ma prawo korzystać. Taki uniwersalny system jest nazywany system pojedynczego logowania (single sign-on).
2. Funkcjonalność systemów pojedynczego logowania
Systemy pojedynczego logowania realizują dwa istotne zadania:
3. Uwierzytelnianie poprzez system LDAP w oprogramowaniu uPortal
ldap.host=pełna kwalifikowana nazwa serwera LDAP ldap.port=port serwera LDAP ldap.protocol=SSL # jeśli chronimy hasła poprzez stosowanie SSL-a ldap.baseDN=nazwa wyróżniona kontekstu startowego ldap.uidAttribute=atrybut używany do wyszukiwania użytkownika, np. uidoraz, jeśli jest to potrzebne do uzyskania dostępu do bazy,
ldap.managerDN=nazwa wyróżniona administratora ldap.managerPW=hasło administratoraNastępnie w pliku properties/PersonDirs.xml, definiującym sposób dostępu do danych o użytkowniku, wybieramy metodę "LDAP Properties" w bloku <PersonDirInfo> i ustawiamy:
<url>ldap://nazwa_serwera_ldap:port_serwera_LDAP/kontekst_bazowy</url>
<uidquery>uid={0})</uidquery>
<usercontext>ou=Users<usercontext>
Znacznik uidquery pozwala zdefiniować atrybut wyszukania,
natomiast usercontext określa kontekst przeszukania (w zakresie
podanego bazowego URL-a). Znaczniki <logonid> oraz
<logonpassword> są używane do wskazania nazwy i hasła w
przypadku, gdy dostęp do bazy LDAP nie może być anonimowy.
Następnie w bloku <attributes>
definiujemy nazwy atrybutów w bazie LDAP.
Kolejnym krokiem jest określenie sposobu uwierzytelniania. Jeśli chcemy korzystać z bazy LDAP, to konieczna jest zmiana w pliku properties/security.properties, musi w nim wystąpić wiersz:
root=org.jasig.portal.security.provider.SimpleLdapSecurityContextFactorylub
root=org.jasig.portal.security.provider.CacheLdapSecurityContextFactoryW drugim przypadku pobrane z LDAP-a dane uwierzytelniania są cache'owane przez aplikację i mogą być używane przy kolejnych próbach uwierzytelnienia. Jest to metoda symulowania systemu pojedynczego logowania, ale należy podkreślić, że nie jest to zalecane podejście z powodów bezpieczeństwa, gdyż w takim podejściu hasło jest przechowywane otwartym tekstem w pamięci aplikacji. Jeśli zalogowanie w uPortalu ma oznaczać dostęp do wszystkich innych usług sieciowych oferowanych w ramach portala (np. WebMail, dostęp do zasobów pracowniczej bazy danych czy do elektronicznych czasopism prenumerowanych przez macirzystą instytucję), to niezbędne jest wdrożenie profesjonalnego systemu pojedynczego logowania.
4. System pojedynczego logowania
https://nazwa_serwera_cas/cas/login?service=http://nazwa_serwera_uportal/uPortal/Authenticationktóry przy pierwszym odwołaniu kieruje użytkownika do formularza uwierzytelniania, np. w polach nazwa użytkownika i hasło podajemy swoje dane, które są przekazywane do serwera CAS.
Podobnie jak uPortal, CAS może zostać skonfigurowany tak, by korzystał z dowolnych, zaimplementowanych w systemie metod uwierzytelniania, na przykład z Kerberosa czy LDAP-a.
W najprostszym przypadku CAS sprawdza nazwę użytkownika i hasło, jeśli weryfikacja powiedzie się, to generuje bilet (ticket), który zostaje przesłany do URL-a wskazanego w parametrze service, czyli do docelowej aplikacji, ubiegającej się o uwierzytelnienie (w tym przypadku uPortal). Aplikacja musi przekazać bilet z powrotem do CAS-a, razem z parametrem service wskazującym, kto odebrał bilet. Bilet po jednokrotnym użyciu traci ważność i nie może być ponownie użyty. Gdy użytkownik zostanie uwierzytelniony CAS ustanawia cookie. Jeśli inna usługa przekieruje użytkownika do strony cas/login, to serwer CAS jest w stanie rozpoznać, że dany użytkownik już został uwierzytelniony i automatycznie wygenerować mu bilet i przekierować do docelowej aplikacji bez konieczności wypełniania formularza uwierzytelniania, czyli uzyskujemy efekt pojedynczego logowania.
Najnowsze wersje CAS-a (serii 2.0) mogą nie tylko uwierzytelnić użytkownika. CAS 2.0 może również przyznać aplikacji prawa do uwierzytelniania pośredniego (proxy authentication). Usługa proxy pozwala na "wcielenie się" w użytkownika, odegranie jego roli wobec innej usługi sieciowej. W tym scenariuszu CAS przekazuje aplikacji występującej jako proxy (np. uPortal), bilet typu PGT, proxy granting ticket, dotyczący konkretnego użytkownika. Aplikacja, gdy musi uwierzytelnić tego użytkownika w kolejnej usłudze, przesyła bilet PGT do CAS-a, a CAS zwraca bilet dla danego użytkownika. Wówczas aplikacja wywołuje URL związany z logowaniem, przekazując bilet, a proxy dostaje z serwera CAS nazwę użytkownika. Może istnieć łańcuch aplikacji mających prawo występowania jako proxy, dlatego CAS zwraca oprócz nazwy użytkownika listę aplikacji typu proxy granting. Dzięki temu docelowa aplikacja może decydować, czy zaakceptować informację dotyczącą uwierzytelnienia.
b. Integracja CAS-a z uPortalem
W celu integracji należy wykonać następujące czynności:
ant distdokona kompilacji plików źródłowych Javy (katalog src), powstaną pliki .class w katalogu build. Powstanie również plik .jar, zawierający wszystkie skompilowane klasy, który zostanie zainstalowany w podkatalogu drzewa web (dokładnie w katalogu web/WEB-INF/lib) oraz plik .war, stanowiący zawartość katalogu web (w katalogu lib). Plik lib/cas.war musi zostać umieszczony w katalogu aplikacji Tomcata (np. /usr/local/tomcat/webapps).
Domyślnie dystrybucja serwera CAS generuje pusty kod uwierzytelniania, co oznacza, że serwer CAS uwierzytelni każdego użytkownika podającego taki sam ciąg znaków jako nazwę użytkownika i hasło. Aby zastosować własny schemat uwierzytelniania, np. poprzez serwis LDAP, należy odpowiednio zmodyfikować klasy związane z uwierzytelnianiem (edu.yale.its.tp.cas.auth) - będzie o tym mowa w dalszej części opracowania.
Biblioteka java/lib/casclient.jar musi zostać udostępniona aplikacji uPortal. Należy przekopiować ją do katalogu lib dystrybucji uPortala.
https://nazwa_serwera_cas/cas/login?service=http://nazwa_serwera_uportal/uPortal/AuthenticationNależy pamiętać, że serwer CAS musi pracować w oparciu o protokół SSL (bezpieczny port). Wymaga to odpowiedniego skonfigurowania Tomcata, wg poniższej procedury (zakładamy, że cas.crt jest certyfikatem serwera CAS, ca.crt certyfikatem centrum, które wystawiło certyfikat serwerowi CAS):
openssl pkcs12 -export -inkey cas.key -in cas.crt -out cas.pkcs12
keytool -list -v -keystore cas.pkcs12 -storetype pkcs12 -storepass secret
java -classpath /src/Java/Jetty-4.2.11/lib/org.mortbay.jetty.jar org.mortbay.util.PKCS12Import cas.pkcs12 cas.keystore
keytool -keyclone -keystore cas.keystore -alias 1 -dest cas -storepass secret
keytool -list -keystore cas.keystore -storepass secret
keytool -delete -alias 1 -keystore cas.keystore -storepass secret
keytool -list -keystore cas.keystore -storepass secret
(cat cas.crt; echo ; cat ca.crt )> certchain.pem
openssl crl2pkcs7 -nocrl -certfile certchain.pem -outform DER -out certchain.pkcs7
keytool -import -alias cas -trustcacerts -file certchain.pkcs7 -keystore ca.keystore -storepass secret
keytool -list -keystore cas.keystore -storepass secretcas.keystore należy wskazać jako keystoreFile w pliku conf/server.xml w sekcji Factory konektora SSL; w keystorePass podajemy hasło (jeśli jest inne niż domyślne czyli changeit)
openssl x509 -in ca.crt -out ca.der -outform DERi integrujemy plik ca.der z podsystemem JVM
keytool -import -alias UMK-CA -file ca.der -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeitSprawdzamy zawartość magazynu cecerts
keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
c. Konfiguracja CAS-a do współpracy z LDAP-em
package edu.yale.its.tp.cas.auth.provider;
import edu.yale.its.tp.cas.auth.*;
import netscape.ldap.*;
import java.util.*;
/** Interface for password-based authentication handlers. */
public class LDAPHandler implements PasswordHandler {
public boolean authenticate(javax.servlet.ServletRequest request,
String username,
String password) {
String LDAPhost = "ldap.uci.uni.torun.pl";
String FILTER = "uid="+username;
String SEARCHBASE = "ou=Users,dc=uni,dc=torun,dc=pl";
LDAPConnection ld = new LDAPConnection();
String EntryDN = null;
String ManagerEntryDN = "cn=Manager,dc=uni,dc=torun,dc=pl";
try {
ld.connect( LDAPhost, LDAPv2.DEFAULT_PORT);
ld.authenticate ( 3, ManagerEntryDN, "secret" );
LDAPSearchResults res = ld.search( SEARCHBASE,
LDAPConnection.SCOPE_SUB,
FILTER, null, false );
while ( res.hasMoreElements() ) {
LDAPEntry findEntry = null;
try {
findEntry = res.next();
EntryDN = findEntry.getDN();
} catch ( LDAPException e ) {
return(false);
}
}
try {
ld.authenticate(3, EntryDN, password);
} catch( LDAPException e ) {
return(false);
}
} catch( LDAPException e ) {
return(false);
}
try {
ld.disconnect();
} catch( LDAPException e ) {
return(false);
}
return(true);
}
}
W pliku web/WEB-INF/web.xml dystrybucji modyfikujemy blok
<context-param> zawierający wiersz:
<param-name>edu.yale.its.tp.cas.authHandler<param-name>zmieniamy wiersz <param-value> na:
<param-value>edu.yale.its.tp.cas.auth.provider.LDAPHandler<param-value>Po rekompilacji (ant dist) przekopiowujemy nowy plik lib/cas.war do katalogu $TOMCAT_HOME/webapps i usuwamy zawartość katalogu $TOMCAT_HOME/webapps/cas. Następnie restartujemy Tomcata.
Po tych modyfikacjach procedura uwierzytelniania jest realizowana w oparciu o bazę LDAP (w tym przypadku w gałęzi ou=Users,dc=uni,dc=torun,dc=pl wyszukujemy podanej nazwy użytkownika poprzez filtr: uid=nazwa_użytkownika; po skutecznym znalezieniu wpisu, realizowana jest próba dowiązania do bazy jako wyszukany użytkownik przy użyciu podanego hasła; Pozytywne dowiązanie oznacza sukces procesu uwierzytelnienia).
Ponieważ serwer CAS działa jako system pojedynczego logowania, po pierwszym uwierzytelnieniu, kolejne aplikacje mogą korzystać z funkcjonalności CAS-a oraz uPortala, który w tej sytuacji może występować jako aplikacja proxy-granting.
5. Przykłady łączenia aplikacji WWW z systemem pojedynczego logowania uPortala
package org.jasig.portal.channels;
import org.jasig.portal.IChannel;
import org.jasig.portal.ChannelStaticData;
import org.jasig.portal.ChannelRuntimeData;
import org.jasig.portal.ChannelRuntimeProperties;
import org.jasig.portal.PortalEvent;
import org.jasig.portal.PortalException;
import org.jasig.portal.utils.XSLT;
import org.jasig.portal.services.LogService;
import org.xml.sax.ContentHandler;
import java.io.StringWriter;
import org.jasig.portal.security.*;
import org.jasig.portal.security.provider.*;
import java.lang.reflect.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.Date;
import org.jasig.portal.security.provider.YaleCasContext;
public class CImp implements IChannel
{
ChannelStaticData staticData = null;
ChannelRuntimeData runtimeData = null;
private static final String sslLocation = "CImp/CImp.ssl";
private String HordeUrl = null;
private String ButtonTitle = null;
private String Server = null;
private String Mailbox = null;
private String Image = null;
private String Title = null;
private boolean bFirstTime = true;
private ISecurityContext ic;
private boolean bAuthenticated = false;
private LogService logger;
public CImp ()
{
this.staticData = new ChannelStaticData ();
this.runtimeData = new ChannelRuntimeData ();
}
public ChannelRuntimeProperties getRuntimeProperties ()
{
// Channel will always render, so the default values are ok
return new ChannelRuntimeProperties ();
}
public void receiveEvent (PortalEvent ev)
{
// no events for this channel
}
public void setStaticData (ChannelStaticData sd)
{
this.staticData = sd;
HordeUrl = sd.getParameter( "HordeUrl" );
ButtonTitle = sd.getParameter( "ButtonTitle" );
Server = sd.getParameter( "Server" );
Mailbox = sd.getParameter( "Mailbox" );
Title = sd.getParameter( "Title" );
Image = sd.getParameter( "Image" );
ic = staticData.getPerson().getSecurityContext();
if (ic!=null && ic.isAuthenticated())
bAuthenticated = true;
}
public void setRuntimeData (ChannelRuntimeData rd)
{
this.runtimeData = rd;
}
public void renderXML (ContentHandler out) throws PortalException
{
StringWriter w = new StringWriter ();
String userid = null;
String domain = null;
String password = null;
YaleCasContext ycc;
int i;
int l;
if( bAuthenticated )
{
userid = ic.getPrincipal().getUID();
if ( (l = userid.indexOf("@")) != -1 ) {
domain = userid.substring(l+1);
userid = userid.substring(0, l);
}
java.util.Enumeration en = ic.getSubContexts();
while (en.hasMoreElements()) {
ISecurityContext sctx = (ISecurityContext)en.nextElement();
if (sctx instanceof YaleCasContext) {
ycc = (YaleCasContext) sctx;
password = ycc.getCasPGT();
}
}
}
w.write ("<?xml version='1.0'?>>\n");
w.write ("<content>\n");
if( HordeUrl != null ) w.write (" <hordeurl>" + HordeUrl + "</hordeurl>\n");
if( Server != null ) w.write (" <server>" + Server + "</server>\n");
if( Image != null ) w.write (" <image>" + Image + "</image>\n");
if( Title != null ) w.write (" <title>" + Title + "</title>\n");
if( Mailbox != null ) w.write (" <mailbox>" + Mailbox + "</mailbox>\n");
if( ButtonTitle != null ) w.write (" <buttontitle>" + ButtonTitle + "</buttontitle>\n");
if( userid != null ) w.write (" <userid>" + userid + "</userid>\n");
if( password != null )
w.write (" <password>" + password + "</password>\n");
w.write ("</content>\n");
XSLT xslt = new XSLT(this);
xslt.setXML(w.toString());
xslt.setXSL(sslLocation, "main", runtimeData.getBrowserInfo());
xslt.setTarget(out);
xslt.setStylesheetParameter("baseActionURL", runtimeData.getBaseActionURL());
xslt.transform();
}
}
Wywołanie metody getCasPGT() służy do pobrania
proxy-granting ticket. Metoda ta została dopisana do klasy
source/org/jasig/portal/security/provider/YaleCasContext.java:
/* MGW get PGT from previously cached PGT IOU */
public String getCasPGT () {
return ProxyTicketReceptor.getProxyGrantingTicket(this.pgtIou);
}
W klasie YaleCasContext
umieszczonej w tym samym pliku trzeba również zamienić wiersz:
private String pgtIou = null;na
public String pgtIou = null;
<?xml version="1.0"?> <?xml-stylesheet title="main" href="CImp/Imp.xsl" type="text/xsl" media="explorer"?> <document> </document>Natomiast plik Imp.xsl postać:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="baseActionURL">default</xsl:param>
<xsl:template match="content">
<xsl:choose>
<xsl:when test="not(hordeurl) or hordeurl = ''">
Channel administrator has not configured the HordeUrl variable
</xsl:when>
<xsl:when test="not(userid) or userid = ''">
cannot determine userid
</xsl:when>
<xsl:otherwise>
<xsl:variable name="hordeurl"><xsl:value-of select="hordeurl"/></xsl:variable>
<xsl:variable name="userid"><xsl:value-of select="userid"/></xsl:variable>
<table bgcolor="#222244" width="100%">
<xsl:if test="title">
<xsl:variable name="title"><xsl:value-of select="title"/></xsl:variable>
<tr><td bgcolor="#444466" align="center">
<font face="Helvetica, Arial, sans-serif" color="#FFFFFF">
<b><xsl:value-of select="title"/>i
</b></font></td></tr>
</xsl:if>
<tr><td><center>
<xsl:if test="image">
<xsl:variable name="image"><xsl:value-of select="image"/>
</xsl:variable>
<img src="{$image}" border="0"></img>
</xsl:if>
<form action="{$hordeurl}/imp/redirect.php" method="post" name="implogin" target="horde">
<input type="hidden" name="url" value="{$hordeurl}" />
<input type="hidden" name="imapuser" value="{$userid}" />
<input type="hidden" name="uP_root" value="me"/>
<xsl:choose>
<xsl:when test="not(password) or password = ''">
<xsl:choose>
<xsl:when test="not(random) or random = ''">
Password : <input type="password" name="pass" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="random"><xsl:value-of select="random"/></xsl:variable>
<input type="hidden" name="uprand" value="{$random}" />
<xsl:variable name="expires"><xsl:value-of select="expires"/></xsl:variable>
<input type="hidden" name="upexp" value="{$expires}" />
<xsl:variable name="encrypted"><xsl:value-of select="encrypted"/></xsl:variable>
<input type="hidden" name="upencr" value="{$encrypted}" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="password"><xsl:value-of select="password"/></xsl:variable>
<input type="hidden" name="pass" value="{$password}" />
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(server) or server = ''">
<input type="hidden" name="server" value="default" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="server"><xsl:value-of select="server"/></xsl:variable>
<input type="hidden" name="server" value="{$server}" />
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(mailbox) or mailbox = ''">
<input type="hidden" name="mailbox" value="INBOX" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="mailbox"><xsl:value-of select="mailbox"/></xsl:variable>
<input type="hidden" name="mailbox" value="{$mailbox}" />
</xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="not(buttontitle) or buttontitle = ''">
<input type="submit" class="uportal-button" value="Mail"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="buttontitle"><xsl:value-of select="buttontitle"/></xsl:variable>
<input type="submit" class="uportal-button" value="{$buttontitle}"/>
</xsl:otherwise>
</xsl:choose>
</form>
</center></td></tr>
</table>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
function getCredential($credential)
{
if (!empty($_SESSION['__auth']) &&
!empty($_SESSION['__auth']['authenticated'])) {
$credentials = unserialize(Secret::read(Secret::getKey('auth'),
$_SESSION['__auth']['credentials']));
}
if (isset($credentials[$credential])) {
return $credentials[$credential];
} else {
$url =
"https://cas.uci.uni.torun.pl:8443/cas/proxy?pgt=$credential&targetService=imap://imap.uci.uni.torun.pl";
$fp=fopen($url,"r");
$data=fread($fp,"1000000");
$entry = sprintf('Auth::getCredential, data = %s', $data);
Horde::logMessage($entry, __FILE__, __LINE__, LOG_INFO);
fclose($fp);
if ( preg_match("//", $data )) {
preg_match("/(.*)<\/cas:proxyTicket>/", $data, $val);
return $val[1];
}
}
}
przy czym cas.uci.uni.torun.pl to nazwa serwera CAS, a
imap.uci.uni.torun.pl. - nazwa serwera IMAP.
$pt = Auth::getCredential($pass);a samo wywołanie zamieniamy na następujące:
$stream = @imap_open(IMP::serverString() . $imp['searchfolders'][$i],
$imp['user'], $pt, OP_READONLY);
Taka sama poprawka jest potrzebna w funkcji głównej, w bloku case
EXPUNGE_MAILBOX.
/* If we already have a session... */zamieniamy wiersz:
if (array_key_exists('imp', $_SESSION) && is_array($_SESSION['imp'])) {
na
if (0 & array_key_exists('imp', $_SESSION) && is_array($_SESSION['imp'])) {
gdyż nigdy nie chcemy akceptować poprzedniej sesji.
$imp['pass'] = Secret::write(Secret::getKey('imp'), Horde::getFormData('pass'));
zamieniamy na:
$imp['pass'] = Horde::getFormData('pass');
auth sufficient /lib/security/pam_cas.so debug -simap://imap.uci.uni.torun.pl -phttps://portal.uci.uni.torun.pl:8443/uPortal2CasProxyServlet auth required /lib/security/pam_pwdb.so shadow nullok account required /lib/security/pam_pwdb.so shadow nullokBilioteka /lib/security/pam_cas.so pochodzi z pakietu cas-client (jest wynikiem kompilacji w katalogu pam_cas dystrybucji cas-client).
Tak przygotowany kanał razem z modyfikacją konfiguracji IMAP i kilku fragmentów aplikacji IMP dają możliwość dostępu do poczty ze środowiska uPortal bez konieczności ponownego uwierzytelniania.
<?php
include ('./config.php');
include ('./lib/general.inc');
include_once('CAS/CAS.php');
include_once('CAS/client.php');
session_start();
phpCAS::client(CAS_VERSION_2_0,'cas.uci.uni.torun.pl',8443,'cas');
if ( phpCAS::authenticate() ) {
# zmienna której ustawienie decyduje o przyznaniu pełnego dostępu
$_SESSION['zmienna']=1;
$_SESSION['cachedData']=array();
}
$newbase = 0;
if (isset($_GET['base']) && $_GET['base']) $newbase = urlencode(urldecode($_GET[
'base']));
if (isset($_GET['entry']) && $_GET['entry']) $entry = urlencode(urldecode($_GET[
'entry']));
?>
<html>
<head>
<title>LDAP-USER</title>
<meta http-equiv="content-type" content="text/html;charset=ISO-8859-2">
</head>
<frameset cols="35%,*" framespacing="0">
<frame marginheight="4" marginwidth="4" src="tree.php<?= $newbase ? "?newbase
=$newbase" : "" ?>" name="left">
<frame marginheight="4" marginwidth="4" src="entry.php<?= $entry ? "?actionID
=23&dn=$entry" : "" ?>" name="right">
</frameset>
</html>
?>
Wywołanie funkcji phpCAS::authenticate(), mające miejsce po
zainicjowaniu powiązania z serwerem CAS decyduje o wyniku
uwierzytelnienia. Jeśli użytkownik jest znany serwerowi CAS, wynik
wywołania jest pozytywny i nie jest potrzebne odesłanie do formularza
logowania.
Ważne linki