Login Procedure
The new OpenCms login as following:
Module Parameters
The CASified OpenCms-LDAP module has the following parameters:
...
Requirements
1. OpenCms is authenticated by CAS + LDAP.
2. OpenCms is authorized by CAS + LDAP.
3. Support OpenCms OU.
4. CAS will search LDAP for groups and roles when validating, not authenticating.
5. Validation URI can be customised, not /serviceValidate only.
6. Easy to extend the module to support CAS + DATABASE.
Environments
Tested in Fedora 10, OpenJDK 1.6.0, Tomcat 5.5.27, OpenCms7.0.5, CAS3.3.1, OpenLDAP 2.4.12.
Login Procedure
Module Parameters
Module parameters for authentication handler:
Code Block |
---|
AuthenticationHandler: cn.langhua.opencms.ldap.cas.CmsCasAuthenticationHandler
AutoUserRoleName: not required. If you want the user can login OpenCms workplace by default, this parameter should be RoleWorkplaceUsers.
CasUrl: not required, default is https://localhost:8443/cas.
CasLoginUri: not required, the uri to CAS login, default is /login.
CasValidateUri: not required, the uri to CAS validate, default is /serviceValidate.
CasLenientURL: not required, if set, this url will be used to validate CAS ticket, default is null.
CasLogoutUri: not required, default is /logout.
|
Module parameters for authorization handler:
Code Block |
---|
AuthenticationHandler: cn.langhua.opencms.ldap.cas.CmsCasAuthorizationHandler GroupSearchDN: required, the group dn to resolve OpenCms OU. If not set, will use BaseDN. RoleSearchDN: required, the role dn to resolve OpenCms role. If not set, will use BaseDN. BaseDN: not required. AutoUserRoleName: not required. If you want the user can login OpenCms workplace by default, this parameter should be RoleWorkplaceUsers. CasUrl: not required, default is https://localhost:8443/cas. CasValidateUriCasLoginUri: not required, the uri to CAS validatelogin, default is /validatelogin. FilterCasValidateUri: not required, the filteruri to loginCAS LDAP servervalidate, default is (objectclass=*)/serviceValidate. ScopeCasLenientURL: thenot scoperequired, toif search LDAPset, defaultthis isurl subwill whichbe meansused searchto subtreevalidate fromCAS theticket, BaseDN.default URL: the URL of the LDAP server, ldap://localhost:389is null. UseCmsLoginWhenLDAPFailCasLogoutUri: When LDAP connection or login failed, whether using OpenCms login instead. Default value is true. |
You have to config your CAS server to use LDAP as the above parameters configed.
How to validate service ticket
I use CAS 1.0 protocal to validate service ticket in the login procedure.
Code Block |
---|
String ticket = CmsRequestUtil.getNotEmptyParameter(getRequest(), PARAM_TICKET); CmsModule ldapModule = OpenCms.getModuleManager().getModule("cn.langhua.opencms.ldap"); if (ldapModule != null) { String casUrl = ldapModule.getParameter("CasUrl", "https://localhost:8443/cas"); String loginUri = ldapModule.getParameter("CasLoginUri", "/login"); String validateUri = ldapModule.getParameter("CasValidateUri", "/validate"); String serviceUrl = getRequest().getRequestURL().toString(); String url = URLEncoder.encode(serviceUrl, "UTF-8"); if (ticket == null) { not required, default is /logout. |
Add a new validate servlet to CAS
Here I use /authzValidate as the new validate servlet uri for an example.
1. Add /authzValidate in $(cas_server)/WEB-INF/web.xml
Code Block |
---|
<!-- start service validate extensions -->
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/authzValidate</url-pattern>
</servlet-mapping>
<!-- end service validate extensions -->
|
2. Modify $(cas_server)/WEB-INF/cas-servlet.xml
Code Block |
---|
...
<!-- start service validate extensions -->
<prop
key="/authzValidate">
authzValidateController
</prop>
<!-- end service validate extensions -->
...
<!-- start service validate extensions -->
<bean id="authzValidateController" class="org.jasig.cas.web.ServiceValidateController"
p:validationSpecificationClass="org.jasig.cas.validation.Cas20WithoutProxyingValidationSpecification"
p:centralAuthenticationService-ref="centralAuthorizationService"
p:proxyHandler-ref="proxy20Handler"
p:argumentExtractor-ref="casArgumentExtractor" />
<!-- end service validate extensions -->
|
3. Modify $(cas_server)/WEB-INF/spring-configuration/applicationContext.xml
Code Block |
---|
<!-- CentralAuthorizationService, please note, this is authz --> <bean id="centralAuthorizationService" class="cn.langhua.cas.LDAPAuthzCASImpl" p:ticketGrantingTicketExpirationPolicy-ref="grantingTicketExpirationPolicy" p:serviceTicketExpirationPolicy-ref="serviceTicketExpirationPolicy" p:authenticationManager-ref="authenticationManager" p:ticketGrantingTicketUniqueTicketIdGenerator-ref="ticketGrantingTicketUniqueIdGenerator" p:ticketRegistry-ref="ticketRegistry" p:servicesManager-ref="servicesManager" getResponse().sendRedirect(casUrl + loginUri + "?service=" + url); } else { // there's a ticket, we should validate the ticket URL validateURL = new URL(casUrl + validateUri + "?" + PARAM_TICKET + "=" + ticket + "&" + PARAM_SERVICE + "=" + url); URLConnection conn = validateURL.openConnection(); InputStreamReader result = new InputStreamReader(conn.getInputStream(), "UTF-8"); BufferedReader reader = new BufferedReader(result); String oneline = reader.readLine(); if (CmsStringUtil.isNotEmpty(oneline) && oneline.equals("yes")) { // the ticket is true m_username = reader.readLine().trim(); m_password = "cas_login"; m_actionLogin = "true"; reader.close(); result.close(); } else { // the ticket is false, forward the request to cas login page reader.close(); result.close(); getResponse().sendRedirect(casUrl + loginUri + "?service=" + url); } } } |
/system/loing/index.html
...
p:persistentIdGenerator-ref="persistentIdGenerator"
p:uniqueTicketIdGeneratorsForService-ref="uniqueIdGeneratorsMap"
p:attributeRepository-ref="attributeRepository" />
|
4.Modify $(cas_server)/WEB-INF/deployerConfigContext.xml
Code Block |
---|
<bean id="attributeRepository"
class="cn.langhua.cas.services.persondir.support.ldap.LdapPersonAttributeDaoExtension">
<property name="baseDN"
value="o=langhua,c=cn" />
<!-- This query is used to find the entry for populating attributes. {0} will be replaced by the new Principal ID extracted from the ldap-->
<property name="query" value="(uid:caseExactmatch:={0})" />
<property name="contextSource" ref="contextSource" />
<property name="groupSearchDN" value="ou=users,ou=opencms,ou=applications,o=langhua,c=cn" />
<property name="roleSearchDN" value="ou=roles,ou=opencms,ou=applications,o=langhua,c=cn" />
<property name="authorType" value="member" />
<property name="ldapAttributesToPortalAttributes">
<map>
<!-- Mapping beetween LDAP entry's attributes (key) and Principal"s (value) -->
<entry key="groups" value="groups" />
<entry key="roles" value="roles" />
<entry key="uid" value="uid" />
<entry value="title" key="title"/>
<entry key="cn" value="Name"/>
<entry key="sn" value="sn"/>
</map>
</property>
</bean>
|
5. Modify $(cas_server)/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp
Code Block |
---|
<%@ page session="false" pageEncoding="UTF-8"
%><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"
%><%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"
%><cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>
<c:if
test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}"> <cas:attributes>
<c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"
varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)}" step="1">
<cas:attribute>
<cas:name>${fn:escapeXml(attr.key)}</cas:name>
<cas:value>${fn:escapeXml(attr.value)}</cas:value>
</cas:attribute>
</c:forEach> </cas:attributes>
</c:if>
<c:if test="${not empty pgtIou}">
<cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
</c:if>
<c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
<cas:proxies>
<c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
<cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
</c:forEach>
</cas:proxies>
</c:if>
</cas:authenticationSuccess>
</cas:serviceResponse>
|
6. Sample of /authzValidate response
Code Block |
---|
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>shijh</cas:user>
<cas:attributes>
<cas:attribute>
<cas:name>uid</cas:name>
<cas:value>shijh</cas:value>
</cas:attribute>
<cas:attribute>
<cas:name>Name</cas:name>
<cas:value>石京海</cas:value>
</cas:attribute>
<cas:attribute>
<cas:name>sn</cas:name>
<cas:value>石京海</cas:value>
</cas:attribute>
<cas:attribute>
<cas:name>title</cas:name>
<cas:value>manager</cas:value>
</cas:attribute>
<cas:attribute>
<cas:name>roles</cas:name>
<cas:value>cn=RoleRootAdmins,ou=opencms,ou=roles,ou=applications,o=langhua,c=cn</cas:value>
</cas:attribute>
<cas:attribute>
<cas:name>distinguishedName</cas:name>
<cas:value>uid=shijh,ou=beijing,o=langhua,c=cn</cas:value>
</cas:attribute>
<cas:attribute>
<cas:name>groups</cas:name>
<cas:value>cn=Administrators,ou=opencms,ou=groups,ou=applications,o=langhua,c=cn</cas:value>
</cas:attribute>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
|
How to get the module and the source code
The source code of cn.langhua.cas is here.
Source code of OpenCms-LDAP module:
SVN:
http://www.langhua.cn/langhua/modules/ldap/
Username:anon
Password:anon
ViewVC:
http://www.langhua.cn/viewvc/svn/modules/ldap/
Shi Yusen/Beijing Langhua Ltd.
http://langhua.org/
http://langhua.biz/
http://www.langhua.cn/