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插件。类似于这样:
设置步骤:在“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);
到时效果就是第一次弹窗让输入域用户名密码,第二次就不用输入了,直接自动登录。