Ngnix,一個高性能的web服務(wù)器纳胧,毫無疑問它是當(dāng)下的寵兒镰吆。卓越的性能,靈活可擴展躲雅,在服務(wù)器領(lǐng)域里攻城拔寨鼎姊,征戰(zhàn)天下。
靜態(tài)文件對于大多數(shù)website是不可或缺的一部分相赁。使用Nginx來處理靜態(tài)文件也是常見的方式相寇。然而,一些靜態(tài)文件钮科,我們并不像任何情況下都公開給任何用戶唤衫。例如一些提供給用戶下載的文件,一些用戶上傳的涉及用戶隱私的圖片等绵脯。我們我希望用戶登錄的情況下可以訪問佳励,未登錄的用戶則不可見。
粗略的處理蛆挫,在后端程序可以做過濾赃承,渲染頁面的時候,在視圖邏輯里面驗證用戶登錄悴侵,然后返回對應(yīng)的頁面瞧剖。例如下面的flask代碼(偽代碼)
@app.router('/user/idcard'):
def user_idcard_page():
if user is login:
return '<img src="/upload/user/xxx.png'>"
else:
reutrn '<p>Pemission Denied<p>', 403
可是這樣的處理,還有一個問題可免,靜態(tài)文件是交給 nginx 處理的抓于,如果hacker找到了文件的絕對地址,直接訪問 http://www.example.com/upload/user/xxx.png
也是可以的浇借。恰巧這些文件又涉及用戶隱私捉撮,比如用戶上傳的身份證照片。那么碼農(nóng)可不希望第二天媒體報道妇垢,知名網(wǎng)站XXX存在漏洞巾遭,Hacker獲取了用戶身份證等信息。
為了做這樣的限制闯估,可以借助 Nginx 的一個小功能----XSendfile恢总。 其原理也比較簡單,大概就是使用了請求重定向睬愤。
我們知道片仿,如果用Nginx做服務(wù)器前端的反向代理,一個請求進來尤辱,nginx先補捉到砂豌,然后再根據(jù)規(guī)則轉(zhuǎn)發(fā)給后端的程序處理厢岂,或者直接處理返回。前者處理一些動態(tài)邏輯阳距,后者多是處理靜態(tài)文件塔粒。因此上面那個例子中,直接訪問靜態(tài)文件的絕對地址筐摘,Nginx就直接返回了卒茬,并沒有調(diào)用后端的 user_idcard_page
做邏輯限制。
為了解決這個問題咖熟,nginx提供的 XSendfile功能圃酵,簡而言之就是用 internal 指令。該指令表示只接受內(nèi)部的請求馍管,即后端轉(zhuǎn)發(fā)過來的請求郭赐。后端的視圖邏輯中,需要明確的寫入X-Accel-Redirect
這個headers信息确沸。
偽代碼如下:
location /upload/(.*) {
alias /vagrant/;
internal;
}
@app.router('upload/<filename>')
@login_required
def upload_file(filename):
response = make_response()
response['Content-Type'] = 'application/png'
response['X-Accel-Redirect'] = '/vagrant/upload/%s' % filename
return response
經(jīng)過這樣的處理捌锭,就能將靜態(tài)資源進行重定向罗捎。這樣的用法還是比較常見的,很多下載服務(wù)器可以通過這樣的手段針對用戶的權(quán)限做下載處理桨菜。
Flask是我喜歡的web框架,F(xiàn)lask甚至實現(xiàn)了一個 sendfile的方法雷激,比上面的做法還簡單。我用vagrant作了一個虛擬機告私,用Flask實現(xiàn)了上面的需求屎暇,具體代碼如下:
項目結(jié)構(gòu)
project struct
project
app.py
templates
static
0.jpeg
upload
0.jpeg
nginx的配置 nginx conf
web.conf
server {
listen 80 default_server;
# server_name localhost;
server_name 192.168.33.10;
location / {
proxy_pass http://127.0.0.1:8888;
proxy_redirect off;
proxy_set_header Host $host:8888;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 正常的靜態(tài)文件
location /static/(.*) {
root /vagrant/;
}
# 用戶上傳的文件驻粟,需要做權(quán)限限制
location /upload/(.*) {
alias /vagrant/;
internal; # 只接受內(nèi)部請求的指令
}
}
Flask 代碼
app.py
from functools import wraps
from flask import Flask, render_template, redirect, url_for, session, send_file
app = Flask(__name__)
app.config['SECRET_KEY'] = 'you never guess'
def login_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not session.get('login'):
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
@app.route('/')
def index():
return 'index'
@app.route('/user')
@login_required
def user():
return render_template('upload.html')
# 用戶上傳的文件視圖處理,在此處返回請求給nginx
@app.route('/upload/<filename>')
@login_required
def upload(filename):
return send_file('upload/{}'.format(filename))
@app.route('/login')
def login():
session['login'] = True
return 'log in'
@app.route('/logout')
def logout():
session['login'] = False
return 'log out'
if __name__ == '__main__':
app.run(debug=True)
簡單部署
gunicorn -w4 -b0.0.0.0:8888 app:app --access-logfile access.log --error-logfile error.log
代碼示例 https://github.com/rsj217/flask--scaffold/tree/master/nginx-x-sendfile