编辑
2023-10-22
测试平台
00
请注意,本文编写于 189 天前,最后修改于 189 天前,其中某些信息可能已经过时。

目录

1、背景
2、Django接入LDAP认证
2.1 安装django-ldap-auth
2.2 Settings配置LDAP
2.3 登录接口中使用LDAP认证
2.4 LDAP认证测试
2.4.3 LDAP和本地都认证失败
3、Docker安装python-ldap失败
4、总结

1、背景

在公司中,一般都会有LDAP认证,我们开发的效能平台如果单独设置一套用户认证体系,那样会很不方便。
因此,效能平台想要获得更好的用户体验,就需要在接入公司现在有的LDAP用户认证。

2、Django接入LDAP认证

拿我的开源项目举例子,后端使用的框架是Django,已经有现成的LDAP库,直接集成即可,不需要造轮子。
下面是具体的步骤

2.1 安装django-ldap-auth

pip install django-auth-ldap==2.3.0

一定要指定版本,否则可能会出现不兼容的情况
我使用的Django2.2,所以最多能支持的版本是2.3.0
具体的版本兼容查看这里 https://django-auth-ldap.readthedocs.io/en/latest/changes.html#id4

2.2 Settings配置LDAP

在Django的配置文件最底部加上这个即可

py
# settings.py # LDAP配置 import ldap from django_auth_ldap.config import LDAPSearch AUTHENTICATION_BACKENDS = ( 'django_auth_ldap.backend.LDAPBackend', ) USE_LDAP = False # 如果需要开启LDAP认证,就设置位True AUTH_LDAP_SERVER_URI = "ldap://localhost:389" # LDAP服务器地址,默认端口389 AUTH_LDAP_BIND_DN = "cn=admin,dc=myorg,dc=com" # LDAP管理员账号 AUTH_LDAP_BIND_PASSWORD = "admin" # LDAP管理员密码 AUTH_LDAP_USER_SEARCH = LDAPSearch( "ou=Tester,dc=myorg,dc=com", ldap.SCOPE_SUBTREE, "(uid=%(user)s)", ) # LDAP搜索账号,ou可以理解为组织单位或者部门,不填写也是ok,dc可以理解为域名 AUTH_LDAP_USER_ATTR_MAP = { "username": "uid", "first_name": "givenName", "last_name": "sn", "email": "mail", } # 不配置也可以

2.3 登录接口中使用LDAP认证

整体逻辑是: 判断settings的USE_LDAP是否为真,为真就走LDAP认证 LDAP认证成功,就会设置local_user,然后返回jwt token LDAP认证不成功,走本地认证,认证成功就返回jwt token 最终两个都不成功,就会返回登录失败

py
# views.py import logging from typing import Optional from django.conf import settings from django.contrib.auth import authenticate, get_user_model from django.contrib.auth.hashers import check_password, make_password from drf_yasg.utils import swagger_auto_schema from rest_framework.response import Response from rest_framework.views import APIView def ldap_auth(username: str, password: str) -> Optional[User]: ldap_user = authenticate(username=username, password=password) if ldap_user and ldap_user.backend == "django_auth_ldap.backend.LDAPBackend": logger.info(f"LDAP authentication successful for {username}") local_user: User = User.objects.filter(username=username).first() if local_user: local_user.password = make_password(password) local_user.save(update_fields=["password"]) logger.info(f"ldap认证通过,更新本地用户密码: {username}") return local_user logger.info(f"LDAP authentication failed for {username}") return None def local_auth(username: str, password: str) -> Optional[User]: local_user = User.objects.filter(username=username).first() if not local_user: logger.warning(f"Local user does not exist: {username}") return None if local_user.is_active == 0: logger.warning(f"Local user is blocked: {username}") return None if not check_password(password, local_user.password): logger.warning(f"Local authentication failed: {username}") return None return local_user def generate_token_and_respond(local_user: User): jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER payload = jwt_payload_handler(local_user) token = jwt_encode_handler(payload) response.LOGIN_SUCCESS["token"] = token response.LOGIN_SUCCESS["user"] = local_user.username response.LOGIN_SUCCESS["is_superuser"] = local_user.is_superuser response.LOGIN_SUCCESS["show_hosts"] = local_user.show_hosts return Response(response.LOGIN_SUCCESS) class LoginView(APIView): """ 登陆视图,用户名与密码匹配返回token """ authentication_classes = () permission_classes = () @swagger_auto_schema(request_body=UserLoginSerializer) def post(self, request): serializer = UserLoginSerializer(data=request.data) if serializer.is_valid(): username: str = serializer.validated_data["username"] password: str = serializer.validated_data["password"] masked_password = f"{password[0]}{'*' * (len(password) - 2)}{password[-1]}" logger.info(f"Received login request for {username=}, password={masked_password}") local_user = None if settings.USE_LDAP: logger.info(f"Attempting LDAP authentication for {username=}") local_user = ldap_auth(username, password) if not local_user: logger.info( f"LDAP authentication failed or not enabled, falling back to local authentication for {username=}") local_user = local_auth(username, password) if local_user: logger.info(f"Authentication successful for {username=}") return generate_token_and_respond(local_user) else: logger.info(f"Authentication failed for {username=}") return Response(response.LOGIN_FAILED) else: return Response(serializer.errors)

2.4 LDAP认证测试

LDAP服务端配置如下:

  • 有一个公司myorg.com
  • 有一个部门TestORg
  • TestOrg部门有一个小组AutoTest
  • AutoTest下面有一个用户ayo,密码是123456

image.png

2.4.1 LDAP认证成功

