DjangoTornadoNginxAuth » History » Version 4
Mark Caglienzi, 12/12/2013 02:52 PM
Snellito il file di configurazione di nginx, aggiunto il passaggio di cookie da django a tornado (thanks Christopher!)
1 | 1 | Christopher R. Gabriel | h1. Django Tornado Nginx Auth |
---|---|---|---|
2 | 2 | Mark Caglienzi | |
3 | 2 | Mark Caglienzi | Si suppone che si abbia un progetto django nella directory @/home/utente/projects/django/@ e un progetto tornado nella directory @/home/utente/projects/tornado/@. Si vuole configurare nginx in modo che django venga servito all'url http://example.com/ e tornado all'url http://example.com/tornado/, facendo in modo che l'accesso a tornado sia inibito se non si è autenticati a django. |
4 | 2 | Mark Caglienzi | |
5 | 2 | Mark Caglienzi | La guida è condotta su un sistema Debian Jessie, al momento in testing (per altre versioni probabilmente andrà adattato qualcosa). |
6 | 2 | Mark Caglienzi | |
7 | 2 | Mark Caglienzi | h2. Prerequisiti |
8 | 2 | Mark Caglienzi | |
9 | 2 | Mark Caglienzi | Servono (oltre a django e tornado, ovviamente) anche i pacchetti: @devscripts@, @mercurial@, @fakeroot@. |
10 | 2 | Mark Caglienzi | |
11 | 2 | Mark Caglienzi | h2. Compilazione di nginx |
12 | 2 | Mark Caglienzi | |
13 | 2 | Mark Caglienzi | Esiste "un plugin per nginx":https://github.com/perusio/nginx-auth-request-module (scritto da uno dei core developer di nginx stesso) che nella versione 1.4.x del server (quella attualmente in debian) non è incluso. Quindi bisogna scaricare il plugin e ricompilare nginx. Per comodità si useranno il più possibile i sorgenti e il sistema di build di debian. |
14 | 2 | Mark Caglienzi | |
15 | 2 | Mark Caglienzi | <pre> |
16 | 2 | Mark Caglienzi | $ mkdir /home/utente/projects/nginx |
17 | 2 | Mark Caglienzi | $ cd /home/utente/projects/nginx |
18 | 2 | Mark Caglienzi | $ dget http://ftp.de.debian.org/debian/pool/main/n/nginx/nginx_1.4.4-1.dsc |
19 | 2 | Mark Caglienzi | </pre> |
20 | 2 | Mark Caglienzi | |
21 | 2 | Mark Caglienzi | Questo per scaricare il pacchetto sorgente di nginx 1.4.4 e averlo automaticamente scompattato. |
22 | 2 | Mark Caglienzi | |
23 | 2 | Mark Caglienzi | <pre> |
24 | 2 | Mark Caglienzi | $ cd nginx-1.4.4/debian/modules |
25 | 2 | Mark Caglienzi | $ hg clone http://mdounin.ru/hg/ngx_http_auth_request_module nginx-auth-request |
26 | 2 | Mark Caglienzi | </pre> |
27 | 2 | Mark Caglienzi | |
28 | 2 | Mark Caglienzi | A questo punto nella sotto-directory @debian/modules/nginx-auth-request@ c'è il codice del plugin richiesto (preso direttamente dal repository mercurial dove viene sviluppato, dato che il repository su github è meno aggiornato). |
29 | 2 | Mark Caglienzi | |
30 | 2 | Mark Caglienzi | <pre> |
31 | 2 | Mark Caglienzi | # apt-get build-dep nginx |
32 | 2 | Mark Caglienzi | </pre> |
33 | 2 | Mark Caglienzi | |
34 | 2 | Mark Caglienzi | In questo modo verranno installati i pacchetti necessari a compilare nginx. Ora bisogna modificare il file @debian/rules@ nella sezione riguardante il pacchetto binario @nginx-full@ (solo in questa, nelle altre non è necessario), aggiungendo la riga: |
35 | 2 | Mark Caglienzi | |
36 | 2 | Mark Caglienzi | <pre> |
37 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/nginx-auth-request \ |
38 | 2 | Mark Caglienzi | </pre> |
39 | 2 | Mark Caglienzi | |
40 | 2 | Mark Caglienzi | sotto alle altre direttive @--add-module@, in modo che il file si presenti così: |
41 | 2 | Mark Caglienzi | |
42 | 2 | Mark Caglienzi | <pre> |
43 | 2 | Mark Caglienzi | [...] |
44 | 2 | Mark Caglienzi | config.status.full: config.env.full ### In questa sezione... |
45 | 2 | Mark Caglienzi | cd $(BUILDDIR_full) && CFLAGS="$(CFLAGS)" CORE_LINK="$(LDFLAGS)" ./configure \ |
46 | 2 | Mark Caglienzi | --prefix=/usr/share/nginx \ |
47 | 2 | Mark Caglienzi | --conf-path=/etc/nginx/nginx.conf \ |
48 | 2 | Mark Caglienzi | --error-log-path=/var/log/nginx/error.log \ |
49 | 2 | Mark Caglienzi | --http-client-body-temp-path=/var/lib/nginx/body \ |
50 | 2 | Mark Caglienzi | --http-fastcgi-temp-path=/var/lib/nginx/fastcgi \ |
51 | 2 | Mark Caglienzi | --http-log-path=/var/log/nginx/access.log \ |
52 | 2 | Mark Caglienzi | --http-proxy-temp-path=/var/lib/nginx/proxy \ |
53 | 2 | Mark Caglienzi | --http-scgi-temp-path=/var/lib/nginx/scgi \ |
54 | 2 | Mark Caglienzi | --http-uwsgi-temp-path=/var/lib/nginx/uwsgi \ |
55 | 2 | Mark Caglienzi | --lock-path=/var/lock/nginx.lock \ |
56 | 2 | Mark Caglienzi | --pid-path=/run/nginx.pid \ |
57 | 2 | Mark Caglienzi | --with-pcre-jit \ |
58 | 2 | Mark Caglienzi | --with-debug \ |
59 | 2 | Mark Caglienzi | --with-http_addition_module \ |
60 | 2 | Mark Caglienzi | --with-http_dav_module \ |
61 | 2 | Mark Caglienzi | --with-http_geoip_module \ |
62 | 2 | Mark Caglienzi | --with-http_gzip_static_module \ |
63 | 2 | Mark Caglienzi | --with-http_image_filter_module \ |
64 | 2 | Mark Caglienzi | --with-http_realip_module \ |
65 | 2 | Mark Caglienzi | --with-http_stub_status_module \ |
66 | 2 | Mark Caglienzi | --with-http_ssl_module \ |
67 | 2 | Mark Caglienzi | --with-http_sub_module \ |
68 | 2 | Mark Caglienzi | --with-http_xslt_module \ |
69 | 2 | Mark Caglienzi | --with-ipv6 \ |
70 | 2 | Mark Caglienzi | --with-mail \ |
71 | 2 | Mark Caglienzi | --with-mail_ssl_module \ |
72 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/nginx-auth-pam \ |
73 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/nginx-dav-ext-module \ |
74 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/nginx-echo \ |
75 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/nginx-upstream-fair \ |
76 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/ngx_http_substitutions_filter_module \ |
77 | 2 | Mark Caglienzi | --add-module=$(MODULESDIR)/nginx-auth-request \ ### ...aggiungere questa riga |
78 | 2 | Mark Caglienzi | $(CONFIGURE_OPTS) >$@ |
79 | 2 | Mark Caglienzi | touch $@ |
80 | 2 | Mark Caglienzi | [...] |
81 | 2 | Mark Caglienzi | </pre> |
82 | 2 | Mark Caglienzi | |
83 | 2 | Mark Caglienzi | A questo punto bisogna compilare i pacchetti binari: |
84 | 2 | Mark Caglienzi | |
85 | 2 | Mark Caglienzi | <pre> |
86 | 2 | Mark Caglienzi | $ cd /home/utente/projects/nginx/nginx-1.4.4 |
87 | 2 | Mark Caglienzi | $ fakeroot debian/rules binary |
88 | 2 | Mark Caglienzi | </pre> |
89 | 2 | Mark Caglienzi | |
90 | 2 | Mark Caglienzi | Alla fine della compilazione nella directory @/home/utente/projects/nginx@ saranno presenti i pacchetti binari: |
91 | 2 | Mark Caglienzi | |
92 | 2 | Mark Caglienzi | <pre> |
93 | 2 | Mark Caglienzi | nginx_1.4.4-1_all.deb |
94 | 2 | Mark Caglienzi | nginx-common_1.4.4-1_all.deb |
95 | 2 | Mark Caglienzi | nginx-doc_1.4.4-1_all.deb |
96 | 2 | Mark Caglienzi | nginx-extras_1.4.4-1_amd64.deb |
97 | 2 | Mark Caglienzi | nginx-extras-dbg_1.4.4-1_amd64.deb |
98 | 2 | Mark Caglienzi | nginx-full_1.4.4-1_amd64.deb |
99 | 2 | Mark Caglienzi | nginx-full-dbg_1.4.4-1_amd64.deb |
100 | 2 | Mark Caglienzi | nginx-light_1.4.4-1_amd64.deb |
101 | 2 | Mark Caglienzi | nginx-light-dbg_1.4.4-1_amd64.deb |
102 | 2 | Mark Caglienzi | nginx-naxsi_1.4.4-1_amd64.deb |
103 | 2 | Mark Caglienzi | nginx-naxsi-dbg_1.4.4-1_amd64.deb |
104 | 2 | Mark Caglienzi | nginx-naxsi-ui_1.4.4-1_all.deb |
105 | 2 | Mark Caglienzi | </pre> |
106 | 2 | Mark Caglienzi | |
107 | 2 | Mark Caglienzi | Si può procedere ad installare i pacchetti necessari direttamente da questa directory: |
108 | 2 | Mark Caglienzi | |
109 | 2 | Mark Caglienzi | <pre> |
110 | 2 | Mark Caglienzi | # dpkg -i nginx_1.4.4-1_all.deb nginx-common_1.4.4-1_all.deb nginx-full_1.4.4-1_amd64.deb |
111 | 2 | Mark Caglienzi | </pre> |
112 | 2 | Mark Caglienzi | |
113 | 2 | Mark Caglienzi | h2. Avvio delle applicazioni |
114 | 2 | Mark Caglienzi | |
115 | 2 | Mark Caglienzi | Dato che la guida si concentra sulla gestione dell'autenticazione tra django e tornado, si assume per semplicità che l'applicazione tornado sia hello-world ("link":http://www.tornadoweb.org/en/stable/#hello-world) e che le due applicazioni siano servite dai server di sviluppo: |
116 | 2 | Mark Caglienzi | |
117 | 2 | Mark Caglienzi | <pre> |
118 | 2 | Mark Caglienzi | $ cd /home/utente/projects/django |
119 | 2 | Mark Caglienzi | $ ./manage.py runserver |
120 | 2 | Mark Caglienzi | </pre> |
121 | 2 | Mark Caglienzi | |
122 | 2 | Mark Caglienzi | <pre> |
123 | 2 | Mark Caglienzi | $ cd /home/utente/projects/tornado |
124 | 2 | Mark Caglienzi | $ ./hello-world.py |
125 | 2 | Mark Caglienzi | </pre> |
126 | 2 | Mark Caglienzi | |
127 | 2 | Mark Caglienzi | In questo modo il server di sviluppo di django risponde a @127.0.0.1:8000@ e tornado a @127.0.0.1:8888@ |
128 | 2 | Mark Caglienzi | |
129 | 2 | Mark Caglienzi | h2. Creazione del file di configurazione di nginx |
130 | 2 | Mark Caglienzi | |
131 | 2 | Mark Caglienzi | Si crei il file @/etc/nginx/sites-available/django@: |
132 | 2 | Mark Caglienzi | |
133 | 2 | Mark Caglienzi | <pre> |
134 | 2 | Mark Caglienzi | server { |
135 | 2 | Mark Caglienzi | listen 80; |
136 | 2 | Mark Caglienzi | root /home/utente/projects/django; |
137 | 2 | Mark Caglienzi | server_name django; |
138 | 2 | Mark Caglienzi | access_log /home/utente/projects/django/access.log; |
139 | 2 | Mark Caglienzi | error_log /home/utente/projects/django/error.log; |
140 | 2 | Mark Caglienzi | location / { |
141 | 1 | Christopher R. Gabriel | proxy_pass http://127.0.0.1:8000; |
142 | 2 | Mark Caglienzi | } |
143 | 2 | Mark Caglienzi | location /tornado { |
144 | 2 | Mark Caglienzi | auth_request /is_logged_in/; |
145 | 4 | Mark Caglienzi | proxy_pass http://127.0.0.1:8888; |
146 | 2 | Mark Caglienzi | } |
147 | 2 | Mark Caglienzi | } |
148 | 2 | Mark Caglienzi | </pre> |
149 | 2 | Mark Caglienzi | |
150 | 2 | Mark Caglienzi | e si attivi con: |
151 | 2 | Mark Caglienzi | |
152 | 2 | Mark Caglienzi | <pre> |
153 | 2 | Mark Caglienzi | # ln -s /etc/nginx/sites-available/django /etc/nginx/sites-enabled/django |
154 | 2 | Mark Caglienzi | # /etc/init.d/nginx restart |
155 | 2 | Mark Caglienzi | </pre> |
156 | 2 | Mark Caglienzi | |
157 | 2 | Mark Caglienzi | In questo modo si dice a nginx che per URL sotto a @/tornado@ deve fare una request a @/is_logged_in/@ e accettare di passare il controllo a tornado solo se riceve una risposta HTTP 200. |
158 | 2 | Mark Caglienzi | |
159 | 2 | Mark Caglienzi | Aggiungere la riga |
160 | 2 | Mark Caglienzi | |
161 | 2 | Mark Caglienzi | <pre> |
162 | 2 | Mark Caglienzi | 127.0.0.1 django |
163 | 2 | Mark Caglienzi | </pre> |
164 | 2 | Mark Caglienzi | |
165 | 2 | Mark Caglienzi | al file @/etc/hosts@. |
166 | 2 | Mark Caglienzi | |
167 | 2 | Mark Caglienzi | h2. Modifica delle applicazioni Django e Tornado |
168 | 2 | Mark Caglienzi | |
169 | 2 | Mark Caglienzi | L'applicazione hello world di tornado va modificata così: |
170 | 2 | Mark Caglienzi | <pre> |
171 | 2 | Mark Caglienzi | #!/usr/bin/env python |
172 | 1 | Christopher R. Gabriel | import tornado.ioloop |
173 | 1 | Christopher R. Gabriel | import tornado.web |
174 | 2 | Mark Caglienzi | |
175 | 2 | Mark Caglienzi | class MainHandler(tornado.web.RequestHandler): |
176 | 1 | Christopher R. Gabriel | def get(self): |
177 | 4 | Mark Caglienzi | self.write("Hello, %s!" % self.get_cookie('user')) ### 'Hello, world!' diventa 'Hello, username!' |
178 | 4 | Mark Caglienzi | ### Il setup del cookie viene fatto nella view di login di django (v. sotto) |
179 | 2 | Mark Caglienzi | |
180 | 2 | Mark Caglienzi | application = tornado.web.Application([ |
181 | 4 | Mark Caglienzi | # (r"/", MainHandler), ### Commentare/cancellare questa riga... |
182 | 4 | Mark Caglienzi | (r'/tornado', MainHandler), ### ...e aggiungere questa |
183 | 2 | Mark Caglienzi | ]) |
184 | 2 | Mark Caglienzi | |
185 | 2 | Mark Caglienzi | if __name__ == "__main__": |
186 | 2 | Mark Caglienzi | application.listen(8888) |
187 | 2 | Mark Caglienzi | tornado.ioloop.IOLoop.instance().start() |
188 | 2 | Mark Caglienzi | </pre> |
189 | 2 | Mark Caglienzi | |
190 | 2 | Mark Caglienzi | in modo che @MainHandler@ risponda all'URL @/tornado@ anziché all'URL @/@. Nell'applicazione django va aggiunto in @urls.py@: |
191 | 2 | Mark Caglienzi | |
192 | 2 | Mark Caglienzi | <pre> |
193 | 2 | Mark Caglienzi | url(r'^is_logged_in/$', 'myproject.views.is_logged_in', name='is_logged_in'), |
194 | 2 | Mark Caglienzi | </pre> |
195 | 2 | Mark Caglienzi | |
196 | 2 | Mark Caglienzi | e una view di questo tipo: |
197 | 2 | Mark Caglienzi | |
198 | 2 | Mark Caglienzi | <pre> |
199 | 2 | Mark Caglienzi | from django import http |
200 | 1 | Christopher R. Gabriel | |
201 | 2 | Mark Caglienzi | def is_logged_in(request): |
202 | 1 | Christopher R. Gabriel | if request.user.is_anonymous(): |
203 | 2 | Mark Caglienzi | return http.HttpResponseForbidden("NO") |
204 | 1 | Christopher R. Gabriel | else: |
205 | 1 | Christopher R. Gabriel | return http.HttpResponse("OK") |
206 | 1 | Christopher R. Gabriel | </pre> |
207 | 1 | Christopher R. Gabriel | |
208 | 4 | Mark Caglienzi | che non fa altro che restituire un HTTP 403 in caso l'utente non sia loggato, e un HTTP 200 diversamente. |
209 | 1 | Christopher R. Gabriel | |
210 | 4 | Mark Caglienzi | Bisogna poi settare un cookie con i dati che si vuole che passino da django a tornado nella view di login (e cancellarlo in quella di logout), ad esempio in questo modo: |
211 | 4 | Mark Caglienzi | |
212 | 1 | Christopher R. Gabriel | <pre> |
213 | 4 | Mark Caglienzi | from django import http |
214 | 4 | Mark Caglienzi | from django.contrib import messages |
215 | 4 | Mark Caglienzi | from django.contrib.auth import authenticate, login, logout |
216 | 4 | Mark Caglienzi | from django.contrib.auth.decorators import login_required |
217 | 4 | Mark Caglienzi | from django.core.urlresolvers import reverse |
218 | 4 | Mark Caglienzi | from django.shortcuts import render |
219 | 4 | Mark Caglienzi | from django.utils.translation import ugettext_lazy as _ |
220 | 4 | Mark Caglienzi | |
221 | 4 | Mark Caglienzi | def login_view(request): |
222 | 4 | Mark Caglienzi | username = password = '' |
223 | 4 | Mark Caglienzi | _next = request.GET.get("next", None) |
224 | 4 | Mark Caglienzi | if request.POST: |
225 | 4 | Mark Caglienzi | username = request.POST['username'] |
226 | 4 | Mark Caglienzi | password = request.POST['password'] |
227 | 4 | Mark Caglienzi | user = authenticate(username=username, password=password) |
228 | 4 | Mark Caglienzi | if user is not None: |
229 | 4 | Mark Caglienzi | if user.is_active: |
230 | 4 | Mark Caglienzi | login(request, user) |
231 | 4 | Mark Caglienzi | messages.info(request, _('Welcome, %s!' % username)) |
232 | 4 | Mark Caglienzi | if _next: |
233 | 4 | Mark Caglienzi | response = http.HttpResponseRedirect(_next) |
234 | 4 | Mark Caglienzi | else: |
235 | 4 | Mark Caglienzi | response = http.HttpResponseRedirect(reverse('home')) |
236 | 4 | Mark Caglienzi | response.set_cookie( |
237 | 4 | Mark Caglienzi | key='user', |
238 | 4 | Mark Caglienzi | value=request.user.username, |
239 | 4 | Mark Caglienzi | expires=request.session.get_expiry_age(), |
240 | 4 | Mark Caglienzi | ) |
241 | 4 | Mark Caglienzi | return response |
242 | 4 | Mark Caglienzi | else: |
243 | 4 | Mark Caglienzi | messages.error(request, _('Account %s is disabled' % username)) |
244 | 4 | Mark Caglienzi | else: |
245 | 4 | Mark Caglienzi | messages.error(request, |
246 | 4 | Mark Caglienzi | _("The credentials you supplied were not correct or did not grant access to this resource.")) |
247 | 4 | Mark Caglienzi | return render(request, 'login.html') |
248 | 4 | Mark Caglienzi | |
249 | 4 | Mark Caglienzi | @login_required |
250 | 4 | Mark Caglienzi | def logout_view(request): |
251 | 4 | Mark Caglienzi | logout(request) |
252 | 4 | Mark Caglienzi | response = http.HttpResponseRedirect(reverse('home')) |
253 | 4 | Mark Caglienzi | response.delete_cookie('user') |
254 | 4 | Mark Caglienzi | return response |
255 | 4 | Mark Caglienzi | </pre> |
256 | 4 | Mark Caglienzi | |
257 | 4 | Mark Caglienzi | In questo esempio il cookie @user@ contiene lo username dell'utente appena loggato, ma si possono definire cookie per qualsiasi dato sia necessario passare tra django e tornado. Come si vede il valore di @expires@ del cookie è settato uguale all'@expiry_age@ della sessione attuale. |
258 | 4 | Mark Caglienzi | |
259 | 4 | Mark Caglienzi | Se un template dell'applicazione django ha un link di questo tipo (o se si tenta di scrivere a mano l'URL): |
260 | 4 | Mark Caglienzi | |
261 | 4 | Mark Caglienzi | <pre> |
262 | 2 | Mark Caglienzi | <a href="/tornado" class="btn">Tornado</a> |
263 | 2 | Mark Caglienzi | </pre> |
264 | 2 | Mark Caglienzi | |
265 | 2 | Mark Caglienzi | nginx si comporterà così: |
266 | 2 | Mark Caglienzi | |
267 | 2 | Mark Caglienzi | * Vede la richiesta a @http://django/tornado@ |
268 | 2 | Mark Caglienzi | * La direttiva @auth_request@ passa il controllo all'URL @http://django/is_logged_in/@ |
269 | 2 | Mark Caglienzi | * Django controlla se l'utente è loggato o meno, e dà una response di tipo 200 o 403 |
270 | 4 | Mark Caglienzi | * Se la risposta è 200, allora nginx prosegue e passa il controllo a @http://127.0.0.1:8888@ dove risponde tornado, che avrà accesso ai cookie eventualmente settati dalla view di login di django |
271 | 2 | Mark Caglienzi | * Se la risposta è 403, nginx non prosegue e l'utente si vede impossibilitato a proseguire |
272 | 2 | Mark Caglienzi | |
273 | 3 | Mark Caglienzi | h2. Dubbi/problemi |
274 | 2 | Mark Caglienzi | |
275 | 2 | Mark Caglienzi | * Compilare un plugin (seppur proveniente da una fonte autorevole, come un core dev di nginx) non mi piace troppo, specialmente se in un pacchetto così fondamentale come il server web/proxy |
276 | 2 | Mark Caglienzi | * Non ho ancora risolto il problema per cui l'utente non loggato vede un errore 403 anziché un più educato redirect alla view di login. Purtroppo il plugin non supporta i redirect, ma soltanto 403/200, quindi perché funzioni la direttiva @auth_request@ non ho potuto usare il decoratore @login_required@ nella view django. |
277 | 2 | Mark Caglienzi | * Il "changelog di nginx":http://nginx.org/en/CHANGES dice che dalla versione 1.5.4 del 27 agosto 2013 il modulo @ngx_http_auth_request_module@ è incluso, quindi ci si può aspettare che anche i futuri pacchetti debian lo includano. |