Longtime I wanted to share SSO - CAS up to date documentation :
Zimbra - CAS Client
Works with ZCS 8.6, 8.7 RC
Important
* The following modifications are not preserved from Zimbra upgrade/update.
* multi servers installation case : each modification or command must have to be passed where the zimbra mailboxd is enabled.
* to ease the installation the domain you want to CASify must have a virtual hostname set in Zimbra
* most common problems are from ntpd which are not synchro, CAS self-signed certificate (see at the end), new dependancy needs with CAS client core, new filter need in the zimbra.web.xml.in
Java Client Installation
Put into ''/opt/zimbra/jetty/common/lib'' folder the following jar files :
* cas-client-core-3.4.1.jar
* slf4j-api-1.7.21.jar
zimbra.web.xml.in modifications
Add before the following piece of code :
Code: Select all
<error-page>
<error-code>404</error-code>
<location>/public/error.jsp</location>
</error-page>
Code: Select all
<!-- CAS filter start -->
<filter>
<filter-name>CasSingleSignOutFilter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://cas.domain.tld:443/cas</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasSingleSignOutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>CasAuthenticationFilter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>https://cas.domain.tld:443/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>https://mail.domain.tld:443</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasAuthenticationFilter</filter-name>
<url-pattern>/public/preauth.jsp</url-pattern>
</filter-mapping>
<filter>
<filter-name>CasValidationFilter</filter-name>
<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>https://cas.domain.tld:443/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>https://mail.domain.tld:443</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CasValidationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CasHttpServletRequestWrapperFilter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CasHttpServletRequestWrapperFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- CAS filter end -->
Put in the ''/opt/zimbra/jetty/webapps/zimbra/public'' folder the following jsp file :
Code: Select all
<%@ page import="java.security.InvalidKeyException" %>
<%@ page import="java.security.NoSuchAlgorithmException" %>
<%@ page import="java.security.SecureRandom" %>
<%@ page import="java.util.HashMap" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.util.TreeSet" %>
<%@ page import="javax.crypto.Mac" %>
<%@ page import="javax.crypto.SecretKey" %>
<%!
public static final String DOMAIN_KEY =
"b8d15fdfe2a7890d37c7708026c388e0648b0d716c28b0beb5d8316caf95686b"; // generate by the foolowing command : zmprov gdpak domain.tld
public static String generateRedirect(HttpServletRequest request, String name) {
HashMap params = new HashMap();
String ts = System.currentTimeMillis()+"";
params.put("account", name);
params.put("by", "name"); // needs to be part of hmac
params.put("timestamp", ts);
params.put("expires", "0"); // means use the default
String preAuth = computePreAuth(params, DOMAIN_KEY);
return request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+"/service/preauth/?" +
"account="+name+
"&by=name"+
"×tamp="+ts+
"&expires=0"+
"&preauth="+preAuth;
}
public static String computePreAuth(Map params, String key) {
TreeSet names = new TreeSet(params.keySet());
StringBuffer sb = new StringBuffer();
for (Iterator it=names.iterator(); it.hasNext();) {
if (sb.length() > 0) sb.append('|');
sb.append(params.get(it.next()));
}
return getHmac(sb.toString(), key.getBytes());
}
private static String getHmac(String data, byte[] key) {
try {
ByteKey bk = new ByteKey(key);
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(bk);
return toHex(mac.doFinal(data.getBytes()));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("fatal error", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("fatal error", e);
}
}
static class ByteKey implements SecretKey {
private byte[] mKey;
ByteKey(byte[] key) {
mKey = (byte[]) key.clone();;
}
public byte[] getEncoded() {
return mKey;
}
public String getAlgorithm() {
return "HmacSHA1";
}
public String getFormat() {
return "RAW";
}
}
public static String toHex(byte[] data) {
StringBuilder sb = new StringBuilder(data.length * 2);
for (int i=0; i<data.length; i++ ) {
sb.append(hex[(data[i] & 0xf0) >>> 4]);
sb.append(hex[data[i] & 0x0f] );
}
return sb.toString();
}
private static final char[] hex =
{ '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' ,
'8' , '9' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f'};
%><%
String casUser = request.getRemoteUser().toString().trim();
String redirect = generateRedirect(request, casUser);
response.sendRedirect(redirect);
%>
<html>
<head>
<title>Pre-auth redirect</title>
</head>
<body>
You should never see this page.
</body>
</html>
If you do not own a certificate for you CAS server signed by a trusted CA you will need to add the certicate into the zimbra keystore.
Code: Select all
keytool -import -file /tmp/cas.crt -alias cas -trustcacerts -keystore /opt/zimbra/common/etc/java/cacerts -storepass changeit