Windows客户端的无密码Python LDAP3身份验证

Passwordless Python LDAP3 authentication from Windows client

我使用的是出色的ldap3软件包,并且尝试与活动目录服务器建立连接,但无需提供纯文本形式的实际凭据。

支持以下SASL机制。
['GSSAPI', 'GSS-SPNEGO', 'EXTERNAL', 'DIGEST-MD5']

我尝试安装GSSAPI软件包,但在Windows计算机上不起作用。
pip install gssapi上的错误为:
subprocess.CalledProcessError: Command 'krb5-config --libs gssapi' returned non-zero exit status 1.

有人可以为此提供一个简单的例子吗?
我相信GSS-SPNEGO可能是解决方案,但我在互联网上找不到任何可理解的例子。


谢谢您提出这个问题。我今天给了它最后一枪,让它起作用。

请参阅Davide的答案

它要求您具有ldap3软件包并安装winkerberos软件包:

1
pip install winkerberos

然后,您需要用站点链接(PYTHON_HOME\\Lib\\site-packages\\ldap3\\protocol\\sasl\\kerberos.py)中的kerberos.py文件替换为他链接到替换kerberos.py的文件。

您需要在替换的kerberos.py文件中更改以下行:

1
from treadmill import kerberoswrapper as kerberos

更改为

1
import winkerberos as kerberos

然后您可以像这样连接:

1
2
3
4
5
6
7
8
9
from ldap3 import Server, Connection, Tls, SASL, GSSAPI
import ssl

tls = Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_TLSv1)
server = Server('server_fqdn', use_ssl=True, tls=tls)
c = Connection(server, authentication=SASL, sasl_mechanism=GSSAPI)
c.bind()
print(c.extend.standard.who_am_i())
c.unbind()

将server_fqdn替换为AD服务器的标准域名。

您可能希望将版本值更改为AD服务器使用的任何协议。

如果有人没有那么麻烦的方法来完成此操作,请输入提示音!


使用初始答案并避免猴子打补丁,可以根据此处提供的文件和ldap3\\core\\connection.py模块使用以下代码。

