基於Django設計Kibana使用者認證方案 薦
問題背景
前段時間,負責ELK那哥們兒想把Kibana調整成為LDAP內部使用者認證,運維這邊瞭解到這個需求,著手調研。
解決方案及思路
首先Kibana現在只是用了一個叫 Search guard 的外掛來控制訪問,但是這個外掛需要和JIRA使用者統一認證,需要手動開通賬戶,維護成本高,並且實施起來的效率也很低,所以才打算搭建一個LDAP伺服器,然後同步JIRA使用者資訊,實現Kibana統一認證。
經過討論方案,大致的初步方案就是通過結合Nginx的 auth_request 模組,當用戶請求一個受特定保護的資源時候, auth_request 會將請求轉發到LDAP驗證服務上,然後根據驗證服務返回的狀態碼決定重定向或者允許訪問等進一步的操作。其中在Nginx配置檔案中的LDAP認證服務中需要完善LDAP伺服器的配置資訊。大概的思路就是這樣,想到這裡的時候,又發現一個問題, 如何保證LDAP和JIRA使用者能夠及時同步呢,並且各種許可權的資訊也需要相應的對接 。
這時候負責 JIRA 的哥們兒,說其實我們那邊有個介面,可以通過POST使用者名稱和密碼資訊,驗證是否能夠登入。並且驗證通過會返回一個有關使用者資訊的JSON,如果想要進行許可權控制的話,也可以通過這個JSON。所以現在事情就變得簡單了,重新簡單梳理一下解決方案。
如下:
- 1、使用者請求受保護的Nginx反向代理資源
- 2、Nginx的auth_request模組將請求轉發給自己寫的django的驗證服務中。判斷cookie解密後是否正確。如果都符合條件則返回200狀態碼,nginx不會攔截請求,而是構建一個subprocess請求受保護的本地資源。
- 3、如果django驗證服務沒有通過,則會返回一個401請求,nginx對401請求進行攔截並且重定向到登入頁面,所以就返回到django的login服務中,展示登入介面。
- 4、在登入介面中,輸入使用者名稱和密碼提交,傳到後端時,後端使用內部介面,傳送使用者名稱和密碼,通過返回的狀態碼驗證是否成功,成功之後將使用者名稱加密之後放入cookie並且重定向到受保護的資源中;驗證失敗則返回提示使用者名稱或者密碼失敗。
詳細設計
前期準備工作
版本選擇:
Nginx 1.14.1
Django 1.9.13
Kibana 6.5.1
Python 2.7
需要注意事項: 1、Nginx需要注意的點就是,預設yum安裝的Nginx沒有編譯auth_request模組, 所以需要到官網重新下載原始碼,增加--with-http_auth_request_module進行編譯。 2、程式碼中需要用到Python的requests、Crypto 模組需要額外安裝
Nginx 1.4.1下載地址:
ofollow,noindex" target="_blank">http://nginx.org/download/nginx-1.14.1.tar.gz
requests 模組安裝:
pip install requests
Crypto模組安裝:
pip install Crypto
安裝成功後,可分別通過nginx -V、pip show requests、pip show Crypto驗證:


程式碼分析
最開始設計後端的驗證服務時參考了Nginx官網上,Nginx結合ldap的身份驗證demo,demo地址:
https://github.com/nginxinc/nginx-ldap-auth
demo中大致的思路就是接受請求,首次訪問將會重定向到login頁面上,loging登入後,將接收到的使用者名稱和密碼,去查詢ldap伺服器上的資訊,成功後將把使用者名稱和密碼以 ; 號連線後做 base64 寫入 cookie,下一次訪問受保護的資源時,然後寫 Location 裡寫入 target 的值,來實現重定向跳回。
但是demo中首先的問題就是使用者和密碼這種敏感的資訊不應該放入cookie中,並且在demo中用到了簡單的BaseHTTPServer ,用這個模組就會出現幾個問題:
- 1、不支援url的解析和轉發,需要使用者自己解析
- 2、回寫的響應需要自己維護格式,容易出錯
- 3、沒有模板支援,如果需要寫HTML頁面,也需要自己維護。
所以為了解決以上的缺點,剛好對於Django有一定的瞭解,就打算基於Django框架實現使用者驗證的服務,以及登入時模板頁面。廢話不多說來看看程式碼。
程式碼詳解: 首先是驗證服務,通過檢查是否存在 cookie ,不存在的話就返回狀態碼401;如果存在的話,通過將cookie解密,獲取其中關鍵的欄位,判斷是否登入,如果解密成功的話,返回狀態碼 200 ,解密失敗的話,返回狀態碼 401 。
class prcrypt(object): def __init__(self,key): if len(key) < 16: key = key+(16-len(key))*'\0' self.key = key self.mode = AES.MODE_CBC def encrypt(self,text): cryptor = AES.new(self.key,self.mode,IV=self.key) length = 16 count = len(text) add = count % length if add : text = text + ('\0' * (length-add)) self.ciphertext = cryptor.encrypt(text) return base64.b64encode(self.ciphertext) def decrypt(self,text): cryptor = AES.new(self.key,self.mode,IV=self.key) plain_text = cryptor.decrypt(base64.b64decode(text)) return plain_text.rstrip('\0')
程式碼詳解:這段加密操作通過 aes 模組進行加密,大概原理首先傳入一個 key 值,作為例項化 AES物件的金鑰 。然後將需要加密的文字補足成 16的倍數 進行加密操作,最後將加密後的文字 base64 進行轉換。解密的話 逆操作 即可。
程式碼詳解:首先 前面一部分 程式碼是接收前端傳來的使用者名稱和密碼,並且通過requests模組post到指定的介面地址,然後解析返回的結果,根據返回結果的狀態碼判斷是否能登陸成功。
第二部分通過判斷登入成功後,將 使用者名稱和登入狀態 加密,設定cookie,指定 超時時間 一小時,只能由http協議傳輸。然後重定向到 受保護的資源 ,受保護的資源就會再次請求 驗證服務 ,驗證服務對cookie進行 判斷正確後 就會允許訪問受保護資源;如果 登入失敗 則會返回無效使用者名稱和密碼到登入介面。
前端登入介面:

配置分析
http { include/etc/nginx/mime.types; upstream backend { server 192.168.128.5:5601; } #需要受保護的Kibana程式 server { listen 8081; # nginx服務開放8081埠 location / { auth_request /auth-proxy; error_page 401 =200 /login; proxy_pass http://backend/; } #這個路徑下受auth-request保護,所有401的請求都會重定向到login上 location /login { proxy_pass http://127.0.0.1:8888/login; proxy_set_header X-Target $request_uri; } # 這是我們認證的頁面,指向我們django專案中的login路徑 location /auth-proxy { internal; proxy_pass http://127.0.0.1:8888; } # 這裡用作auth-request請求的路徑 location /static{ alias /data/htdocs/www/elk/static/; } } }
上面就是主要的Nginx配置檔案
urlpatterns = [ url(r'^$',login), url(r'^login',login), url(r'^auth-proxy',nginx_auth), url(r'^admin/', include(admin.site.urls)), ]
對應django專案的urls檔案
總結
其實這個使用者認證的解決方案不難理解,比較麻煩就是最初需求瞭解確定以及和其他專案負責人溝通的問題。在技術上單純的沒什麼難點。
通過在網上查資料,還有Nginx官網上的一些demo有助於你迅速瞭解到模組的用處,以及對一些問題的解決方案,不少網友也遇到了類似的問題,在他們身上多總結經驗,有助於自己解決問題,所以一句話“多動手,多溝通,知行合一”。
以下是我參考的一些網站和部落格,大家有興趣可以看看:
Nginx官網上利用auth_request結合LDAP的認證方案:
https://www.nginx.com/blog/nginx-plus-authenticate-users/用 Nginx 的 auth_request 模組整合 LDAP 認證:
https://www.jianshu.com/p/9f2da3cf5579
python的aes加密和解密:
https://my.oschina.net/u/1458120/blog/648350