许久没写blog了,本人作为“猿”唯独缺一个“恒”字,前不久公司新项目需求实现cas单点登陆集成AD域,虽然实现过程曲折,但感觉很有意思,情不自禁记录之。主要功能实现以下2点:
1.加入域的计算机登陆实现免登陆进入系统
2.未加入域的计算机可以在登陆界面输入域用户登陆系统
准备
1.cas服务端使用的是cas-server-3.5.2,下载地址:http://developer.jasig.org/cas/
2.cas客户端使用的是cas-client-3.2.1
3.cas集成AD域所需jar包:jcifs-1.3.18.jar,jcifs-ext-0.9.4.jar;下载地址:http://developer.jasig.org/repo/content/groups/m2-legacy/org/samba/
4.集成环境为windows;本文的重点是cas集成ad,cas部署环境在此不再赘叙,此次集成主要参考官方标准文档:https://wiki.jasig.org/display/CASUM/SPNEGO
集成部署
- cas服务端修改
1.修改login-webflow.xml,在CAS目录下的WEB-INF文件夹下,在此配置文件中加入以下两个标签。
<action-state id="startAuthenticate">
<evaluate expression="negociateSpnego" />
<transition on="success" to="spnego" />
</action-state>
<action-state id="spnego">
<evaluate expression="spnego" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>
2,修改login-webflow.xml,将以下3个标签属性中的“viewLoginForm”修改为“startAuthenticate”。
<action-state id="passwordPolicyCheck">
<evaluate expression="passwordPolicyAction" />
<transition on="showWarning" to="passwordServiceCheck" />
<transition on="success" to="sendTicketGrantingTicket" />
<!-- <transition on="error" to="viewLoginForm" /> -->
<transition on="error" to="startAuthenticate" />
</action-state>
<action-state id="generateLoginTicket">
<evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
<!-- <transition on="generated" to="viewLoginForm" /> -->
<transition on="generated" to="startAuthenticate" />
</action-state>
<global-transitions>
<!-- CAS-1023 This one is simple - redirects to a login page (same as renew) when 'ssoEnabled' flag is unchecked
instead of showing an intermediate unauthorized view with a link to login page -->
<!-- <transition to="viewLoginForm" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException"/> -->
<transition to="startAuthenticate" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException"/>
<transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
<transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
</global-transitions>
3.修改CAS的cas-servlet.xml配置文件
cas-servlet.xml在CAS目录下的WEB-INF文件夹下,在cas-servlet.xml文件加入如下图所示的两个标签
<bean id="negociateSpnego" class="org.jasig.cas.support.spnego.web.flow.SpnegoNegociateCredentialsAction" />
<bean id="spnego" class="org.jasig.cas.support.spnego.web.flow.SpnegoCredentialsAction">
<property name="centralAuthenticationService" ref="centralAuthenticationService"/>
</bean>
4.修改CAS的deployConfigContextxml配置文件
deployerConfigContext.xml在CAS目录下的WEB-INF文件夹下,修改该文件bean标签“authenticationManager”的两个属性“credentialsToPrincipalResolvers”“authenticationHandlers”。
首先在“credentialsToPrincipalResolvers”属性的list标签中加入一个bean标签:
<bean class="org.jasig.cas.support.spnego.authentication.principal.SpnegoCredentialsToPrincipalResolver" />
然后在“authenticationHandlers”属性list标签中也加入一个bean标签:
<!--自定义的验证bean实现功能点2.未加入域的计算机可以在登陆界面输入域用户登陆系统-->
<bean class="org.jasig.cas.util.PwdAndUserTest"/>
<!--jcifs验证-->
<bean class="org.jasig.cas.support.spnego.authentication.handler.support.JCIFSSpnegoAuthenticationHandler">
<property name="authentication">
<bean class="jcifs.spnego.Authentication" />
</property>
<property name="principalWithDomainName" value="false" />
<property name="NTLMallowed" value="true"/>
</bean>
- 源码修改
下载JCIFS源码,CAS的分支SPNEGO的源码;JCIFS版本使用的jcifs-1.3.18.jar;CAS使用的cas-server-3.5.2-release;
按照以下步骤修改完源码后,替换原jar中对应的class文件。
1.修改JCIFS框架的Config类
jcifs.Config.java
原代码:filename = System.getProperty(“jcifs.properties”);
修改后:filename = “jcifs.properties”所在的路径地址。可以通过Java方法获取工程路径实现。
jcifs.properties文件是不存在的,需要自己创建。
文件内容:
jcifs.http.domainController=AD域IP地址
jcifs.smb.client.username=AD域管理员用户名
jcifs.smb.client.password=AD域管理员密码
jcifs.util.loglevel=2
jcifs.http.domainname=AD域名
jcifs.http.port=AD域默认端口号
2.修改CAS-spnego框架的SpnegoNegociateCredentialsAction类
org.jasig.cas.support.spnego.web.flow.SpnegoNegociateCredentialsAction.java
原代码:private boolean ntlm = false;
修改后:private boolean ntlm = true;
3.修改CAS-spnego框架的SpnegoCredentialsAction类
org.jasig.cas.support.spnego.web.flow.SpnegoCredentialsAction
原代码:private boolean ntlm = false;
修改后:private boolean ntlm = true;
最后将修改后的源码打成jar包。
- 部署jar包
把以下jar包复制到\WEB-INF\lib目录下,
cas-server-support-spnego-3.4.2.jar
jcifs-1.3.18.jar
jcifs-ext-0.9.4.jar
附:PwdAndUserTest.java单独验证实现功能点2
/*
* Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license
* distributed with this file and available online at
* http://www.ja-sig.org/products/cas/overview/license/
*/
package org.jasig.cas.util;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.util.StringUtils;
/**
* Simple test implementation of a AuthenticationHandler that returns true if the username and password match. This
* class should never be enabled in a production environment and is only designed to facilitate unit testing and load
* testing.
*
* @author Scott Battaglia
* @version $Revision: 19492 $ $Date: 2009-12-09 08:16:08 -0500 (Wed, 09 Dec 2009) $
* @since 3.0
*/
public final class PwdAndUserTest extends AbstractUsernamePasswordAuthenticationHandler
{
public PwdAndUserTest()
{
log.warn(this.getClass().getName()
+ " is only to be used in a testing environment. NEVER enable this in a production environment.");
}
public boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials)
{
final String username = credentials.getUsername();
final String password = credentials.getPassword();
System.out.println(username + "<==========>" + password);
if (StringUtils.hasText(username) && StringUtils.hasText(password))
{
if (CheckADUser(username, password))
{
log.debug("User [" + username + "] was successfully authenticated.");
return true;
}
}
log.debug("User [" + username + "] failed authentication");
return false;
}
private boolean CheckADUser(String username, String password)
{
String host = getProperties("jcifs.properties", "jcifs.http.domainController"); // AD服务器IP
String port = getProperties("jcifs.properties", "jcifs.http.port");// 端口
String user = getProperties("jcifs.properties", "jcifs.http.domainname") + "\\" + username;// 这里有两种格式,domain\User或邮箱的后缀名,建议用domain\User这种格式
String url = new String("ldap://" + host + ":" + port);
Hashtable env = new Hashtable();
DirContext ctx;
env.put(Context.SECURITY_AUTHENTICATION, "simple");// 一种模式,不用管,就这么写就可以了
env.put(Context.SECURITY_PRINCIPAL, user);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
try
{
ctx = new InitialDirContext(env);
ctx.close();
return true; // 验证成功返回true
}
catch (NamingException err)
{
return false;// 验证失败返回false
}
}
// 读取配置文件
public String getProperties(String name, String key)
{
PropertiesUtil p = new PropertiesUtil(name);
return p.readValue(key);
}
}
注意:
1.ie设置,工具-》Internet选项-》安全-》Internet-》自定义级别-》用户验证-》选为自动使用“当前用户名和密码登陆”。
加入域的计算机也可以通过AD域的组策略统一设置此设置。
2.win7免登陆后台报错:修改win7客户机HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa\LMCompatibilityLevel 为1,此方法太low,可以通过修改AD域服务器的组策略-》网络安全:LAN管理器身份验证级别。