AD域踩坑全记录


AD域与javaweb应用

本次出差主要做与AD域集成,过程是大坑套小坑,小坑里面还有小小坑,苦战一个星期,终于解放了自己,顺利回家了,哈哈哈…

javaweb应用程序与AD域身份认证

首先,要做统一身份认证,也就是说,要java应用程序去ad域验证这个人的用户名密码是否存在于ad域,这个比较简单,只有一个坑,直接上代码吧。

	private final String FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
	private final String URL = "ldap://127.0.0.1:389/";
	public boolean validateUP(String username,String pwd)throws NamingException{
		Hashtable<String,String> hashtable = new Hashtable<String,String>();
		hashtable.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
		hashtable.put(Context.PROVIDER_URL, URL);//服务器地址
		hashtable.put(Context.SECURITY_AUTHENTICATION, "simple");
		hashtable.put(Context.SECURITY_PRINCIPAL, username);//用户名
		hashtable.put(Context.SECURITY_CREDENTIALS, pwd);//密码
		return new InitialLdapContext(hashtable,null)!=null;
	}

看上去挺简单,而且试了下好用,嗯…以为很顺利了,但是,用了半天时间之后,不好使了!纳尼?
于是就开始网上各种的找资源,随后用了下面的,这波终于稳了。

	static String host = "172.16.7.11";// AD域IP,必须填写正确
	static String domain = "@raffles.local";// 域名后缀,例.@noker.cn.com
	static String port = "389"; // 端口,一般默认389
	public boolean validateUP(String userName, String password) {
		
		String url = new String("ldap://" + host + ":" + port);// 固定写法
		String user = userName.indexOf(domain) > 0 ? userName : userName + domain;// 网上有别的方法,但是在我这儿都不好使,建议这么使用
		Hashtable<String, String> env = new Hashtable<String, String>();// 实例化一个Env
		DirContext ctx = null;
		env.put(Context.SECURITY_AUTHENTICATION, "simple");// LDAP访问安全级别(none,simple,strong),一种模式,这么写就行
		env.put(Context.SECURITY_PRINCIPAL, user); // 用户名
		env.put(Context.SECURITY_CREDENTIALS, password);// 密码
		env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");// LDAP工厂类
		env.put(Context.PROVIDER_URL, url);// Url
		try {
			ctx = new InitialDirContext(env);// 初始化上下文
			System.out.println("身份验证成功!");
			return true;
		} catch (AuthenticationException e) {
			System.out.println("身份验证失败!");
			e.printStackTrace();
		} catch (javax.naming.CommunicationException e) {
			System.out.println("AD域连接失败!");
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("身份验证未知异常!");
			e.printStackTrace();
		} finally {
			if (null != ctx) {
				try {
					ctx.close();
					ctx = null;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		return false;
	}

好的,统一身份认证做好了,简单、舒服…接下来是自动登录了。

javaweb应用自动登录(免登录)

客户方有一个需求,大概意思就是要登录他们域内账户的电脑之后,不需要输入用户名密码,可以直接登入我们的系统。
听起来似乎很简单,做起来想简单也可以很简单。

第一版开始activeX:

既然是已经登录windows电脑了,那我只需要取到当前登录windows的这个人是谁就可以了,这是正常程序员思维,没毛病吧?
于是,通过查找资料,发现确实可以做到,但是只有ie能做到,并且需要启用activeX插件。类似于这样:
Alt

设置步骤:在“IE-Internet选项-安全-自定义级别-ActiveX控件和插件-对未标记为可安全执行脚本的ActivesX控件”,设置为“提示”或“启用”。
这种办法最简单,最容易,代码直接前端js获取就可以,

<script type="text/javascript">
window.function (){
	var WshNetwork = new ActiveXObject("WScript.Network");
	alert("Domain = " + WshNetwork.UserDomain);
	alert("Computer Name = " + WshNetwork.ComputerName);
	alert("User Name = " + WshNetwork.UserName);
}
</script>

很快,具体细节实现就不写了,无非就是进入登录页的时候,就获取用户名,然后拿着用户名去我们的系统匹配用户,如果存在该用户就加载权限等信息,然后登录成功。
唰唰唰…唰,搞定收工!
然后客户不想设置这东西,我靠,白做了,还好没费啥劲。

那就继续找办法吧。

第二版开始jcifs:

对于这个获取的过程,已经有实现过的插件了,在网上搜了下,我用的是jcifs-1.3.19.jar,然后不需要别的了,就可以自动获取到用户名了。
首先配置web.xml:

	<filter>
		<filter-name>MyNtlmHttpFilter</filter-name>
		<filter-class>jcifs.http.NtlmHttpFilter</filter-class>
		<init-param>
			<param-name>jcifs.http.domainController</param-name>
				<param-value>172.16.7.11</param-value>
		</init-param>
		<init-param>
			<param-name>jcifs.util.loglevel</param-name>
			<param-value>6</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>MyNtlmHttpFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

配置也很好理解,就是一个过滤器,初始化参数一个是域地址ip,一个是日志级别。ip改成自己的就好。
然后,页面代码:通过<%= request.getRemoteUser() %>就可以在页面端直接获取了,然后通过判断是否获取到来进行自动登录。
当然,因为我是试用的阶段是这样做的,如果这个jar实际可用的话,我就会写个过滤器,过滤一些路径,然后通过后台request获取用户名进行判断了…

都配好了,代码也写个差不多了,测一测,然后刚上来就报了个错

jcifs.smb.SmbException: The parameter is incorrect.
......

JCIFS的HttpFilter并不支持NTLM2协议,而当客户端是WIN7以上系统时,默认采用的是NTLM2协议。如果此时域控服务器也支持NTLM2,则会默认采用NTLM2协议验证。
在网上找了个办法,改客户机注册表。

REGEDIT4
[HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Lsa]
"LMCompatibilityLevel"=dword:00000001

将上面的代码保存为一个.reg的文件。然后双击运行一下。ok通了。
然而。。。下面我们来说第三版吧。。。

第三版开始Waffle

当时是在这里:https://www.w3cschool.cn/tomcat/6wds1ka3.html 看到的,waffle首选唉!于是下载了好几个版本,然后去试。
下载地址: https://github.com/Waffle/waffle
在这里插入图片描述
在smples文件夹下,我貌似发现了春天。。。于是开始看例子,然后自己往项目里配置。

web.xml开始:

	<filter>
		<filter-name>SecurityFilter</filter-name>
		<filter-class>waffle.servlet.NegotiateSecurityFilter</filter-class>
		<init-param>
			<param-name>principalFormat</param-name>
			<param-value>fqn</param-value>
		</init-param>
		<init-param>
			<param-name>roleFormat</param-name>
			<param-value>both</param-value>
		</init-param>
		<init-param>
			<param-name>allowGuestLogin</param-name>
			<param-value>true</param-value>
		</init-param>
		<init-param>
			<param-name>securityFilterProviders</param-name>
			<param-value>
    		waffle.servlet.spi.NegotiateSecurityFilterProvider
    		waffle.servlet.spi.BasicSecurityFilterProvider
    	</param-value>
		</init-param>
		<init-param>
			<param-name>waffle.servlet.spi.NegotiateSecurityFilterProvider/protocols</param-name>
			<param-value>
    		Negotiate
    		NTLM
    	</param-value>
		</init-param>
		<init-param>
			<param-name>waffle.servlet.spi.BasicSecurityFilterProvider/realm</param-name>
			<param-value>WaffleFilterDemo</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>SecurityFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<servlet>
		<servlet-name>WaffleInfo</servlet-name>
		<servlet-class>waffle.servlet.WaffleInfoServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>WaffleInfo</servlet-name>
		<url-pattern>/waffle</url-pattern>
	</servlet-mapping> 

然后同样用<%= request.getRemoteUser() %>来获取用户名,ok启动项目,完美搞定,于是升级测试系统,我*******好吧。
看下图:
在这里插入图片描述

Waffle在Linux(类UNIX)上不起作用???
我真的是不服。。。继续

第四版开始记ip

本人想,既然是自动登录,我记录ip和用户名总可以的吧,然后下次这个人就免登录啊。于是说干就干,简单做了下,
下面是后台获取ip的代码:

	public static String getIp2(HttpServletRequest request) {
	      String ip = request.getHeader("x-forwarded-for"); 
	        System.out.println("x-forwarded-for ip: " + ip);
	        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {  
	            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
	            if( ip.indexOf(",")!=-1 ){
	                ip = ip.split(",")[0];
	            }
	        }  
	        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
	            ip = request.getHeader("Proxy-Client-IP");  
	            System.out.println("Proxy-Client-IP ip: " + ip);
	        }  
	        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
	            ip = request.getHeader("WL-Proxy-Client-IP");  
	            System.out.println("WL-Proxy-Client-IP ip: " + ip);
	        }  
	        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
	            ip = request.getHeader("HTTP_CLIENT_IP");  
	            System.out.println("HTTP_CLIENT_IP ip: " + ip);
	        }  
	        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
	            ip = request.getHeader("HTTP_X_FORWARDED_FOR");  
	            System.out.println("HTTP_X_FORWARDED_FOR ip: " + ip);
	        }  
	        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
	            ip = request.getHeader("X-Real-IP");  
	            System.out.println("X-Real-IP ip: " + ip);
	        }  
	        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {  
	            ip = request.getRemoteAddr();  
	            System.out.println("getRemoteAddr ip: " + ip);
	        } 
	        System.out.println("获取客户端ip: " + ip);
	        return ip;  
    }

