Django, Flask ve Redis Eğitimi: Python Çerçeveleri Arasında Web Uygulaması Oturum Yönetimi
Yayınlanan: 2022-03-11Django Flask'a Karşı: Django Yanlış Seçim Olduğunda
Django'yu çoğu kişisel ve müşteri projemde, çoğunlukla daha klasik web uygulamaları ve ilişkisel veritabanlarını içerenler için seviyorum ve kullanıyorum. Ancak, Django gümüş kurşun değildir.
Tasarım gereği Django, ORM, Template Engine System ve Settings nesnesi ile çok sıkı bir şekilde bağlantılıdır. Ayrıca, bu yeni bir proje değil: geriye dönük olarak uyumlu kalması için çok fazla bagaj taşıyor.
Bazı Python geliştiricileri bunu büyük bir sorun olarak görüyor. Django'nun yeterince esnek olmadığını ve mümkünse bundan kaçındığını ve bunun yerine Flask gibi bir Python mikro çerçevesi kullandığını söylüyorlar.
Ben bu görüşü paylaşmıyorum. Django, her proje spesifikasyonuna uymasa bile, uygun yer ve zamanda kullanıldığında harikadır. Mantra devam ederken: "İş için doğru aracı kullanın".
(Doğru yer ve zaman olmasa bile, bazen Django ile programlamanın benzersiz faydaları olabilir.)
Bazı durumlarda, daha hafif bir çerçeve (Flask gibi) kullanmak gerçekten güzel olabilir. Çoğu zaman, bu mikro çerçeveler, hacklemenin ne kadar kolay olduğunu fark ettiğinizde parlamaya başlar.
Kurtarma için mikro çerçeveler
Müşteri projelerimden birkaçında, Django'dan vazgeçmeyi ve tipik olarak müşteriler ilginç şeyler yapmak istediğinde (bir durumda, örneğin, uygulama nesnesine ZeroMQ yerleştirmek) ve projeyi bir mikro çerçeveye geçmeyi tartıştık. Django ile hedeflere ulaşmak daha zor görünüyor.
Daha genel olarak, Flask'ı aşağıdakiler için yararlı buluyorum:
- Basit REST API arka uçları
- Veritabanı erişimi gerektirmeyen uygulamalar
- NoSQL tabanlı web uygulamaları
- Özel URL yapılandırmaları gibi çok özel gereksinimleri olan web uygulamaları
Aynı zamanda, uygulamamız, Django'nun yıllar önce çözdüğü kullanıcı kaydı ve diğer genel görevleri gerektiriyordu. Hafifliği göz önüne alındığında, Flask aynı araç takımıyla gelmiyor.
Soru ortaya çıktı: Django ya hep ya hiç anlaşması mı? Projeden tamamen çıkarmalı mıyız yoksa diğer mikro çerçevelerin veya geleneksel çerçevelerin esnekliğiyle birleştirmeyi öğrenebilir miyiz? Kullanmak istediğimiz parçaları seçip diğerlerinden kaçınabilir miyiz?
Her iki dünyanın da en iyisine sahip olabilir miyiz? Özellikle oturum yönetimi söz konusu olduğunda evet diyorum.
(Django serbest çalışanları için bir çok proje olduğunu söylemeye gerek yok.)
Şimdi Python Eğitimi: Django Oturumlarını Paylaşma
Bu gönderinin amacı, kullanıcı kimlik doğrulama ve kayıt görevlerini Django'ya devretmek, ancak kullanıcı oturumlarını diğer çerçevelerle paylaşmak için Redis'i kullanmaktır. Bunun gibi bir şeyin faydalı olacağı birkaç senaryo düşünebilirim:
- Django uygulamanızdan ayrı olarak bir REST API geliştirmeniz gerekiyor ancak oturum verilerini paylaşmak istiyorsunuz.
- Daha sonra değiştirilmesi veya herhangi bir nedenle ölçeğinin genişletilmesi gerekebilecek ve yine de oturum verilerine ihtiyaç duyan belirli bir bileşeniniz var.
Bu eğitimde, oturumları iki çerçeve (bu durumda Django ve Flask) arasında paylaşmak için Redis kullanacağım. Mevcut kurulumda, kullanıcı bilgilerini depolamak için SQLite kullanacağım, ancak gerekirse arka uçunuzu bir NoSQL veritabanına (veya SQL tabanlı bir alternatife) bağlayabilirsiniz.
Oturumları Anlamak
Django ve Flask arasında oturumları paylaşmak için, Django'nun oturum bilgilerini nasıl depoladığı hakkında biraz bilgi sahibi olmamız gerekir. Django belgeleri oldukça iyidir, ancak tam olması için biraz arka plan sağlayacağım.
Oturum Yönetimi Çeşitleri
Genel olarak, Python uygulamanızın oturum verilerini iki yoldan biriyle yönetmeyi seçebilirsiniz:
Tanımlama bilgisi tabanlı oturumlar : Bu senaryoda, oturum verileri arka uçtaki bir veri deposunda saklanmaz. Bunun yerine serileştirilir, imzalanır (SECRET_KEY ile) ve istemciye gönderilir. İstemci bu verileri geri gönderdiğinde, bütünlüğü kurcalamaya karşı kontrol edilir ve sunucuda tekrar seri durumdan çıkarılır.
Depolama tabanlı oturumlar : Bu senaryoda, oturum verilerinin kendisi istemciye gönderilmez. Bunun yerine, oturum deposunda depolanan geçerli kullanıcının kimliğini belirtmek için yalnızca küçük bir kısım gönderilir (anahtar).
Örneğimizde, ikinci senaryoyla daha çok ilgileniyoruz: oturum verilerimizin arka uçta saklanmasını ve ardından Flask'ta kontrol edilmesini istiyoruz. Aynı şey öncekinde de yapılabilir, ancak Django belgelerinin belirttiği gibi, ilk yöntemin güvenliği konusunda bazı endişeler var.
Genel İş Akışı
Oturum işleme ve yönetiminin genel iş akışı bu şemaya benzer olacaktır:
Oturum paylaşımını biraz daha ayrıntılı olarak inceleyelim:
Yeni bir istek geldiğinde, ilk adım onu Django yığınındaki kayıtlı ara katman yazılımı aracılığıyla göndermektir. Burada beklediğiniz gibi oturum yönetimi ve yönetimi ile ilgili olan
SessionMiddleware
sınıfıyla ilgileniyoruz:class SessionMiddleware(object): def process_request(self, request): engine = import_module(settings.SESSION_ENGINE) session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) request.session = engine.SessionStore(session_key)
Bu pasajda, Django kayıtlı
SessionEngine
(bu konuya birazdan geleceğiz),request
SESSION_COOKIE_NAME
dosyasını (varsayılan olaraksessionid
) çıkarır ve oturum depolamayı işlemek için seçilenSessionEngine
yeni bir örneğini oluşturur.
Daha sonra (kullanıcı görünümü işlendikten sonra, ancak yine de ara katman yazılımı yığınında), oturum motoru, veri deposundaki değişiklikleri kaydetmek için kaydetme yöntemini çağırır. (Görüntü işleme sırasında, kullanıcı, örneğin
request.session
ile oturum nesnesine yeni bir değer ekleyerek, oturum içinde birkaç şeyi değiştirmiş olabilir.) Ardından, istemciyeSESSION_COOKIE_NAME
gönderilir. İşte basitleştirilmiş versiyon:def process_response(self, request, response): .... if response.status_code != 500: request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, request.session.session_key, max_age=max_age, expires=expires, domain=settings.SESSION_COOKIE_DOMAIN, path=settings.SESSION_COOKIE_PATH, secure=settings.SESSION_COOKIE_SECURE or None, httponly=settings.SESSION_COOKIE_HTTPONLY or None) return response
Özellikle, Redis arka ucuna veri depolamak ve yüklemek için değiştireceğimiz SessionEngine
sınıfıyla ilgileniyoruz.
Neyse ki, bunu bizim için halleden birkaç proje var. İşte redis_sessions_fork'tan bir örnek. Oturumu Redis'e ve Redis'ten (sırasıyla) depolamak ve yüklemek için yazılan save
ve load
yöntemlerine çok dikkat edin:
class SessionStore(SessionBase): """ Redis session back-end for Django """ def __init__(self, session_key=None): super(SessionStore, self).__init__(session_key) def _get_or_create_session_key(self): if self._session_key is None: self._session_key = self._get_new_session_key() return self._session_key def load(self): session_data = backend.get(self.session_key) if not session_data is None: return self.decode(session_data) else: self.create() return {} def exists(self, session_key): return backend.exists(session_key) def create(self): while True: self._session_key = self._get_new_session_key() try: self.save(must_create=True) except CreateError: continue self.modified = True self._session_cache = {} return def save(self, must_create=False): session_key = self._get_or_create_session_key() expire_in = self.get_expiry_age() session_data = self.encode(self._get_session(no_load=must_create)) backend.save(session_key, expire_in, session_data, must_create) def delete(self, session_key=None): if session_key is None: if self.session_key is None: return session_key = self.session_key backend.delete(session_key)
Oturum verilerini yüklemek için Flask'ta benzer bir şey uygulamamız gerekeceğinden, bu sınıfın nasıl çalıştığını anlamak önemlidir. Bir REPL örneği ile daha yakından bakalım:
>>> from django.conf import settings >>> from django.utils.importlib import import_module >>> engine = import_module(settings.SESSION_ENGINE) >>> engine.SessionStore() <redis_sessions_fork.session.SessionStore object at 0x3761cd0> >>> store["count"] = 1 >>> store.save() >>> store.load() {u'count': 1}
Oturum mağazasının arayüzünün anlaşılması oldukça kolaydır, ancak kaputun altında çok şey oluyor. Flask'a benzer bir şey uygulayabilmemiz için biraz daha derine inmeliyiz.

