`
tomhibolu
  • 浏览: 1431481 次
文章分类
社区版块
存档分类
最新评论

JAVA中如何集成AD域用户认证

 
阅读更多

最近在一个大项目中碰到有人需要在JAVA中如何集成AD域用户认证,这个问题从纯粹的技术角度来讲还真是不难,大不了自己将NTLM或者Kerberos的协议实现一遍,终归可以将此事搞定,不过世界上汽车轮子早造好了,还是用已经有的轮子吧,呵呵。

NTLM HTTP认证

过程如下:

1: C --> S GET ...

2: C <-- S 401 Unauthorized

WW-Authenticate: NTLM

3: C --> S GET ...

Authorization: NTLM <base64-encoded type-1-message>

4: C <-- S 401 Unauthorized

WWW-Authenticate: NTLM <base64-encoded type-2-message>

5: C --> S GET ...

Authorization: NTLM <base64-encoded type-3-message>

6: C <-- S 200 Ok

从交互过程可以发现,client会发送type-1消息和type-3消息给server,而server会发送type-2消息给client

Type-1消息包括机器名、Domain

Type-2消息包括server发出的NTLM challenge

Type-3消息包括用户名、机器名、Domain、以及两个根据server发出的challenge计算出的response,这里response是基于challenge和当前用户的登录密码计算而得

具体细节参考下面两个网址:

http://www.innovation.ch/personal/ronald/ntlm.html

http://davenport.sourceforge.net/ntlm.html#whatIsNtlm

注:

IE里,上述的交互会由浏览器自动完成,M$总是有办法自己到OS里去拿到Domain、用户名、密码等等信息的,而FF就没有这么方便了,它必须要用户手工输入,当server返回401错误后,FF会弹出该对话框让用户输入用户名、密码(在IE中,如果使用当前登录的用户名、密码验证失败后也会弹出这样的对话框)

OK,有了NTLM HTTP认证协议,下面要实现SSO就方便多了。这时server已经拿到client的认证信息:用户名、Domain、密码和challenge的某个运算值,这时server只要利用这些信息连接到ADActive Directory,活动目录)(或者其他认证服务器)进行认证即可。

但这里还有个问题,因为server拿到的并不是密码,而是密码的某个单向hash值,那怎么用这个信息到AD上认证呢?

答案是SMBServer Message Block)!

SMBM$用来进行局域网文件共享和传输的协议,也称为CIFSCommon Internet File System),CIFS协议的细节可以在MSDN上查到:

http://msdn2.microsoft.com/en-us/library/aa302240.aspx

也可以到samba上去看看最新的一些发展:

http://www.samba.org/

我们着重看一下CIFS协议里连接和断开连接的部分:

连接:

断开连接:

OK,看起来蛮复杂的,不过没关系,关键我们要知道,在CIFS连接server(比如AD)时,首先server会发一个叫做EncryptionKey的东东给client,然后client会利用和NTLM HTTP认证中一样的算法计算出一个responseserver,这个细节很关键!

因为如果http server(在这里充当CIFSclient)用这个EncryptionKey作为给http clientchallengehttp client会计算出responsehttp server,然后http server就可以拿着这个responseAD上验证了!

现在有三个参与者了:http clienthttp serverAD

想象一下,首先http clienthttp请求给http server,为了对这个client认证,http server首先连接AD,然后就得到一个EncryptionKey,它就把这个EncryptionKey作为challenge返回给http client,然后http client会根据这个challenge和用户密码计算出response送给http server,而http server就拿着这个responseAD去认证了J

下图就表示整个这个过程:

现在,我们已经有足够的理论武装起来可以实现SSO了,但是,难道要我们自己去实现这些协议吗?当然可以,有兴趣可以尝试一下J

不过另一个选择是使用Open SourcelibraryjCIFS就是干这些事情的。

--------------------------------------------------------------------------------------

jCIFSsamba组织下的一帮牛开发的一套兼容SMB协议的library,我们可以用它来在java里访问Windows共享文件,当然,既然它帮我们实现了SMB协议,那要用它来实现NTLM SSO就很容易了。

http://jcifs.samba.org/

在这个网址可以下载到jCIFSsource codelibrary

好,现在可以休息一下了,我们通过一个例子step by step看一下jCIFS怎么来实现SSO吧。

1.jcifs-1.2.13.jar放到tomcatwebapp目录

2.创建一个web.xml,用于创建一个servlet filter,处理http连接(记得把里面的ip地址替换为你自己的AD serverip地址)

<web-app xmlns="http://java.sun.com/xml/ns/javaee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

version="2.5">

<display-name>Welcome to Tomcat</display-name>

<description>

Welcome to Tomcat

</description>

<filter>

<filter-name>NtlmHttpFilter</filter-name>

<filter-class>jcifs.http.NtlmHttpFilter</filter-class>

<init-param>

<param-name>jcifs.http.domainController</param-name>

<param-value>10.28.1.212</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>NtlmHttpFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

</web-app>

3.重新启动tomcat,打开http://localhost:8080/,如果用的IE,就会自动使用当前用户进行验证,而如果使用FF,就会弹出对话框,输入用户名密码后就可以验证通过,看到tomcat的页面了

这个例子够简单的,jCIFS应用也确实非常简单了,当然如果你要实现一些其他特性,比如根据当前登录的用户账户决定用户的权限、以及看到页面的内容,那你就必须通过jCIFSAPI去操作了,可以参考jCIFSAPI文档:

http://jcifs.samba.org/src/docs/api/

最后,说点这个方案的问题和不足吧,

-首先由于jCIFS只是应用了SMB协议进行认证,这样它就没办法拿到用户的其他的一些信息,比如组信息或者权限信息。对于这个问题,一般可以由我们自己的应用程序通过LDAPAD上去存取,但毕竟增加了我们的工作。

-第二个不足是,NTLM认证是一个M$准备放弃的协议,在Windows 2000和以后的操作系统中,缺省的认证协议是Kerberos,只有在和2000之前的系统通信时才使用NTLM。当然这并不是说jCIFS2000以上就用不起来了,缺省情况总是可以用的,M$总是要保持兼容的J当然如果你想实现基于KerberosSSO,你可以去参考下面列出的文章,但这就不是这里讨论的话题了。

http://free.tagish.net/jaas/

http://java.sun.com/j2se/1.4.2/docs/guide/security/jgss/single-signon.html

附录部分给出NTLM协议和算法的细节,不感兴趣的就不用管它了,反正这些会由client(一般是IEFF)和jCIFS已经帮我们处理了。

Type-1消息格式

struct {

byte protocol[8];// 'N', 'T', 'L', 'M', 'S', 'S', 'P', '"0'

byte type;// 0x01

byte zero[3];

short flags; // 0xb203

byte zero[2];

short dom_len;// domain string length

short dom_len;// domain string length

short dom_off;// domain string offset

byte zero[2];

short host_len;// host string length

short host_len;// host string length

short host_off;// host string offset (always 0x20)

byte zero[2];

byte host[*];// host string (ASCII)

byte dom[*];// domain string (ASCII)

} type-1-message;

Type-2消息格式

struct {

byte protocol[8];// 'N', 'T', 'L', 'M', 'S', 'S', 'P', '"0'

byte type;// 0x02

byte zero[7];

short msg_len;// 0x28

byte zero[2];

short flags; // 0x8201

byte zero[2];

byte nonce[8];// nonce

byte zero[8];

} type-2-message;

Type-3消息格式

struct {

byte protocol[8];// 'N', 'T', 'L', 'M', 'S', 'S', 'P', '"0'

byte type;// 0x03

byte zero[3];

short lm_resp_len;// LanManager response length (always 0x18)

short lm_resp_len;// LanManager response length (always 0x18)

short lm_resp_off;// LanManager response offset

byte zero[2];

short nt_resp_len;// NT response length (always 0x18)

short nt_resp_len;// NT response length (always 0x18)

short nt_resp_off;// NT response offset

byte zero[2];

short dom_len;// domain string length

short dom_len;// domain string length

short dom_off;// domain string offset (always 0x40)

byte zero[2];

short user_len;// username string length

short user_len;// username string length

short user_off; // username string offset

byte zero[2];

short host_len;// host string length

short host_len;// host string length

short host_off;// host string offset

byte zero[6];

short msg_len; // message length

byte zero[2];

short flags;// 0x8201

byte zero[2];

byte dom[*];// domain string (unicode UTF-16LE)

byte user[*];// username string (unicode UTF-16LE)

byte host[*];// host string (unicode UTF-16LE)

byte lm_resp[*];// LanManager response

byte nt_resp[*];// NT response

} type-3-message;

Response的计算算法

/* setup LanManager password */

char lm_pw[14];

int len = strlen(passw);

if (len > 14) len = 14;

for (idx=0; idx<len; idx++)

lm_pw[idx] = toupper(passw[idx]);

for (; idx<14; idx++)

lm_pw[idx] = 0;

/* create LanManager hashed password */

unsignedchar magic[] = { 0x4B, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25 };

unsignedchar lm_hpw[21];

des_key_schedule ks;

setup_des_key(lm_pw, ks);

des_ecb_encrypt(magic, lm_hpw, ks);

setup_des_key(lm_pw+7, ks);

des_ecb_encrypt(magic, lm_hpw+8, ks);

memset(lm_hpw+16, 0, 5);

/* create NT hashed password */

int len = strlen(passw);

char nt_pw[2*len];

for (idx=0; idx<len; idx++)

{

nt_pw[2*idx] = passw[idx];

nt_pw[2*idx+1] = 0;

}

unsignedchar nt_hpw[21];

MD4_CTX context;

MD4Init(&context);

MD4Update(&context, nt_pw, 2*len);

MD4Final(nt_hpw, &context);

memset(nt_hpw+16, 0, 5);

/* create responses */

unsignedchar lm_resp[24], nt_resp[24];

calc_resp(lm_hpw, nonce, lm_resp);

calc_resp(nt_hpw, nonce, nt_resp);

Helpers:

/*

* takes a 21 byte array and treats it as 3 56-bit DES keys. The

* 8 byte plaintext is encrypted with each key and the resulting 24

* bytes are stored in the results array.

*/

void calc_resp(unsignedchar *keys,unsignedchar *plaintext,unsignedchar *results)

{

des_key_schedule ks;

setup_des_key(keys, ks);

des_ecb_encrypt((des_cblock*) plaintext, (des_cblock*) results, ks, DES_ENCRYPT);

setup_des_key(keys+7, ks);

des_ecb_encrypt((des_cblock*) plaintext, (des_cblock*) (results+8), ks, DES_ENCRYPT);

setup_des_key(keys+14, ks);

des_ecb_encrypt((des_cblock*) plaintext, (des_cblock*) (results+16), ks, DES_ENCRYPT);

}

/*

* turns a 56 bit key into the 64 bit, odd parity key and sets the key.

* The key schedule ks is also set.

*/

void setup_des_key(unsignedchar key_56[], des_key_schedule ks)

{

des_cblock key;

key[0] = key_56[0];

key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1);

key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2);

key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3);

key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4);

key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5);

key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6);

key[7] = (key_56[6] << 1) & 0xFF;

des_set_odd_parity(&key);

des_set_key(&key, ks);

}

----------------------------------------------------------------------------------------------------------------------------------------------

不过由于此种方式jcifs仅仅实现了NTLM,而Window7和Vista使用的是NTLM2,因此在某些版本使用这个方案是有问题的,好在这些大牛们在2009版开发时进行了扩展将AD新版的NTLMv2以及Kerberos做了很好的支持,总算前途一片坦荡,呵呵。

分享到:
评论

相关推荐

    Java-AD域认证实现

    打包命令为:mvn clean package Jar包运行命令为:java -jar C:\Users\z00459km\Desktop\demo-0.0.1-SNAPSHOT.jar 亲测AD域认证通过,内容包含两种认证信息写法。

    java实现AD域认证

    AD域认证则是指通过AD服务对用户身份进行验证的过程。 #### 二、Java 实现 AD 认证的方法 Java 中可以通过利用标准的 LDAP (Lightweight Directory Access Protocol) 协议与 AD 服务器交互来实现 AD 认证。Java 的...

    java验证AD域用户登录

    Java验证AD域用户登录是企业级应用中常见的一种身份验证方式,主要用于确保只有授权的用户才能访问特定的系统或服务。AD(Active Directory)域是由微软Windows Server操作系统提供的目录服务,用于集中管理用户账户...

    Java AD域插入用户和密码修改

    1. **导入AD域证书**:在Java应用中使用AD域服务之前,必须先导入AD域的证书,以建立安全的SSL/TLS连接。这通常涉及将证书导入到Java的信任库(cacerts),可以通过`keytool`命令行工具完成。 2. **配置环境**:...

    java使用ldap修改ad域用户密码

    在使用 Keytool 工具后,我们需要导入 CA 证书,以便 Java 应用程序可以信任域控制器的认证中心。 编写 Java 代码 最后,我们可以编写 Java 代码来连接到 Active Directory 域控制器,并对用户密码进行修改。 ```...

    cas集成AD域

    3. **配置AD域验证**:在CAS服务器的配置中,设置认证处理器为支持AD的LdapAuthenticationHandler,指定搜索基DN(Distinguished Name)、过滤器和属性映射。这样,当用户尝试登录时,CAS会将用户名发送到AD进行验证...

    JAVA ldap AD 域 免证书 查询 修改 删除 新增 启用 禁用 修改密码

    在这篇文章中,我们将探讨使用 JAVA 实现 LDAP 的 AD 域免证书查询、修改、删除、新增、启用、禁用和修改密码的操作。 首先,让我们了解什么是 LDAP 和 AD 域。LDAP(Lightweight Directory Access Protocol)是一...

    LDAP实现AD域账号验证 - Java/SpringBoot

    在提供的压缩包`springboot-ldap`中,应该包含了示例代码,展示如何在SpringBoot项目中配置和使用LDAP进行AD域账号验证。通过学习和理解这些代码,你可以更好地掌握如何在自己的项目中实现这一功能。务必仔细阅读并...

    java连接AD进行用户登陆

    对于需要集成 AD 的 Java 应用程序而言,能够通过 Java 语言与 AD 进行交互是非常重要的能力之一。本文将详细介绍如何使用 Java 实现与 Active Directory 的连接以及进行用户登录验证的过程。 #### 二、关键概念与...

    java操控AD域源码

    Java操控AD域源码主要涉及的是使用Java编程语言与Active Directory(AD)...通过这个源码,开发者可以学习如何在Java应用中集成AD域服务,这对于开发企业级应用,尤其是需要统一用户管理和权限控制的应用来说非常有用。

    java ad域操作

    总结起来,Java通过JNDI和相关的库提供了强大的能力来操作AD域,包括连接、查询、读写属性、用户认证、对象的创建与删除等。在实际开发中,应确保遵循最佳实践,确保代码的健壮性和安全性。理解并熟练掌握这些概念和...

    AD域单点登陆NTLM

    4. **SSO实现**:验证成功后,用户可以无感知地访问其他系统,因为已经通过AD域认证。 DEMO包通常包含一个示例程序,展示了如何使用提供的JAR包进行实际操作。`index.jsp`可能是一个简单的登录页面,用户在此输入...

    kerberos认证过程,AD域认证

    ### Kerberos认证过程详解 #### 一、基本原理与核心概念 Kerberos是一种广泛使用的安全协议,主要用于网络环境下的身份验证(Authentication)。身份验证的目的在于确认一个人或实体是否确实如其所声称的身份那样。...

    JAVA修改AD域密码_免证书

    在本文中,我们将深入探讨如何使用Java JNDI来修改AD域密码,并重点讲解如何实现免SSL验证的方式。 首先,让我们了解JNDI。JNDI是Java平台的一个接口,它提供了一组API,允许开发者查找和绑定网络资源,如DNS记录、...

    java使用ldap修改ad域用户密码收集.pdf

    6、在安装证书服务的服务器中,需要导出域根证书和计算机证书,并使用 Java 的 keytool 工具创建或导入证书库文件中。 7、使用 Java 语言可以编写代码来修改 Active Directory 域用户密码,需要使用 LDAP 协议和 ...

    Springboot-LDAP针对AD域控做用户和组织进行同步.zip

    总之,通过Spring Boot 2.x和LDAP的集成,我们可以构建出高效、安全的企业级应用,实现与AD域控制器的无缝对接,从而方便地管理和同步用户及组织信息。这对于大型企业来说,是提高IT效率、保证数据安全的关键步骤。

    JAVA对接AD域集成

    在Java开发中,"JAVA对接AD域集成"指的是将Java应用程序与Active Directory (AD) 域服务进行整合,以实现用户身份验证、权限管理等功能。以下是对代码片段中的关键知识点的详细解释: 1. **LDAP (轻量级目录访问...

    java操作Ldap,支持建立开启状态的用户,支持修改密码,放入eclipse测试即用

    本项目提供了一种便捷的方式,允许开发者在Java环境中创建、管理和更新AD域中的用户信息,并且可以直接在Eclipse集成开发环境中进行测试。 首先,我们需要了解LDAP。LDAP是一种用于存储和检索分布式目录信息的标准...

    页面AD用户自动登录程序

    如果验证成功,服务器会创建一个会话cookie或者JWT(JSON Web Token),这个令牌包含了用户的认证信息,以便后续请求中验证用户身份。然后,服务器返回一个响应,指示前端页面用户已经成功登录,并可以访问受保护的...

    java创建AD账号等

    首先需要使用域管理员的身份认证AD服务器,这通常通过设置`Hashtable`环境变量来完成,例如: ```java Hashtable, String&gt; env = new Hashtable, String&gt;(); env.put(Context.INITIAL_CONTEXT_FACTORY, ...

Global site tag (gtag.js) - Google Analytics