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

大东 · January 02, 2016 · Last by 云真机 replied at July 15, 2019 · 4113 hits
本帖已被设为精华帖!

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搭建了内网的设备管理平台。因为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

汪汪 [Topic was deleted] 中提及了此贴 29 Jun 20:34
汪汪 STF 框架之 minitouch 工具 中提及了此贴 06 Dec 15:53

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

大东 #10 · February 07, 2017 作者

#9楼 @huafeihua 邮箱密码。

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

使用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 · May 22, 2018 作者
涂洋 回复

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 · May 22, 2018 作者
涂洋 回复

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

大东 回复

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

大东 回复

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

涂洋 回复

大佬的微信号加密过的

涂洋 回复

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

斯特瑞 回复

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

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up