ldap3kerberos.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""Replaces the use of python-gssapi with kerberos in ldap3.
"""


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import base64
import socket

import ldap3
from ldap3.core.exceptions import LDAPCommunicationError
from ldap3.protocol.sasl.sasl import send_sasl_negotiation
from ldap3.protocol.sasl.sasl import abort_sasl_negotiation

from ldap3.protocol.sasl.external import sasl_external
from ldap3.protocol.sasl.digestMd5 import sasl_digest_md5
from ldap3.protocol.sasl.plain import sasl_plain
from ldap3.utils.log import log, log_enabled, BASIC
from ldap3 import EXTERNAL, DIGEST_MD5, GSSAPI


import winkerberos as kerberos

NO_SECURITY_LAYER = 1
INTEGRITY_PROTECTION = 2
CONFIDENTIALITY_PROTECTION = 4


class Connection(ldap3.Connection):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def do_sasl_bind(self,
                     controls):
        if log_enabled(BASIC):
            log(BASIC, 'start SASL BIND operation via <%s>', self)
        self.last_error = None
        with self.connection_lock:
            result = None

            if not self.sasl_in_progress:
                self.sasl_in_progress = True
                try:
                    if self.sasl_mechanism == EXTERNAL:
                        result = sasl_external(self, controls)
                    elif self.sasl_mechanism == DIGEST_MD5:
                        result = sasl_digest_md5(self, controls)
                    elif self.sasl_mechanism == GSSAPI:
                        result = sasl_gssapi(self, controls)
                    elif self.sasl_mechanism == 'PLAIN':
                        result = sasl_plain(self, controls)
                finally:
                    self.sasl_in_progress = False

            if log_enabled(BASIC):
                log(BASIC, 'done SASL BIND operation, result <%s>', result)

            return result


def sasl_gssapi(connection, controls):
   """
    Performs a bind using the Kerberos v5 ("GSSAPI") SASL mechanism
    from RFC 4752. Does not support any security layers, only authentication!
    sasl_credentials can be empty or a tuple with one or two elements.
    The first element determines which service principal to request a ticket
    for and can be one of the following:
    - None or False, to use the hostname from the Server object
    - True to perform a reverse DNS lookup to retrieve the canonical hostname
      for the hosts IP address
    - A string containing the hostname
    The optional second element is what authorization ID to request.
    - If omitted or None, the authentication ID is used as the authorization ID
    - If a string, the authorization ID to use. Should start with"dn:" or
     "user:".
   """

    # pylint: disable=too-many-branches
    target_name = None
    authz_id = b''
    if connection.sasl_credentials:
        if (len(connection.sasl_credentials) >= 1 and
                connection.sasl_credentials[0]):
            if connection.sasl_credentials[0] is True:
                hostname = \\
                    socket.gethostbyaddr(connection.socket.getpeername()[0])[0]
                target_name = 'ldap@' + hostname

            else:
                target_name = 'ldap@' + connection.sasl_credentials[0]
        if (len(connection.sasl_credentials) >= 2 and
                connection.sasl_credentials[1]):
            authz_id = connection.sasl_credentials[1].encode("utf-8")
    if target_name is None:
        target_name = 'ldap@' + connection.server.host

    gssflags = (
        kerberos.GSS_C_MUTUAL_FLAG |
        kerberos.GSS_C_SEQUENCE_FLAG |
        kerberos.GSS_C_INTEG_FLAG |
        kerberos.GSS_C_CONF_FLAG
    )

    _, ctx = kerberos.authGSSClientInit(target_name, gssflags=gssflags)

    in_token = b''
    try:
        while True:
            status = kerberos.authGSSClientStep(
                ctx,
                base64.b64encode(in_token).decode('ascii')
            )
            out_token = kerberos.authGSSClientResponse(ctx) or ''
            result = send_sasl_negotiation(
                connection,
                controls,
                base64.b64decode(out_token)
            )
            in_token = result['saslCreds'] or b''
            if status == kerberos.AUTH_GSS_COMPLETE:
                break

        kerberos.authGSSClientUnwrap(
            ctx,
            base64.b64encode(in_token).decode('ascii')
        )
        unwrapped_token = base64.b64decode(
            kerberos.authGSSClientResponse(ctx) or ''
        )

        if len(unwrapped_token) != 4:
            raise LDAPCommunicationError('Incorrect response from server')

        server_security_layers = unwrapped_token[0]
        if not isinstance(server_security_layers, int):
            server_security_layers = ord(server_security_layers)
        if server_security_layers in (0, NO_SECURITY_LAYER):
            if unwrapped_token.message[1:] != '\\x00\\x00\\x00':
                raise LDAPCommunicationError(
                    'Server max buffer size must be 0 if no security layer'
                )
        if not server_security_layers & NO_SECURITY_LAYER:
            raise LDAPCommunicationError(
                'Server requires a security layer, but this is not implemented'
            )

        client_security_layers = bytearray([NO_SECURITY_LAYER, 0, 0, 0])
        kerberos.authGSSClientWrap(
            ctx,
            base64.b64encode(
                bytes(client_security_layers) + authz_id
            ).decode('ascii')
        )
        out_token = kerberos.authGSSClientResponse(ctx) or ''

        return send_sasl_negotiation(
            connection,
            controls,
            base64.b64decode(out_token)
        )
    except (kerberos.GSSError, LDAPCommunicationError):
        abort_sasl_negotiation(connection, controls)
        raise

安装winkerberos:pip install winkerberos

在脚本中,使用以下代码(connect_timeoutmodereceive_timeout参数仅作为示例,可以省略或更改):

1
2
3
4
5
6
7
8
import ldap
import ldap3kerberos

server = ldap3.Server(fqdn, connect_timeout=10, mode=ldap3.IP_V4_ONLY)
conn = ldap3kerberos.Connection(
    server, authentication=ldap3.SASL, sasl_mechanism=ldap3.GSSAPI,
    auto_bind=True, receive_timeout=10
)

If you have several domain controller servers for an AD domain, ensure you are connecting to some specific server, otherwise you will get the exception:

winkerberos.GSSError: SSPI: InitializeSecurityContext: The specified target is unknown or unreachable