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;
	}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值