原谅我一开篇就骂人,折腾坏了。
====================================
介绍下背景:JCIFS是现在大多数主流的域登陆SSO采用的开源软件,包含如spring-acegi、liferay等都采用作为SSO的一部分。
可怜的悲剧来了。
JCIFS在单域环境下的确适用,但是在多个域的情况下,俺看了他所有的mailList,就只得到这么一句话,你需要在两个域之间建立信任关系,这样即使多个域情况下,也是能够SSO的。看来老外就是老外,在中国,修改配置是责任问题,这个你们懂的。
还好有个AJ的哥们说了,他改了源码,现在beta版,能支持多域了,俺看见曙光了。
=====================================
先初始说配置:
//jcifs.smb.client.soTimeout 这个参数默认30000,意思是你只要登陆,切换第二个用户就甭想进去了。
// JE上有个哥们提了同样的问题,说是单域下不能切换用户,还自己回答了,唉。。咋不说的更清楚些呢。
//jcifs.smb.client.soTimeout 不能太大了,否则切换不了用户,太小了,又登不进去。这个配置是关键
Config.setProperty( "jcifs.smb.client.soTimeout", "100" );//100
Config.setProperty( "jcifs.netbios.cachePolicy", "1200" );
Config.setProperty( "jcifs.smb.lmCompatibility", "0" );
//jcifs.smb.lmCompatibility的值大于3下面的值为true
Config.setProperty( "jcifs.smb.client.useExtendedSecurity", "false" );
多域思路:
NTLM SSO的实现原理:http://www.cnblogs.com/adylee/articles/975213.html
NTLM 实现域用户名和密码 的核心 代码可以参考 jcifs.http.NtlmHttpFilter.negotiate(req,res)
改造如下:
/**
* 通过cifs获得登陆的用户名,用于自动登录,选择进行域验证
* @param req
* @param resp
* @param domainController 域的域控制地址,通常为IP地址
* @param skipDomainValidate 跳过域验证
* @return
* @throws Exception
*/
protected NtlmPasswordAuthentication negotiate( HttpServletRequest req,
HttpServletResponse resp,String domainController,
boolean skipDomainValidate) throws Exception {
NtlmPasswordAuthentication ntlm = null;
UniAddress dc = null;
String msg = req.getHeader( "Authorization" );
boolean offerBasic = enableBasic && (insecureBasic || req.isSecure());
if( msg != null && (msg.startsWith( "NTLM " ) || (offerBasic && msg.startsWith("Basic ")))) {
if (msg.startsWith("NTLM ")) {
dc = UniAddress.getByName( domainController,true );
byte[] challenge = SmbSession.getChallenge( dc );
if(( ntlm = NtlmSsp.authenticate( req, resp, challenge )) == null ) {
return null;
}
}
if ( skipDomainValidate ){
return ntlm;
}
try {
SmbSession.logon( dc, ntlm );
return ntlm;
} catch( Exception e ) {
System.out.println(e.getClass().getName()+":"+e.getMessage());
resp.setHeader( "WWW-Authenticate", "NTLM" );
resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
resp.setContentLength(0);
resp.flushBuffer();
return null;
}
}else{
resp.setHeader( "WWW-Authenticate", "NTLM" );
resp.setStatus( HttpServletResponse.SC_UNAUTHORIZED );
resp.setContentLength(0);
resp.flushBuffer();
return null;
}
}
多域SSO分两部分:1、自动登录部分;2、切换用户部分。
1、自动登录(需要将地址添加到信任站点)
说思路:自动登陆时,比如调用链接:accout.do?method=autoLogin,这时候调用我们修改后的negotiate方法,传入参数(req,res,任意的域IP,true)。我们此时跳过域用户验证部分,只是为了获得客户端提交的信息,这时候,我们就能通过ntlm.getDomain()获得用户需要登陆的域,比如域ds2,现在需要将ds2转会为IP地址(可以定义一个MAP,保存域与IP的对应关系);将获得的IP地址保存在session内,跳转到我们真正进行验证的action的ntlmLogin方法,执行negotiate(req,res,域对应的IP,false),这时候进行验证(防止山寨登陆),这样自动登录就完成了;出错处理,跳转到手工登陆页面。
public ModelAndView autoLogin(HttpServletRequest request,
HttpServletResponse response){
StringBuffer scripts = new StringBuffer();
String ctx = request.getContextPath();
try {
String randomDC = getRandomDomainController();
NtlmPasswordAuthentication ntlm = negotiate(request, response,randomDC,true);
if ( ntlm == null )return null;
//获得域对应的IP地址
String domainController = getDomainController(ntlm.getDomain());
if (logger.isDebugEnabled()) {
logger.debug("客户端信息="+ntlm.getDomain() + ":"+ntlm.getUsername()+"<"+domainController+">");
}
//跳转到真正的登陆方法
request.getSession().setAttribute(SESSION_DOMAINCONTROLLER, domainController);
scripts.append("location.href='"+request.getContextPath()+"/accout.do?method=ntlmLogin';");
super.writeScripts(response, scripts.toString());
return null;
} catch (Exception e) {
e.printStackTrace();
//出错,跳转到 用户手工登陆页面
scripts.append("alert(\"系统错误,请通知管理员\");");
//执行该句话前,小心你的cookie。
scripts.append("document.execCommand('ClearAuthenticationCache');");
scripts.append("location.href='"+ctx + "/accout.do?method=switchLogin';");
super.writeScripts(response, scripts.toString());
return null;
}
}
public ModelAndView ntlmLogin(HttpServletRequest request,
HttpServletResponse response){
String ctx = request.getContextPath();
StringBuffer scripts = new StringBuffer();
try {
String domainController =(String)request.getSession().getAttribute(SESSION_DOMAINCONTROLLER);
if (logger.isDebugEnabled()) {
logger.debug("domainController="+domainController);
}
//用户登陆,开始验证密码
NtlmPasswordAuthentication ntlm = negotiate(request, response,domainController,false);
if ( ntlm == null )return null;
//验证用户部分
//验证用户结束
//自动登陆
// 登录成功跳转到子系统首页
} catch (Exception e) {
e.printStackTrace();
//出错,跳转到 用户手工登陆页面
scripts.append("alert(\"系统错误,请通知管理员\");");
scripts.append("document.execCommand('ClearAuthenticationCache');");
scripts.append("location.href='"+ctx + "/accout.do?method=switchLogin';");
super.writeScripts(response, scripts.toString());
return null;
}
}
2、切换用户
需要解决的两个问题:1、当能自动登录时,表示当服务器向客户端发出401时,IE自动会用当前用户和密码进行提交,不会弹出window的用户登陆提示框(这是用户要求的,非要用window那个登陆提示框),如何控制登陆提示框是否出来是个问题;
2、客户端凭证无法删除,切换用户后,后台还是第一个用户?
解决:
1、控制登陆提示框弹出说明有开关。
2、切换用户或者注销用户后,客户端必须执行document.execCommand('ClearAuthenticationCache');
思路:
切换用户,基本没有采用ntlm了,但是用户名和密码验证还是用它的。代码如下(realm随便瞎写什么都行)
/**
* 切换用户
*/
public ModelAndView switchLogin(HttpServletRequest request,
HttpServletResponse response){
String ctx = request.getContextPath();
try {
String auth = request.getHeader("Authorization");
if ( StringUtils.isBlank(auth)){
//弹出验证框
response.setStatus(response.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "Basic realm=\""+realm+"\"");
response.flushBuffer();
return null;
}
if(auth.startsWith("Basic ")){
//用户名和密码解密
String username_pw = new String(Base64.decode(auth.substring(6)));
String[] array = username_pw.split(":");
String userDomain = array[0];
//明文密码
String password = array[1];
String[] user_domain = userDomain.split(\\\\);
//用户需要登陆的域
String domain = user_domain[0];
//用户名
String princal = user_domain[1];
//获得域对应的IP
String domainController = getDomainController(domain);
//去域验证用户名和密码
boolean validate = validateDomainUser(domainController,princal, password);
//用户验证成功 ,通过用户名登陆
// 登录成功跳转到子系统首页
return null;
}else{
response.setStatus(response.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "Basic realm=\""+realm+"\"");
response.flushBuffer();
return null;
}
} catch (Exception e) {
e.printStackTrace();
scripts.append("alert(\"系统错误,请通知管理员\");");
scripts.append("document.execCommand('ClearAuthenticationCache');");
scripts.append("location.href='"+ctx + "/accout.do?method=switchLogin';");
super.writeScripts(response, scripts.toString());
return null;
}
}
/**
* 验证域用户名和密码
*/
private boolean validateDomainUser(String domainController,String username,String password){
try {
UniAddress mydomaincontroller = UniAddress.getByName( domainController );
NtlmPasswordAuthentication mycreds1 = new NtlmPasswordAuthentication( "", username, password );
SmbSession.logon( mydomaincontroller, mycreds1 );
return true;
} catch (Exception e) {
if (logger.isDebugEnabled()) {
logger.debug("validateDomainUser(String, String, String) - Exception e=" + e); //$NON-NLS-1$
}
}
return false;
}
分享到:
相关推荐
jcifs-ext是jcifs的扩展版本,增加了更多的功能和改进,以更好地支持与Windows环境的交互,如AD域服务的集成。 Active Directory是微软提供的一种目录服务,用于管理和存储有关网络对象(如用户、计算机、组等)的...
1. **身份验证**:jcifs支持NTLM(NT LAN Manager)和Kerberos两种Windows身份验证协议。NTLM是较老的认证方式,而Kerberos是更安全的选择,尤其适合跨域环境。jcifs库可以处理这些复杂的认证流程,使Java应用能够与...
2. **NTLM认证实现**:使用支持NTLM的库(如jcifs、jcacert等)创建认证上下文,处理服务器发来的挑战信息。 3. **用户验证**:将用户输入的凭证与AD服务器进行验证,通过NTLM协议进行身份确认。 4. **SSO实现**:...
特别强调的是,Jespa支持NTLMv2协议,能够实现Web客户端的单点登录(SSO)功能。通过HttpSecurityService,它可以与saslclient NTLMv2的HttpURLConnection协作,为Oracle JNDI LDAP实现NTLMv2身份验证和加密传输。这...
- 确保已经正确导入并配置了NTLM支持的库,如jcifs.jar。 此外,如果认证失败,可以使用网络抓包工具(如Wireshark)捕获网络流量,查看NTLM协商过程是否正常,以排查问题。 总的来说,这个Java代码示例是实现NTLM...
- **NTLM via JCIFS**:支持NTLM协议; - **OpenID**:支持开放身份验证标准; - **SiteMinder**:与CA公司的安全解决方案集成; - **Atlassian Crowd**:与Atlassian的用户管理工具集成; - **jCaptcha**:实现...
SSO是一种让用户在访问多个相互关联的应用时只需一次登录的技术,极大地提高了用户体验和安全性。 描述中提及的"Liferay Portal"是一个知名的开源企业门户平台,它使用了JCIFS(Java CIFS Client)库的扩展来处理...