Django项目使用NGINX通过LDAP实现用户验证

关昊天
2023-12-01

关于利用Nginx实现Ldap统一认证,官方也给出了相关的文档和相应的示例代码
本文章是结合Django框架和简化认证过程。

1. 安装Nginx中相应的模块

实现Ldap登录主要用到Nginx的http_auth_request_module模块,旧版本的Nginx默认是不安装,可以通过命令行:nginx -V 检查一下是否有安装,没安装的自行百度安装。

2. 实现原理

建议大家先看两遍官方的博客,熟悉一下认证的过程。

首先从Nginx的配置文件入手,搞清楚配置文件到底什么意思。

location / {
    auth_request /auth-proxy;
    error_page 401 =200 /login;
    proxy_pass http://backend/;
}

这个意思是说, 所有访问先转到/auth-proxy这里, 如果/auth-proxy返回401, 则访问被拒绝,Nginx则会把连接转发给/login; 如果返回200, 访问允许,继续被nginx转到http://backend/; 返回其他值, 会被认为是个错误。

下面贴一份完整的Nginx配置文件:

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
	worker_connections 768;
	# multi_accept on;
}

http {

	##
	# Basic Settings
	##

	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	keepalive_timeout 65;
	types_hash_max_size 2048;

	include /etc/nginx/mime.types;
	default_type application/octet-stream;

	ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	access_log /var/log/nginx/access.log;
	error_log /var/log/nginx/error.log;

	gzip on;
	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;

    server {
        # nginx监听的端口
        listen 8081;
        
		# 登录认证通通过后才能访问的后端资源
        upstream backend {
       		 server 127.0.0.1:9000;
    }

        location / {
            auth_request /auth-proxy;
            error_page 401 =200 /login;
            proxy_pass http://backend/;
        }
        
		location /login {
            proxy_pass http://127.0.0.1:8014/ldap-auth/?title=Vectorbuilder&app=oa;
            proxy_set_header X-Target $request_uri; 
        }

        location = /auth-proxy {
            internal;
            # Nginx进行统一认证的api
            proxy_pass http://127.0.0.1:8014/api/v1/nginx-ldap-auth/?app=oa
        }
    }
}

这份配置文件已经关闭了缓存,在开发的过程中,建议大家也关闭缓存,不然影响调试。

下面简单解释一下这份配置文件:

  1. 当用户通过浏览器访问http://127.0.0.1:8081/的时候,Nginx会把请求转发给
    /auth-proxy,也就是proxy_pass http://127.0.0.1:8014/api/v1/nginx-ldap-auth/?app=oa这个用来转发连接的api;

  2. 当用户访问这个api的时候,后端会通过request.user.is_authenticated判断用户有没有登录,如果没有登录,则直接返回401状态码,Nginx再把连接转发到登录界面,即nginx配置文件中的/login对应的http://127.0.0.1:8014/ldap-auth/?title=Vectorbuilder&app=oa,用户填写完账号密码后,点击提交按钮,把信息post给/login,后端验证账号密码并完成登录操作,无论验证成功还是失败,都需要返回302并转发连接;如果成功,转发连接的API能获取到用户信息,返回200,如果获取不到用户信息,则返回401继续跳转到登录界面。

  3. 如果Nginx检测到状态码是200,Nginx就会把请求转发到后端的资源,也就是配置文件里面的http://backend/,即:http://127.0.0.1:9000/,如果检测到的状态码是400,则跳转至/login

3. 连接转发的API

from django.views.generic import View
from django.core.cache import cache
from django.http import HttpResponse
from django.conf import settings
from rest_framework import permissions, status

class NginxLinkForward(View):

    def get(self, request):
        app = request.GET.get('app')
        if not request.user.is_authenticated:
            response = HttpResponse(status=status.HTTP_401_UNAUTHORIZED)
            return response
        return HttpResponse(status=status.HTTP_200_OK)

4. 登录的view:

import logging
import ldap
import uuid
from django.conf import settings
from django.http import HttpResponse
from django.urls import reverse
from django.views.generic import TemplateView, ListView, RedirectView
from django.contrib.auth import authenticate, login
from rest_framework import status
from django.core.cache import cache
from app.models import DistributionList