shell
2023-10-22 20:53:28,812 [e66bf977a7e146b68996c5aa164c55d6] fastuser.views INFO [pid:57614] [views.py->post:116] Received login request for username='ayo', password=1****6 2023-10-22 20:53:28,818 [e66bf977a7e146b68996c5aa164c55d6] fastuser.views INFO [pid:57614] [views.py->post:120] Attempting LDAP authentication for username='ayo' 2023-10-22 20:53:28,831 [e66bf977a7e146b68996c5aa164c55d6] django_auth_ldap WARNING [pid:57614] [backend.py->_populate_user_from_attributes:653] cn=ayo,cn=autotest,ou=testorg,dc=myorg,dc=com does not have a value for the attribute givenName 2023-10-22 20:53:28,833 [e66bf977a7e146b68996c5aa164c55d6] django_auth_ldap WARNING [pid:57614] [backend.py->_populate_user_from_attributes:653] cn=ayo,cn=autotest,ou=testorg,dc=myorg,dc=com does not have a value for the attribute mail 2023-10-22 20:53:28,836 [e66bf977a7e146b68996c5aa164c55d6] fastuser.views INFO [pid:57614] [views.py->ldap_auth:63] LDAP authentication successful for ayo 2023-10-22 20:53:28,930 [e66bf977a7e146b68996c5aa164c55d6] fastuser.views INFO [pid:57614] [views.py->ldap_auth:68] ldap认证通过,更新本地用户密码: ayo 2023-10-22 20:53:28,932 [e66bf977a7e146b68996c5aa164c55d6] fastuser.views INFO [pid:57614] [views.py->post:129] Authentication successful for username='ayo' 2023-10-22 20:53:28,942 [e66bf977a7e146b68996c5aa164c55d6] django.server INFO [pid:57614] [basehttp.py->log_message:154] "POST /api/user/login/ HTTP/1.1" 200 264

image.png

2.4.2 LDAP认证失败,走本地认证成功

shell
2023-10-22 20:50:17,208 [120eabbc698741198c2c068179e7294b] fastuser.views INFO [pid:54892] [views.py->post:116] Received login request for username='huacai', password=1****6 2023-10-22 20:50:17,216 [120eabbc698741198c2c068179e7294b] fastuser.views INFO [pid:54892] [views.py->post:120] Attempting LDAP authentication for username='huacai' 2023-10-22 20:50:17,224 [120eabbc698741198c2c068179e7294b] django_auth_ldap ERROR [pid:54892] [config.py->execute:152] search_s('ou=Tester,dc=myorg,dc=com', 2, '(uid=huacai)') raised NO_SUCH_OBJECT({'msgtype': 101, 'msgid': 2, 'result': 32, 'desc': 'No such object', 'ctrls': [], 'matched': 'dc=myorg,dc=com'}) 2023-10-22 20:50:17,228 [120eabbc698741198c2c068179e7294b] fastuser.views INFO [pid:54892] [views.py->ldap_auth:70] LDAP authentication failed for huacai 2023-10-22 20:50:17,230 [120eabbc698741198c2c068179e7294b] fastuser.views INFO [pid:54892] [views.py->post:124] LDAP authentication failed or not enabled, falling back to local authentication for username='huacai' 2023-10-22 20:50:17,322 [120eabbc698741198c2c068179e7294b] fastuser.views INFO [pid:54892] [views.py->post:129] Authentication successful for username='huacai' 2023-10-22 20:50:17,339 [120eabbc698741198c2c068179e7294b] django.server INFO [pid:54892] [basehttp.py->log_message:154] "POST /api/user/login/ HTTP/1.1" 200 284

image.png

2.4.3 LDAP和本地都认证失败

shell
2023-10-22 21:02:09,705 [f65679ab9b75405daeeb06e0fd6cf7bb] fastuser.views INFO [pid:57614] [views.py->post:116] Received login request for username='huacai', password=1*******9 2023-10-22 21:02:09,711 [f65679ab9b75405daeeb06e0fd6cf7bb] fastuser.views INFO [pid:57614] [views.py->post:120] Attempting LDAP authentication for username='huacai' 2023-10-22 21:02:09,717 [f65679ab9b75405daeeb06e0fd6cf7bb] fastuser.views INFO [pid:57614] [views.py->ldap_auth:70] LDAP authentication failed for huacai 2023-10-22 21:02:09,719 [f65679ab9b75405daeeb06e0fd6cf7bb] fastuser.views INFO [pid:57614] [views.py->post:124] LDAP authentication failed or not enabled, falling back to local authentication for username='huacai' 2023-10-22 21:02:09,812 [f65679ab9b75405daeeb06e0fd6cf7bb] fastuser.views WARNING [pid:57614] [views.py->local_auth:83] Local authentication failed: huacai 2023-10-22 21:02:09,814 [f65679ab9b75405daeeb06e0fd6cf7bb] fastuser.views INFO [pid:57614] [views.py->post:132] Authentication failed for username='huacai' 2023-10-22 21:02:09,820 [f65679ab9b75405daeeb06e0fd6cf7bb] django.server INFO [pid:57614] [basehttp.py->log_message:154] "POST /api/user/login/ HTTP/1.1" 200 64

image.png

3、Docker安装python-ldap失败

在本地验证通过之后,推送到GitHub,但结果action构建失败了。
一看报错,大概是缺少了某个lib(libldap2-dev libsasl2-dev)
把log粘贴过来给gpt,一下子就够搞定,太爽啦!!!

image.png

4、总结

  • Django接入LDAP很简单,只需加上对应配置后,再调用LDAP认证即可
  • 在docker中,安装python-ldap需要额外的依赖

文章全部代码在以下两个Pull Request:

本文作者:花菜

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!