Nginx + Flask搭建LDAP认证--nginx-auth-request-module 的使用
需求由来
kibana,Elasticsearch 等业务需要对外服务的时候,头痛了把。传统方法 使用basic认真。用户管理起来麻烦。
公司内部有多个业务开发部门,为了保证这些员工能正常访问 内部一些无法提供认证服务的应用兼顾到系统安全。通过nginx-auth-request-module来实现认证转移。
参考
https://www.cnblogs.com/vipzhou/p/8420808.html
https://github.com/perusio/nginx-auth-request-module
https://www.jianshu.com/p/9f2da3cf5579 (大思路)
Nginx 的 auth_request 模块
auth_request 大抵就是在你访问 Nginx 中受 auth_reuqest 保护的路径时,去请求一个特定的服务。根据这个服务返回的状态码,auth_request 模块再进行下一步的动作,允许访问或者重定向跳走什么的。因此我们可以在上面去定制我们所有个性化的需求。
假定我们的环境是 centos ,yum 安装 nginx 就略了。由于通过 yum 等安装的 nginx 默认没有编译 auth_request 模块。我们需要重新编译一下。
先运行 nginx -V 来获取当前 nginx 的编译参数
nginx安装
本例子,以centos 7.6
yum install epel-release -y
yum install nginx
nginx -V 确认是否带有auth_request模块
[root@vm188-ai ~]# nginx -V
nginx version: nginx/1.16.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)
built with OpenSSL 1.0.2k-fips 26 Jan 2017
TLS SNI support enabled
configure arguments: --prefix=/usr/share/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-stream_ssl_preread_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-http_auth_request_module --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E'
Nginx 认证
主要利用nginx-auth-request-module 进行鉴权
- 1、auth_request对应的路由返回401 or 403时,会拦截请求直接nginx返回前台401 or 403信息;
- 2、auth_request对应的路由返回2xx状态码时,不会拦截请求,而是构建一个subrequest请求再去请求真实受保护资源的接口;
graph LR
a[client]-->B[NGINX plus]
B-->c[backend]
c-->B
B-->D(ldap_auth daemon)
D-->E(ldap server)
E-->D
D-->B
graph TB
a[用户请求]-->B[nginx转发请求ldap]
B-->c[LDAP 401]
c-->d[后端发送登录表单]
d-->e[用户提交认证信息]
e-->f[nginx转发cookie给ldap_auth]
f-->g[ldap_auth请求ldap认证]
g-->h{认证成功}
h-.no.->c
h-.yes.->C[LDAP 200]
C-->END
1. 客户端发送 HTTP 请求,以获取 Nginx 上反向代理的受保护资源。
2. Nginx 的 auth_request 模块 将请求转发给 ldap-auth 这个服务(对应 nginx-ldap-auth-daemon.py),首次肯定会给个 401 .
3. Nginx 将请求转发给 http://backend/login,后者对应于这里的后端服务。它将原始请求的 uri 写入X-Target ,以便于后面跳转。
4. 后端服务向客户端发送登录表单(表单在 demo 代码中定义)。根据 error_page 的配置,Nginx 将登录表单的 http 状态码返回 200。
5. 用户填写表单上的用户名和密码字段并单击登录按钮,从向 / login 发起 POST 请求,Nginx 将其转发到后端的服务上。
6. 后端服务把用户名密码以 base64 方式写入 cookie。
7. 客户端重新发送其原始请求(来自步骤1),现在有 cookie 了 。Nginx 将请求转发给 ldap-auth 服务(如步骤2所示)。
8. ldap-auth 服务解码 cookie,然后做 LDAP 认证。
9. 下一个操作取决于 LDAP 认证是否成功
/etc/nginx/conf.d/auth.conf
proxy_cache_path cache/ keys_zone=auth_cache:10m;
server {
listen 80;
server_name xxx.xxx.xxx.xxx;
location / {
auth_request /auth;
error_page 401 = @error401;
set $mysitename 'ES1 warehouse staff'; # 自定义变量
auth_request_set $user $upstream_http_x_forwarded_user;
proxy_set_header X-Forwarded-User $user;
root /var/www/html;
proxy_pass http://xxx.xxx.xxx.xxx:33307; # 认证后目标服务器
allow 192.168.0.0/16; # 增加白名单
allow 10.0.0.0/8;
deny all;
}
location @error401 {
add_header Set-Cookie "NSREDIRECT=$scheme://$http_host$request_uri;Path=/";
add_header Set-Cookie "NSREDIRECTSITENAME=$mysitename";
return 302 /login;
}
location /login {
proxy_pass http://127.0.0.1:8001/login;
}
location /logout {
proxy_pass http://127.0.0.1:8001/logout;
}
location /auth {
internal;
proxy_pass http://127.0.0.1:8001/auth;
proxy_set_header Host $host;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
}
location /static {
proxy_pass http://127.0.0.1:8001/static;}
location /captcha {
proxy_pass http://127.0.0.1:8001/captcha;}
}
Flask
目录架构
.
├── app.py
├── config.py
├── mycaptcha.py
├── run.py
├── static
│ ├── captcha
│ ├── favicon.ico
│ ├── images
│ │ ├── avatar.png
│ │ ├── bg.svg
│ │ ├── login-bg.jpg
│ │ ├── loginbg.jpg
│ │ └── logo.png
│ ├── js
│ │ ├── jquery-3.3.1.min.js
│ │ ├── main.js
│ │ └── template-web.js
│ └── style
│ └── style.css
├── templates
│ └── login.html
└── views.py
run.py
#!`which python`
from app import app
app.run(host='0.0.0.0',port=8001)
app.py
# coding: utf-8
import json
from flask import Flask, url_for, request, session, make_response, abort
from flask_ldap3_login import LDAP3LoginManager
from flask_login import LoginManager, login_user, UserMixin, current_user, logout_user, login_required
from flask import render_template_string, redirect, render_template, flash, get_flashed_messages
from flask_ldap3_login.forms import LDAPLoginForm
import re
import ldap3
from config import *
import string, random, time
from mycaptcha import Captcha
import os
from datetime import timedelta
import hashlib
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret'
app.config['DEBUG'] = True
# Setup LDAP Configuration Variables. Change these to your own settings.
# All configuration directives can be found in the documentation.
# session
app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)
# Hostname of your LDAP Server
app.config['LDAP_HOST'] = LDAPURL
# Base DN of your directory
app.config['LDAP_BASE_DN'] = 'dc=xxx,dc=cn'
# Users DN to be prepended to the Base DN
app.config['LDAP_USER_DN'] = 'ou=staff'
# Groups DN to be prepended to the Base DN
app.config['LDAP_GROUP_DN'] = 'ou=g'
# The RDN attribute for your user schema on LDAP
app.config['LDAP_USER_RDN_ATTR'] = 'cn'
# The Attribute you want users to