但是,这种办法就不是一个办法,因为例如,同一个计算机,有多个人登录,第一个人登录之后,记录ip和账号,下次登录就免登录了,但第二个人也用这台电脑,然后他刚一访问,就进到第一个人登录之后的页面了。

第五版jespa

目前来讲,是纯java实现的最好解决方案,但是它收费,免费试用两个月。于是我就没怎么研究。
而且,我只知道它有一点,就是必须要运行他们提供的一个文件,用来在域内生成一个管理员的用户名密码,对于这种,我有点hold不住。
因为我是想不到什么办法去让他们运行那个文件。。。ps:人家想万一是病毒呢?漏洞呢?…唉,一般客户都比较小心眼的,而且收费软件人家也不想用。

最终版.net版:

因为.net是可以直接开启域验证的,这是java所不能比的。
所以,利用iframe嵌入.net的域验证,通过.net给返回的用户名实现自动登录。

问题:涉及到跨域,以及.net程序代码。

1、.net获取当前登录域用户名,可以参考:https://blog.csdn.net/FAST_Michael/article/details/6248913
2、来说iframe跨域问题:
对于我java的登录页,ssologin.jsp

<iframe id="adIframe" name="adIframe" style="display:none;" src="http://10.80.4.39:9898/IframeAD.aspx?toUrl=/pl"></iframe>

<script >
		function setADDomainUser(text){
			// 处理过程
    	}
</script>

然后,在加入ADLoginFrame.jsp
在这里插入图片描述

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
    <title>Insert title here</title>
    <script>
        window.onload = function(){
            var text = window.location.href.split('=')[1];
            console.log(text);
            parent.parent.setADDomainUser(text);
        }
    </script>
</head>
<body>
</body>
</html>

iframe跨域,大概就是登录页嵌入.net验证页面地址,然后.net验证后返回给ADLoginFrame.jsp,然后ADLoginFrame.jsp中访问parent.parent.setADDomainUser(text);

到时效果就是第一次弹窗让输入域用户名密码,第二次就不用输入了,直接自动登录。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值