在公司中,一般都会有LDAP认证,我们开发的效能平台如果单独设置一套用户认证体系,那样会很不方便。
因此,效能平台想要获得更好的用户体验,就需要在接入公司现在有的LDAP用户认证。
拿我的开源项目举例子,后端使用的框架是Django,已经有现成的LDAP库,直接集成即可,不需要造轮子。
下面是具体的步骤
pip install django-auth-ldap==2.3.0
一定要指定版本,否则可能会出现不兼容的情况
我使用的Django2.2,所以最多能支持的版本是2.3.0
具体的版本兼容查看这里
https://django-auth-ldap.readthedocs.io/en/latest/changes.html#id4
在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",
} # 不配置也可以
整体逻辑是: 判断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)
LDAP服务端配置如下:
shell2023-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
shell2023-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
shell2023-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
在本地验证通过之后,推送到GitHub,但结果action构建失败了。
一看报错,大概是缺少了某个lib(libldap2-dev libsasl2-dev)
把log粘贴过来给gpt,一下子就够搞定,太爽啦!!!
文章全部代码在以下两个Pull Request:
本文作者:花菜
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!