Not: "Neden sadece SessionEngine'i Flask'a kopyalamıyorsunuz?" diye sorabilirsiniz. Söylemesi yapmaktan kolay. Başta tartıştığımız gibi, Django, Settings nesnesi ile sıkı bir şekilde bağlantılıdır, bu nedenle, herhangi bir ek iş yapmadan sadece bazı Django modüllerini içe aktarıp kullanamazsınız.
Django Oturumu (De-)Serileştirme
Dediğim gibi, Django, oturum depolamasının karmaşıklığını maskelemek için çok iş yapıyor. Yukarıdaki snippet'lerde saklanan Redis anahtarını kontrol edelim:
>>> store.session_key u"ery3j462ezmmgebbpwjajlxjxmvt5adu"
Şimdi, bu anahtarı redis-cli'de sorgulayalım:
redis 127.0.0.1:6379> get "django_sessions:ery3j462ezmmgebbpwjajlxjxmvt5adu" "ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ=="
Burada gördüğümüz, çok uzun, Base64 ile kodlanmış bir dizedir. Amacını anlamak için, nasıl ele alındığını görmek için Django'nun SessionBase
sınıfına bakmamız gerekiyor:
class SessionBase(object): """ Base class for all Session classes. """ def encode(self, session_dict): "Returns the given session dictionary serialized and encoded as a string." serialized = self.serializer().dumps(session_dict) hash = self._hash(serialized) return base64.b64encode(hash.encode() + b":" + serialized).decode('ascii') def decode(self, session_data): encoded_data = base64.b64decode(force_bytes(session_data)) try: hash, serialized = encoded_data.split(b':', 1) expected_hash = self._hash(serialized) if not constant_time_compare(hash.decode(), expected_hash): raise SuspiciousSession("Session data corrupted") else: return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions if isinstance(e, SuspiciousOperation): logger = logging.getLogger('django.security.%s' % e.__class__.__name__) logger.warning(force_text(e)) return {}
Encode yöntemi, önce verileri geçerli kayıtlı serileştiriciyle serileştirir. Başka bir deyişle, oturumu daha sonra tekrar oturuma dönüştürebileceği bir dizgeye dönüştürür (daha fazlası için SESSION_SERIALIZER belgelerine bakın). Ardından, serileştirilmiş verileri hash eder ve bu hash'i daha sonra oturum verilerinin bütünlüğünü kontrol etmek için bir imza olarak kullanır. Son olarak, bu veri çiftini kullanıcıya Base64 ile kodlanmış bir dize olarak döndürür.
Bu arada: 1.6 sürümünden önce, Django oturum verilerinin serileştirilmesi için varsayılan olarak turşu kullanıyordu. Güvenlik endişeleri nedeniyle, varsayılan serileştirme yöntemi artık django.contrib.sessions.serializers.JSONSerializer
şeklindedir.
Örnek Oturum Kodlama
Oturum yönetimi sürecini çalışırken görelim. Burada, oturum sözlüğümüz yalnızca bir sayı ve bir tam sayı olacaktır, ancak bunun daha karmaşık kullanıcı oturumlarına nasıl genelleneceğini hayal edebilirsiniz.
>>> store.encode({'count': 1}) u'ZmUxOTY0ZTFkMmNmODA2OWQ5ZjE4MjNhZmQxNDM0MDBiNmQzNzM2Zjp7ImNvdW50IjoxfQ==' >>> base64.b64decode(encoded) 'fe1964e1d2cf8069d9f1823afd143400b6d3736f:{"count":1}'
Mağaza yönteminin sonucu (u'ZmUxOTY…=='), serileştirilmiş kullanıcı oturumunu ve karmasını içeren kodlanmış bir dizedir. Kodu çözdüğümüzde, hem hash ('fe1964e…') hem de oturumu ( {"count":1}
) gerçekten geri alırız.
Kod çözme yönteminin, o oturum için karmanın doğru olduğundan emin olmak için kontrol ettiğini ve Flask'ta kullanmaya başladığımızda verilerin bütünlüğünü garanti ettiğini unutmayın. Bizim durumumuzda, oturumumuzun istemci tarafında kurcalanması konusunda çok endişeli değiliz çünkü:
Tanımlama bilgisi tabanlı oturumlar kullanmıyoruz, yani tüm kullanıcı verilerini istemciye göndermiyoruz.
Flask'ta, bize verilen anahtarın var olup olmadığını söyleyen ve saklanan verileri döndüren salt okunur bir
SessionStore
ihtiyacımız olacak.
Şişeye Genişletme
Ardından, Flask ile çalışmak için Redis oturum motorunun (veritabanı) basitleştirilmiş bir sürümünü oluşturalım. Temel sınıf olarak aynı SessionStore
(yukarıda tanımlanmıştır) kullanacağız, ancak bazı işlevlerini kaldırmamız gerekecek, örneğin hatalı imzaları kontrol etmek veya oturumları değiştirmek. Django'dan kaydedilen oturum verilerini yükleyecek salt okunur bir SessionStore
ile daha çok ilgileniyoruz. Bakalım nasıl bir araya gelecek:
class SessionStore(object): # The default serializer, for now def __init__(self, conn, session_key, secret, serializer=None): self._conn = conn self.session_key = session_key self._secret = secret self.serializer = serializer or JSONSerializer def load(self): session_data = self._conn.get(self.session_key) if not session_data is None: return self._decode(session_data) else: return {} def exists(self, session_key): return self._conn.exists(session_key) def _decode(self, session_data): """ Decodes the Django session :param session_data: :return: decoded data """ encoded_data = base64.b64decode(force_bytes(session_data)) try: # Could produce ValueError if there is no ':' hash, serialized = encoded_data.split(b':', 1) # In the Django version of that they check for corrupted data # I don't find it useful, so I'm removing it return self.serializer().loads(serialized) except Exception as e: # ValueError, SuspiciousOperation, unpickling exceptions. If any of # these happen, return an empty dictionary (ie, empty session). return {}
Yalnızca load
yöntemine ihtiyacımız var çünkü bu, depolamanın salt okunur bir uygulamasıdır. Bu, doğrudan Flask'tan çıkış yapamayacağınız anlamına gelir; bunun yerine, bu görevi Django'ya yönlendirmek isteyebilirsiniz. Unutmayın, buradaki amaç, size daha fazla esneklik sağlamak için bu iki Python çerçevesi arasındaki oturumları yönetmektir.
Şişe Oturumları
Flask mikro çerçevesi, tanımlama bilgisi tabanlı oturumları destekler; bu, tüm oturum verilerinin istemciye gönderildiği, Base64 ile kodlandığı ve kriptografik olarak imzalandığı anlamına gelir. Ama aslında Flask'ın oturum desteğiyle pek ilgilenmiyoruz.
İhtiyacımız olan, Django tarafından oluşturulan oturum kimliğini almak ve isteğin önceden imzalanmış bir kullanıcıya ait olduğundan emin olabilmemiz için bunu Redis arka ucuna göre kontrol etmektir. Özetle, ideal süreç şöyle olacaktır (bu, yukarıdaki şema ile senkronize edilir):
- Kullanıcının çerezinden Django oturum kimliğini alırız.
- Oturum kimliği Redis'te bulunursa, bu kimlikle eşleşen oturumu döndürürüz.
- Değilse, onları bir giriş sayfasına yönlendiririz.
Bu bilgiyi kontrol etmek ve mevcut user_id
g
değişkenine ayarlamak için bir dekoratöre sahip olmak kullanışlı olacaktır:
from functools import wraps from flask import g, request, redirect, url_for def login_required(f): @wraps(f) def decorated_function(*args, **kwargs): djsession_id = request.cookies.get("sessionid") if djsession_id is None: return redirect("/") key = get_session_prefixed(djsession_id) session_store = SessionStore(redis_conn, key) auth = session_store.load() if not auth: return redirect("/") g.user_id = str(auth.get("_auth_user_id")) return f(*args, **kwargs) return decorated_function
Yukarıdaki örnekte, Django verilerini Redis'ten almak için daha önce tanımladığımız SessionStore
kullanmaya devam ediyoruz. Oturumun bir _auth_user_id
varsa, içeriği görüntüleme işlevinden döndürürüz; aksi takdirde, kullanıcı tam istediğimiz gibi bir giriş sayfasına yönlendirilir.
Bir şeyleri birbirine yapıştırmak
Çerezleri paylaşmak için, bir WSGI sunucusu aracılığıyla Django ve Flask'ı başlatmayı ve bunları birbirine yapıştırmayı uygun buluyorum. Bu örnekte CherryPy kullandım:
from app import app from django.core.wsgi import get_wsgi_application application = get_wsgi_application() d = wsgiserver.WSGIPathInfoDispatcher({ "/":application, "/backend":app }) server = wsgiserver.CherryPyWSGIServer(("127.0.0.1", 8080), d)
Bununla Django “/” üzerinde hizmet verecek ve Flask “/backend” uç noktalarında hizmet verecek.
Sonuç olarak
Django'yu Flask'a karşı incelemek veya sizi yalnızca Flask mikro çerçevesini öğrenmeye teşvik etmek yerine, Django ve Flask'ı birbirine kaynakladım, görevi Django'ya devrederek kimlik doğrulama için aynı oturum verilerini paylaşmalarını sağladım. Django, kullanıcı kaydı, oturum açma ve oturum kapatma (sadece birkaçını saymak gerekirse) çözmek için çok sayıda modülle birlikte geldiğinden, bu iki çerçeveyi birleştirmek size Flask gibi yönetilebilir bir mikro çerçeveyi hackleme fırsatı sunarken değerli zaman kazandıracak.