Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1944215
  • 博文数量: 1000
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7921
  • 用 户 组: 普通用户
  • 注册时间: 2013-08-20 09:23
个人简介

storage R&D guy.

文章分类

全部博文(1000)

文章存档

2019年(5)

2017年(47)

2016年(38)

2015年(539)

2014年(193)

2013年(178)

分类: 服务器与存储

2015-06-10 13:50:54

在网上搜了一些资料,同时也对 NTLM 的认证方式有了些了解,记录之。

 

NTLM HTTP 认证

过程如下:

 

    1: C  --> S   GET ...

   

    2: C <--  S   401 Unauthorized

                 WW-Authenticate: NTLM

   

    3: C  --> S   GET ...

                 Authorization: NTLM

   

    4: C <--  S   401 Unauthorized

                 WWW-Authenticate: NTLM

   

    5: C  --> S   GET ...

                 Authorization: NTLM

   

    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 和当前用户的登录密码计算而得

 

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

 

注:

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

 

 

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

 

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

 

答案是 SMB  Server Message Block )!

 

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

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

 /

 

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

 

 

连接:

 

 

 

断开连接:

 

 

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

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

 

现在有三个参与者了: http client  http server  AD

 

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

 

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

 

 

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

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

 

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

在这个网址可以下载到 jCIFS  source code  library

 

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

1.         jcifs-1.2.13.jar 放到 tomcat  webapp 目录

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

   xmlns:xsi=""

   xsi:schemaLocation=" /web-app_2_5.xsd"

   version="2.5">

 

  Welcome to Tomcat

 

     Welcome to Tomcat

 

 

    NtlmHttpFilter

    jcifs.http.NtlmHttpFilter

 

   

        jcifs.http.domainController

        10.28.1.212

   

   

    jcifs.util.loglevel

    6

   

 

 

 

    NtlmHttpFilter

    /*

 

 

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

 

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

 

 

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

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

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

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

 

 

附录部分给出 NTLM 协议和算法的细节,不感兴趣的就不用管它了,反正这些会由 client (一般是 IE  FF )和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

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

for (; idx<14; idx++)

    lm_pw[idx] = 0;

 

 

/* create LanManager hashed password */

 

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

unsigned char 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

{

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

    nt_pw[2*idx+1] = 0;

}

 

unsigned char 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 */

 

unsigned char 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(unsigned char *keys, unsigned char *plaintext, unsigned char *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(unsigned char 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);

}

 

阅读(1201) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~