`
jusescn
  • 浏览: 122313 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JCIFS支持多域的SSO

阅读更多

原谅我一开篇就骂人,折腾坏了。

====================================

介绍下背景: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;
	}

 

1
0
分享到:
评论
8 楼 弦月001 2014-09-25  
楼主目前对jcifs还记得吗?我目前在做jcifs sso研究开发,我想请问一下楼主,控制登陆提示框弹出说明有开关,这个开关是在域服务器控制吗?
不胜感激。
7 楼 jusescn 2013-01-04  
effort.mjb 写道
博主好,请问怎么样才能直接获取到当前客户端的用户的域信息呢?(注:我用js试过,用active控件试过,可以读取,但是IE总是会弹出安全验证信息,这个客户是不能接受的。),希望博主能给点详细的建议,最好能给个示例代码,非常感谢

effort.mjb 写道
博主好,请问怎么样才能直接获取到当前客户端的用户的域信息呢?(注:我用js试过,用active控件试过,可以读取,但是IE总是会弹出安全验证信息,这个客户是不能接受的。),希望博主能给点详细的建议,最好能给个示例代码,非常感谢

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 (ntlm != null){
   ntlm.getSome();//这里可以调用获得用户的域信息
}
6 楼 effort.mjb 2012-12-23  
博主好,请问怎么样才能直接获取到当前客户端的用户的域信息呢?(注:我用js试过,用active控件试过,可以读取,但是IE总是会弹出安全验证信息,这个客户是不能接受的。),希望博主能给点详细的建议,最好能给个示例代码,非常感谢
5 楼 jusescn 2012-12-21  
yingzhor 写道
博主,您好。 如果,我想扩展一下,如果客户端不再域内,直接把请求重定向到我指定的页面。 而不弹出密码输入框,我应该如何做。

望指点一二。


直接获得用户的域信息,如果没有就跳转下。easy.
4 楼 yingzhor 2012-12-10  
博主,您好。 如果,我想扩展一下,如果客户端不再域内,直接把请求重定向到我指定的页面。 而不弹出密码输入框,我应该如何做。

望指点一二。
3 楼 yuleiye 2012-12-10  
我最近在研究Jcifs用在域验证的问题,碰到不少问题,想请教您,方便的话加我QQ:38768264
2 楼 tevez0014 2012-09-17  
今天遇到个问题,jcifs貌似不支持 post提交方式。请问有遇到类似的问题么?
1 楼 zay1007 2011-12-14  
初学者向你请假域验证的问题 请加我q 5173202

相关推荐

Global site tag (gtag.js) - Google Analytics