我正在编写一个相当简单的内部应用程序(目前在Bottle中进行原型设计),它会将事件通知和更改管理事件发送到内部邮件列表,同时强制这些通知符合几个标准模板(并通过Python Jira API确保所需的问题参考存在且处于适当的状态。)
当然,我要求用户在发送将归因于它们的消息之前对我的应用程序进行身份验证。我们使用LDAP,所有密码哈希都以{SSHA}格式存储。
我发现至少有三种不同的方式来执行身份验证:
这里的代码似乎正确地实现了第一个:
#!python
import hashlib
import ConfigParser, os
from base64 import encodestring as encode
from base64 import decodestring as decode
import ldap
config = ConfigParser.ConfigParser()
config.read(os.path.expanduser('~/.creds.ini'))
uid = config.get('LDAP', 'uid')
pwd = config.get('LDAP', 'pwd')
svr = config.get('LDAP', 'svr')
bdn = config.get('LDAP', 'bdn')
ld = ldap.initialize(svr)
ld.protocol_version = ldap.VERSION3
ld.simple_bind_s(uid, pwd)
def chk(prop, pw):
pw=decode(pw[6:]) # Base64 decode after stripping off {SSHA}
digest = pw[:20] # Split digest/hash of PW from salt
salt = pw[20:] # Extract salt
chk = hashlib.sha1(prop) # Hash the string presented
chk.update(salt) # Salt to taste:
return chk.digest() == digest
if __name__ == '__main__':
import sys
from getpass import getpass
max_attempts = 3
if len(sys.argv) < 2:
print 'Must supply username against which to authenticate'
sys.exit(127)
name = sys.argv[1]
user_dn = ld.search_s(bdn, ldap.SCOPE_SUBTREE, '(uid=%s)' % name)
if len(user_dn) < 1:
print 'No DN found for %s' % name
sys.exit(126)
pw = user_dn[0][1].get('userPassword', [''])[0]
exit_value = 1
attempts = 0
while attempts < max_attempts:
prop = getpass('Password: ')
if chk(prop, pw):
print 'Authentication successful'
exit_value = 0
break
else:
print 'Authentication failed'
attempts += 1
else:
print 'Maximum retries exceeded'
sys.exit(exit_value)
这似乎有效(假设我们在.creds.ini
中有适当的值)。
以下是实现第二个选项的一些代码:
#!python
# ...
### Same ConfigParser and LDAP initialization as before
# ...
def chk(prop, dn):
chk = ldap.initialize(svr)
chk.protocol_version = ldap.VERSION3
try:
chk.simple_bind_s(dn, prop)
except ldap.INVALID_CREDENTIALS:
return False
chk.unbind()
return True
if __name__ == '__main__':
import sys
from getpass import getpass
max_attempts = 3
if len(sys.argv) < 2:
print 'Must supply username against which to authenticate'
sys.exit(127)
name = sys.argv[1]
user_dn = ld.search_s(bdn, ldap.SCOPE_SUBTREE, '(uid=%s)' % name)
if len(user_dn) < 1:
print 'No distinguished name (DN) found for %s' % name
sys.exit(126)
dn = user_dn[0][0]
exit_value = 1
attempts = 0
while attempts < max_attempts:
prop = getpass('Password: ')
if chk(prop, dn):
print 'Authentication successful'
exit_value = 0
break
else:
print 'Authentication failed'
attempts += 1
else:
print 'Maximum retries exceeded'
sys.exit(exit_value)
此处未显示,但我还测试了我可以继续使用ld
LDAP连接,而不依赖于临时chk
LDAP对象。因此,我长期运行的Web服务可以继续重用一个连接。
无论我使用哪两个PAM模块,最后几个选项几乎相同。以下是使用 python-pam :
的示例#!/usr/bin/env python
import pam
pam_conn = pam.pam()
def chk(prop, name):
return pam_conn.authenticate(name, prop)
if __name__ == '__main__':
import sys
from getpass import getpass
max_attempts = 3
if len(sys.argv) < 2:
print 'Must supply username against which to authenticate'
sys.exit(127)
name = sys.argv[1]
exit_value = 1
attempts = 0
while attempts < max_attempts:
prop = getpass('Password: ')
if chk(prop, name):
print 'Authentication successful'
exit_value = 0
break
else:
print 'Authentication failed'
attempts += 1
else:
print 'Maximum retries exceeded'
sys.exit(exit_value)
我的问题是:我应该使用哪些。它们中的任何一个都不如其他人安全吗?关于“最佳实践”是否有任何共识?
答案 0 :(得分:2)
绝对使用bind作为用户提供的用户名和密码。
“使用具有有限搜索权限的服务帐户绑定到LDAP,使用该帐户查找用户的dn,然后尝试使用该dn和用户建议的密码绑定到LDAP”
抱歉,我不知道“Authenticate()函数”的表现如何。
不绑定为userDN,可以绕过适用于密码策略或帐户限制和入侵者检测的内置服务器功能。
-Jim