class LdapAuthView(TemplateView):
    template_name = 'nginx_login.html'

    def auth_failed(self, ctx=None, errmsg=None):
        response = HttpResponse(status=status.HTTP_302_FOUND)
        response['Location'] = '/'
        return response

    def get_context_data(self, **kwargs):
        context = super(LdapAuthView, self).get_context_data(**kwargs)
        context['title'] = self.request.GET.get('title')
        context['app'] = self.request.GET.get('app')
        return context

    def post(self, request, *args, **kwargs):
        username = request.POST.get('username')
        password = request.POST.get('password')
        app = request.POST.get('app')
        ldap_obj = ldap.initialize(settings.AUTH_LDAP_SERVER_URI)
        ldap_obj.protocol_version = ldap.VERSION3

        ldap_obj.bind_s(settings.AUTH_LDAP_BIND_DN,
                        settings.AUTH_LDAP_BIND_PASSWORD,
                        ldap.AUTH_SIMPLE)

        searchfilter = "(&(AccountStatus=active)(mail={})(Notes=*|{}|*))".format(username, app)

        results = ldap_obj.search_s(settings.OU, ldap.SCOPE_SUBTREE,
                                    searchfilter)

        if len(results) >= 1:
            ldapconn = ldap.initialize(settings.AUTH_LDAP_SERVER_URI)
            try:
                # 用上面查询到的dn和用户提交的密码再登录一次,有查询到东西返回大概长这样子的东西:(97, []),找不到就报错
                ldapconn.bind_s(results[0][0], password, ldap.AUTH_SIMPLE)
                # 没报错就表示找到用户信息了
                user = authenticate(username=username, password=password)
                if user:
                    login(request, user)
                response = HttpResponse(status=status.HTTP_302_FOUND)
                response['Location'] = '/'
                return response
            except Exception as e:
                # 用户登录ldap出错
                response = self.auth_failed()
                return response
        else:
            # 管理员登录ldap出错
            response = self.auth_failed()
            return response

5. HTML:

{% load i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ title }} | 登录</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.1.1/css/bootstrap-theme.min.css" rel="stylesheet">
    <link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.0.3/css/font-awesome.min.css" rel="stylesheet">
    <style type="text/css">
        body {
            padding-top: 60px;
            padding-bottom: 40px;
        }
    </style>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.1.1/js/bootstrap.min.js"></script>
</head>

<body>

<div class="container">
    <div class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
        <div class="panel panel-info login">
            <div class="panel-heading">
                <div class="panel-title">{{ title }} | 认证</div>
            </div>

            <div class="panel-body" style="padding-top:30px">
                <form name="login_form" class="form-horizontal" action="/login"
                      method="post">
                    {% csrf_token %}
                    <div class="input-group" style="margin-bottom: 25px">
                        <span class="input-group-addon"><i class="fa fa-user"></i></span>
                        <input type="text" required="" placeholder="Email" class="form-control"
                               id="username" name="username">
                    </div>

                    <div class="input-group" style="margin-bottom: 25px">
                        <span class="input-group-addon"><i class="fa fa-lock"></i></span>
                        <input type="password" required="" placeholder="Password" class="form-control"
                               id="password" name="password">
                    </div>

                    <div class="form-group" style="margin-top:10px">

                        <div class="col-sm-12 controls">
                            <button type="submit" class="btn btn-success" id="btn-login">
                                登录
                            </button>

                        </div>
                    </div>
                    <input type="hidden" name="app" value="{{ app }}">
                </form>
            </div>
        </div>
    </div>

    <script type="text/javascript">
        $(function () {
            $(".alert-message").alert();
        });
    </script>

</div> <!-- /container -->
<footer id="footer">
    <div class="container">
        <p>

        </p>
    </div>
</footer>
</body>
</html>

settings.py需要添加几个参数:

参数的内容自行根据贵公司的Ldap服务器进行配置。

AUTH_LDAP_SERVER_URI = ''
AUTH_LDAP_BIND_DN = ''
AUTH_LDAP_BIND_PASSWORD = ''
AUTH_LDAP_BASE_DN = ''

KEY_APP_LOGGED = 'app:logged:{}:{}'

注意:别忘了在项目的url.py文件中添加上面写好的类的路由。

后端的资源的ip和端口,大家根据自己的实际情况多启动一个项目或者自己再简单写个接口就能用于调试。

 类似资料: