STF [STF 系列] 无痛接入基于 LDAP 的单点登录

大东 · 2016年01月02日 · 最后由 云真机 回复于 2019年07月15日 · 4081 次阅读
本帖已被设为精华帖!

2016 年第一帖。

闲来无事唠嗑一下 STF 相关的技术点。

关于 STF

STF 是啥,怎么安装,可以先看看这两个帖子:

别问我为什么都是 Mac 和 Linux 的,STF 的 Github 上明白得写着:
In principle we will not provide any Windows installation support, but please do send a documentation pull request if you figure out what to do.
官方不提供任何关于 windows 的安装支持,但是你装成功了欢迎你来分享你在 windows 下安装的经验。

关于单点登录和 LDAP

因为 STF 的平台默认是 Mock 登录的,任何人只要获取地址了就可以使用平台上的设备,所以鼓捣着给 STF 接一个单点登录模块,用来限制使用人群,控制使用范围(毕竟设备有限)。
把想法跟小 A 一说,OK,开始鼓捣。
刚准备开干,一扫 STF 的项目目录,/lib/units/auth目录下,除了默认的mock.js模块以外,还有ldap.js这么个文件,打开文件一扫,原来STF已经是有实现好的LDAP模块了,都有现成的模块了,我们还造个毛轮子。
二话不说,找 IT 部门把公司的 LDAP 目录地址、DN 和 DC 拿到,直接接入。

PS:其实 STF 的开发文档里已经说明了 STF 包含的模块了,stf.auth-ldap也在其列,只是我自己偷懒,没有认真阅读,建议大家先读读 STF 里面有说明东西再开工。

另外,应该有许多同学不知道 LDAP 是什么,建议先去百度谷歌补习一下。简单得来说,LDAP 是一种协议,我们通过这种协议可以访问公司的 director server(DS),实现单点登录的效果。(即实现使用域内邮箱密码登录)

准备工作

