Apache:將服務圖像限制為經過身份驗證的使用者
我正在嘗試找出一種方法來限制對我的 apache 配置中的媒體文件夾的訪問。該文件夾從 Django 站點進行上傳,並且圖像/pdf 上傳顯示在站點中以向經過身份驗證的使用者顯示。問題是,任何未經身份驗證的 schmo 都可以導航到
mysite.com/media/images/pic1.jpg
. 這應該是不可能的;我已經嘗試了一些方法來限制這種行為,但我認為我需要一兩個指針。第一次嘗試:XSendfile
Xsendfile 似乎工作,但它(顧名思義)發送文件以供下載,然後我應該顯示圖像的頁面沒有載入。所以看來這不是我的案例所需要的。
第二次嘗試:重寫規則
我在 apache 配置中添加了一些重寫規則:
RewriteCond "%{HTTP_REFERER}" "!^$" RewriteCond "%{HTTP_REFERER}" "!mysite.com/priv/" [NC] RewriteRule "\.(gif|jpg|png|pdf)$" "-" [F,NC]
需要身份驗證的站點的所有部分都在
/priv/
路徑後面,所以我的想法是,如果這可行,那麼導航到/media/images/pic1.jpg
將被重寫。但這不起作用,mysite.com/media/images/pic1.jpg
仍然顯示圖像。第三次嘗試:環境
我在虛擬主機內的環境中嘗試了類似的東西:
<VirtualHost *:80> ... SetEnvIf Referer "mysite\.com\/priv" localreferer SetEnvIf Referer ^$ localreferer <FilesMatch "\.(jpg|png|gif|pdf)$"> Require env localreferer </FilesMatch> ... </VirtualHost>
但這也沒有用;我仍然可以直接導航到圖像。
第四次嘗試:需要有效使用者
我添加
Require valid-user
到 v-host,但我不知道如何根據 Django 使用者模型檢查它。在此更改之後,每次載入顯示圖像的頁面時,我都會收到登錄提示(但沒有 htaccess 等,沒有什麼可驗證的,並且網站上也沒有顯示圖像。然後我嘗試實現此處描述的內容(https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/apache-auth/),但我的 django 項目不喜歡
WSGIHandler
(與預設值相反get_wsgi_application()
)。我得到一個raise AppRegistryNotReady("Apps aren't loaded yet.")
錯誤。看起來這可能是最合理的方法,但我不知道如何開始WSGIHandler
工作,或者使用get_wsgi_application()
.我知道我可以給文件一個難以猜測的類似 uuid 的名稱,但這似乎是一個半途而廢的解決方案。那麼,限制對媒體文件夾的訪問以使這些圖像僅在使用者通過身份驗證的站點部分內連結的最佳策略是什麼?
Ubuntu 20.04、阿帕奇 2.4
| 編輯,遵循一些建議 |
auth.py
def check_password(environ, username, password): print("---->>>---->>>---->>>---->>>---->>> check_password() has been called <<<----<<<----<<<----<<<----<<<----") return True #from django.contrib.auth.handlers.modwsgi import check_password
Apache 日誌顯示該腳本已載入,但該函式顯然未執行,因為列印語句未出現在日誌中。我在這個文件和 wsgi.py 文件中放置了一個雜散的列印語句,以確保該策略進入日誌,只有 wsgi.py 文件中的策略進入日誌。
虛擬主機:
<VirtualHost *:80> ServerName mysite.com ServerAlias mysite.com DocumentRoot /path/to/docroot/ Alias /static/ /path/to/docroot/static/ # Not sure if I need this Alias /media/ /path/to/docroot/media/ <Directory /path/to/docroot/static/> Require all granted </Directory> <Directory /path/to/docroot/media/> Require all granted </Directory> # this is my restricted access directory <Directory /path/to/docroot/media/priv/> AuthType Basic AuthName "Top Secret" AuthBasicProvider wsgi WSGIAuthUserScript /path/to/docroot/mysite/auth.py Require valid-user </Directory> <Directory /path/to/docroot/mysite/> <Files "wsgi.py"> Require all granted </Files> </Directory> WSGIDaemonProcess open-ancestry-web python-home=/path/to/ENV/ python-path=/path/to/docroot/ processes=10 threads=10 WSGIProcessGroup mysite-pgroup WSGIScriptAlias / /path/to/docroot/mysite/wsgi.py LogLevel trace8 ErrorLog "|/bin/rotatelogs -l /path/to/logs/%Y%m%d-%H%M%S_443errors.log 30" CustomLog "|/bin/rotatelogs -l /path/to/logs/%Y%m%d-%H%M%S_443access.log 30" combined </VirtualHost>
|另一個編輯 |
我接受了答案,因為現在一切正常。有很多活動元件,這導致了答案的最初問題。(1) 測試 check_password 函式沒有出現在 apache 日誌中……好吧,它出現在
/var/log/apache2/error.log
而不是設置的自定義日誌中。不知道為什麼,但是好吧…(2) 我的 venv 沒有正確啟動,我實際上並沒有註意到這一點,因為 django 也安裝在系統 Python 上。我從 virtualenv 複製
activate_this.py
腳本並將其添加到我的 venv 並將這樣的東西添加到我的 wsgi 文件中activate_this = '/path/to/ENV/bin/activate_this.py' with open(activate_this) as f: exec(f.read(), {'__file__': activate_this})
修復這些問題後,從 wsgi.py 文件呼叫 check_password 函式即可工作。這裡的“有效”意味著它限制對未經身份驗證的使用者不應訪問的文件夾的訪問。使用者仍然需要提供兩次憑據——一次在正常 django 視圖中,一次在瀏覽器提示中。這很煩人,但實際上我的問題是關於限制訪問的,所以我將它留到另一天。
從 auth.py 呼叫 check_password 的答案建議與我的項目不合作。我收到的錯誤提示它是在 wsgi.py 之前呼叫的——似乎在呼叫 check_password 時未載入 venv 或未載入設置。
這就是 get_wsgi_application 正在做的事情:
def get_wsgi_application(): django.setup(set_prefix=False) # this will lead to "apps_ready=true" return WSGIHandler()
它在返回處理程序之前設置 django 環境。
以下應該在您的 wsgi.py 中解決問題:
application = get_wsgi_application() from django.contrib.auth.handlers.modwsgi import check_password # the sequence is important!!
實際上問題出在 modwsgi.py 的第一行:
UserModel = auth.get_user_model()
因為 get_user_model() 將檢查 apps_ready 以及所有在 python 執行文件導入的那一刻完成的事情!
更好的方法是創建一個單獨的 auth.py 並首先檢查它是否真的被 Apache 呼叫,並通過簡單的列印將轉到 Apache 的 error.log:
def check_password(environ, username, password): print("*********** check_password() has been called ********") return True
一旦執行,您可以用 import 語句替換它並使用 djangos check_password()。
from django.contrib.auth.handlers.modwsgi import check_password
然後在 httpd-vhosts.conf 中類似以下內容:
<VirtualHost *:80> .... <Directory path_to_server_root/secret> AuthType Basic AuthName "Top Secret" AuthBasicProvider wsgi WSGIAuthUserScript path_to_wsgi/wsgi.py Require valid-user </Directory> </VirtualHost>