LDAP 服务器信息与连接 LDAP 服务器的认证账号:

  • LDAP SERVER URI(e.g: ldap://server-xx.testerhome.com:389)
  • LDAP BIND DN (连接 director server 需要用到的认证用户名,一般还需要密码)
  • LDAP BIND DN PWD(认证用户的密码)
  • LDAP BASE DN (服务器目录节点,用于查询用户,格式一般是DC=testerhome, DC=com

经不断尝试,STF 的 ldap 模式也只需要以上几个重要参数即可。以上信息直接跟你们的行政 IT 部门获取或申请即可。

调试 LDAP 服务器连接

从行政 IT 部门获取到了 LDAP 相关的信息以后,我们利用ldapsearch命令来调试是否能够连通你们公司的 DS。
调试方法:

#调试命令
ldapsearch -x -H $LDAP-SERVER-URI -D $LDAP-BIND-DN -b $LDAP-BSAE-DN -w $LDAP-BIND-DN-PWD
#将大写的参数换成你公司对应的参数即可。例子:
ldapsearch -x H ldap://server.testerhome.com -D 'dadong@testerhome.com' -b 'DC=testerhome,DC=com' -w 'dadongdemima'

如果你公司的 DS 目录内有非常多的员工信息,那你使用该命令会返回 N 页的信息,那么如果是这样的话,说明该 SERVER-URI 和 BIND-DN 是可用的。
那么我们就可用开始正式接入我们的 STF 中去了。

STF 的 LDAP 模块

玩过 STF 的都知道,我们启动 STF 时,使用的命令是stf local,但是这样启动的模式是 mock 登录模式,我们要使用 stf 的 ldap 模式的话,需要在启动的时候,切换 auth 的 type 为 ldap,并设置好 ldap 相关的 option。

我们可以使用stf auth-ldap -h命令来看看 ldap 模块里面有哪些必备的 option:

➜  ~  stf auth-ldap -h

  Usage: auth-ldap [options]

  start LDAP auth client

  Options:

    -h, --help                             output usage information
    -p, --port <port>                      port (or $PORT)
    -s, --secret <secret>                  secret (or $SECRET)
    -i, --ssid <ssid>                      session SSID (or $SSID)
    -a, --app-url <url>                    URL to app
    -u, --ldap-url <url>                   LDAP server URL (or $LDAP_URL)
    -t, --ldap-timeout <timeout>           LDAP timeout (or $LDAP_TIMEOUT)
    --ldap-bind-dn <dn>                    LDAP bind DN (or $LDAP_BIND_DN)
    --ldap-bind-credentials <credentials>  LDAP bind credentials (or $LDAP_BIND_CREDENTIALS)
    --ldap-search-dn <dn>                  LDAP search DN (or $LDAP_SEARCH_DN)
    --ldap-search-scope <scope>            LDAP search scope (or $LDAP_SEARCH_SCOPE)
    --ldap-search-class <class>            LDAP search objectClass (or $LDAP_SEARCH_CLASS)
    --ldap-search-field <name>             LDAP search field (or $LDAP_SEARCH_FIELD)

通过stf auth-ldap -h命令我们大致可以看到 stf 的 ldap 模式启动参数多得让我们眼花缭乱,但实际上我们只会用到其中几个,也就是前面需要大家准备的几个。

由于官方 Github 上没有任何文档有对各个模块的详细单个说明,只能硬啃源码了。

我们看看stf提供的ldaputil.js方法里是怎么设计的。
ldaputil 方法里主要有三个步骤:

  • tryConnect() - 用于连接和认证 ldap 服务器
    • options.bind.dn
    • options.bind.credentials
  • tryFind() - 用于登录的条件查询
    • options.search.objectClass || options.search.field
    • options.search.dn
  • tryBind() - 用于授权验证
    • options.search.dn

所以我们知道了,stf里需要启用ldap模式登录的话,必须要有bind dncredentialsobjectClassfieldsearch dn这么几样东西。

但是这几样东西好像跟我们一开始去取的不太一样啊?其实一样的,这里我们来列一下对照的关系:

-u, -ldap-url : LDAP SERVER URI
--ldap-bind-dn : LDAP BIND DN
--ldap-bind-credentials: LDAP BIND DN_PWD
--ldap-search-dn : LDAP BASE DN

但是,好像还有个参数我们一开始没准备到:objectClass 或 field;
这个是用于我们设置登录 LDAP 服务的时候的条件筛选,比如说,DS 目录中,包含了员工的用户名、邮箱,那么你是可以通过 field 这个参数来配置是使用用户名还是使用邮箱来进行验证的。
也就是说,你可以通过这个参数,来限制使用 STF 的用户只能使用邮箱来登录。

邮箱一般是唯一的,名字可能是重复的。

LDAP 模式启动 STF

说了那么多,最后我们需要启用 ldap 模式的命令是:

~ rethinkdb &
~ stf local --public-ip='本机IP' --auth-type ldap --auth-options '["--ldap-url","$LDAP SERVER URI", "--ldap-bind-dn","$LDAP BIND DN","--ldap-search-dn","$LDAP BASE DN", "--ldap-bind-credentials","$LDAP BIND DN PWD", "--ldap-search-class","user", "--ldap-search-field", "mail"]'

#--auth-type ldap,指定auth的模式为ldap
#--auth-option, 配置ldap启动的参数
#--ldap-search-class='user',筛选目录下的所有用户
#--ldap-search-field='mail',使用mail的方式来获取需要授权的用户

OK,再次打开浏览器输入localhost:7100,会发现被重定向到localhost:7100/auth/ldap页面中去了,愉快得使用域内邮箱密码登录吧。

PS:对了,LDAP BIND DN可以是域内的任何一个用户,只要有权访问 DS 目录的用户就可以了,也就是说只要是你们公司的域内可认证的用户密码,就可以作为公共认证账号,不过并不建议这么使用。

我个人认为这样的平台(前提就是只给内部人使用)是非常适合接入 LDAP 的,无需注册,且能够帮助你后续的二次开发中很好得区分部门关系,用户关系。
比如说后面要开发一个区分部门使用设备的功能,有些设备是给开发用的,有些设备是自动化专用的,测试专用的,那么通过 LDAP 的目录关系,就可以很好得做到区分。
大家玩起来吧,欢迎拍砖。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 20 条回复 时间 点赞

有时间一定弄弄看~

#1 楼 @testly 嗯。这个巨型轮子里面有无数小轮子,可以读一读玩一玩。

#1 楼 @testly 阿里要接入成本太高了。。

stf 的进阶知识,好好实践下

最近正烦恼这个登录问题呢,可以找 IT 部门搞一下试试。感谢楼主分享

你好,请问 openstf 修改了模板,需要重新生成。应该怎么生成呢,我看官网给的解释是要运行 gulp clean,但是我运行了提示 No gulpfile found

汪汪 [該主題已被刪除] 中提及了此贴 06月29日 20:34
汪汪 STF 框架之 minitouch 工具 中提及了此贴 12月06日 15:53

/auth/ldap/,要求输入用户名和密码,用户名是 mail 的话,密码是什么呢?

大东 #10 · 2017年02月07日 Author

#9 楼 @huafeihua 邮箱密码。

亲测,证明楼主总结到位,完全支持 ldap 和 AD,感谢楼主大公无私分享!

匿名 #12 · 2018年01月19日

使用 ldap 本地 debug 的时候,出现报错,楼主有没有遇到过?
ERR/auth-ldap 27140 [::ffff:10.3.10.59] Unexpected error InvalidCredentialsError: 80090308: LdapErr: DSID-0C090421, comment: AcceptSecurityContext error, data 52e, v23f0
at messageCallback (/home/liuxu/stf/stf/node_modules/ldapjs/lib/client/client.js:1419:45)
at Parser.onMessage (/home/liuxu/stf/stf/node_modules/ldapjs/lib/client/client.js:1089:14)
at emitOne (events.js:96:13)
at Parser.emit (events.js:188:7)
at Parser.write (/home/liuxu/stf/stf/node_modules/ldapjs/lib/messages/parser.js:111:8)
at Socket.onData (/home/liuxu/stf/stf/node_modules/ldapjs/lib/client/client.js:1076:22)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
at Socket.Readable.push (_stream_readable.js:134:10)
at TCP.onread (net.js:548:20)

楼主,我这边使用 ldap 启动 stf,命令如下:
stf local --public-ip='172.19.37.57' --auth-type ldap --auth-options '["--ldap-url","ldap://172.19.37.57", "--ldap-bind-dn","cn=admin,dc=cpu-os,dc=com","--ldap-search-dn","dc=cpu-os, dc=com", "--ldap-bind-credentials","123456", "--ldap-search-class", "user", "--ldap-search-field", "mail"]'

结果报错如下:
2018-05-21T10:30:55.267Z WRN/auth-ldap 20434 [::ffff:127.0.0.1] Authentication failure for "admin@cpu-os.com"
Unhandled rejection InvalidCredentialsError
at EventEmitter.endListener (/usr/local/tuyang/stf/stf-master/lib/util/ldaputil.js:78:25)
at emitOne (events.js:116:13)
at EventEmitter.emit (events.js:211:7)
at sendResult (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1389:22)
at messageCallback (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1421:16)
at Parser.onMessage (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1089:14)
at emitOne (events.js:116:13)
at Parser.emit (events.js:211:7)
at Parser.write (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/messages/parser.js:111:8)
at Socket.onData (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1076:22)
at emitOne (events.js:116:13)
at Socket.emit (events.js:211:7)
at addChunk (_stream_readable.js:263:12)
at readableAddChunk (_stream_readable.js:250:11)
at Socket.Readable.push (_stream_readable.js:208:10)
at TCP.onread (net.js:597:20)

这个怎么解决呢?

guoxd 回复

大佬,可以说说你是怎么实现 ldap 登录的嘛?我的报错了,详细在本楼的回复里面😂 😂

大东 #15 · 2018年05月22日 Author
涂洋 回复

Authentication failure for "admin@cpu-os.com"
看起来是这个问题呀。看看具体的登录的错误 code 是啥;

大东 回复

控制台的情况是这样的:


xshell 报的错如下:
2018-05-22T05:59:51.988Z WRN/auth-ldap 26040 [::ffff:127.0.0.1] Authentication failure for "admin@cpu-os.com"
Unhandled rejection InvalidCredentialsError
at EventEmitter.endListener (/usr/local/tuyang/stf/stf-master/lib/util/ldaputil.js:78:25)
at emitOne (events.js:116:13)
at EventEmitter.emit (events.js:211:7)
at sendResult (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1389:22)
at messageCallback (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1421:16)
at Parser.onMessage (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1089:14)
at emitOne (events.js:116:13)
at Parser.emit (events.js:211:7)
at Parser.write (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/messages/parser.js:111:8)
at Socket.onData (/usr/local/tuyang/stf/stf-master/node_modules/_ldapjs@1.0.2@ldapjs/lib/client/client.js:1076:22)
at emitOne (events.js:116:13)
at Socket.emit (events.js:211:7)
at addChunk (_stream_readable.js:263:12)
at readableAddChunk (_stream_readable.js:250:11)
at Socket.Readable.push (_stream_readable.js:208:10)
at TCP.onread (net.js:597:20)

我启动的命令如下:
stf local --public-ip='172.19.37.57' --auth-type ldap --auth-options '["--ldap-url","ldap://172.19.37.57", "--ldap-bind-dn","cn=admin, dc=cpu-os, dc=com","--ldap-search-dn","dc=cpu-os, dc=com", "--ldap-bind-credentials","123456", "--ldap-search-class", "top", "--ldap-search-field", "mail"]'

我的 LDAP 是自己搭建的,运行 ldapsearch -x 结果如下:
root@seatu-VirtualBox:/usr/local/tuyang/stf/stf-master# ldapsearch -x

extended LDIF

#

LDAPv3

base (default) with scope subtree

filter: (objectclass=*)

requesting: ALL

#

cpu-os.com

dn: dc=cpu-os,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: cpu-os
dc: cpu-os

admin, cpu-os.com

dn: cn=admin,dc=cpu-os,dc=com
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: admin
description: LDAP administrator

jinlong, cpu-os.com

dn: cn=jinlong,dc=cpu-os,dc=com
cn:: IGppbmxvbmc=
sn: jinlong
objectClass: inetOrgPerson
objectClass: top

taowen, cpu-os.com

dn: cn=taowen,dc=cpu-os,dc=com
cn:: IHRhb3dlbg==
sn: taowen
objectClass: inetOrgPerson
objectClass: top

search: 2
result: 0 Success

numResponses: 5

numEntries: 4

另外,我用 java 和 node 去连接我的 ldap 服务器都是可以的,估计是 stf 启动命令格式有问题,大佬求助啊!!!大佬方便留个 QQ 嘛?不方便的话,可以加一下嘛?我的 qq 是:1052113431,真的万分感谢!

大东 #17 · 2018年05月22日 Author
涂洋 回复

说到底还是鉴权出错了呀。
你 ldap 服务器收到的日志是啥呢?你用其他可用的账号密码登录试试看。
加我微信吧 QW5pa2lrdW4=

大东 回复

其它可用的账号都不行,ldap 日志不知道去哪看😂 ,大佬你的微信是 QW5pa2lrdW4=?为啥说不存在这个用户啊?😂

大东 回复

大佬,加不了你的微信诶,可以加一下我吗?我的微信是 tuyangusst

涂洋 回复

大佬的微信号加密过的

涂洋 回复

我这边这样启动没有报错,但是后面登录有跟你一样的报错,原因是账号或密码不正确导致鉴权失败。账号是邮箱,密码是对应密码 (即公司内网账号对应的密码)。希望对你有帮助。

斯特瑞 回复

同问,请问兄弟你的问题解决了吗

需要 登录 後方可回應,如果你還沒有帳號按這裡 注册