OAuth 2'yi Django/DRF Arka Uçunuza Delirmeden Entegre Etme
Yayınlanan: 2022-03-11Hepimiz oradaydık. API arka ucu üzerinde çalışıyorsunuz ve gidişattan memnunsunuz. Kısa süre önce uygulanabilir minimum ürünü (MVP) tamamladınız, testlerin tümü geçiyor ve bazı yeni özellikleri uygulamayı sabırsızlıkla bekliyorsunuz.
Ardından patron size bir e-posta gönderir: “Bu arada, insanların Facebook ve Google üzerinden giriş yapmasına izin vermemiz gerekiyor; bizimki gibi küçük bir site için bir hesap oluşturmak zorunda kalmamalılar.”
Harika. Kapsam kayması tekrar vurur.
İyi haber şu ki, OAuth 2, sosyal ve üçüncü taraf kimlik doğrulaması (Facebook, Google vb. hizmetler tarafından kullanılır) için endüstri standardı olarak ortaya çıktı, böylece çok çeşitli sosyal medyayı desteklemek için bu standardı anlamaya ve uygulamaya odaklanabilirsiniz. kimlik doğrulama sağlayıcıları.
OAuth 2'ye aşina değilsinizdir; Bu benim başıma geldiğinde değildim.
Bir Python geliştiricisi olarak, içgüdüleriniz sizi Python paketlerini kurmak için önerilen Python Paket Dizini (PyPA) aracı olan pip'e yönlendirebilir. Kötü haber şu ki, pip, OAuth ile ilgili 278 paket biliyor - 53'ü özellikle Django'dan bahsediyor. Sadece seçenekleri araştırmak bir haftalık çalışmaya değer, boşverin kod yazmaya başlayın.
Bu öğreticide, Python Social Auth kullanarak OAuth 2'yi Django veya Django Rest Framework'ünüze nasıl entegre edeceğinizi öğreneceksiniz. Bu makale Django REST Çerçevesine odaklansa da, burada sağlanan bilgileri, aynısını çeşitli diğer yaygın arka uç çerçevelerinde uygulamak için uygulayabilirsiniz.
OAuth 2 akışına hızlı bir genel bakış
OAuth 2, baştan bir web kimlik doğrulama protokolü olarak tasarlandı. Bu, bir net kimlik doğrulama protokolü olarak tasarlanmış gibi değildir; HTML oluşturma ve tarayıcı yönlendirmeleri gibi araçların sizin için kullanılabilir olduğunu varsayar.
Bu açıkça JSON tabanlı bir API için bir engeldir, ancak bu sorunu çözebilirsiniz.
Geleneksel, sunucu taraflı bir web sitesi yazıyormuş gibi bu süreçten geçeceksiniz.
Sunucu tarafı OAuth 2 Akışı
İlk adım tamamen uygulama akışının dışında gerçekleşir. Proje sahibi, oturum açmanız gereken her bir OAuth 2 sağlayıcısına başvurunuzu kaydettirmelidir.
Bu kayıt sırasında, OAuth 2 sağlayıcısına, başvurunuzun istekleri almak için uygun olacağı bir geri arama URI'si sağlarlar. Karşılığında, bir istemci anahtarı ve istemci sırrı alırlar. Bu belirteçler, oturum açma isteklerini doğrulamak için kimlik doğrulama işlemi sırasında değiştirilir.
Belirteçler, istemci olarak sunucu kodunuza başvurur. Ana bilgisayar, OAuth 2 sağlayıcısıdır. API'nizin müşterileri için tasarlanmamıştır.
Akış, uygulamanız "Facebook ile Giriş Yap" veya "Google+ ile Giriş Yap" gibi bir düğme içeren bir sayfa oluşturduğunda başlar. Temel olarak bunlar, her biri aşağıdaki gibi bir URL'ye işaret eden basit bağlantılardan başka bir şey değildir:
https://oauth2provider.com/auth? response_type=code& client_id=CLIENT_KEY& redirect_uri=CALLBACK_URI& scope=profile& scope=email
(Not: Okunabilirlik için yukarıdaki URI'ye eklenen satır sonları.)
İstemci anahtarınızı sağladınız ve URI'yi yeniden yönlendirdiniz, ancak sır yok. Karşılığında, sunucuya yanıt olarak bir kimlik doğrulama kodu ve hem 'profil' hem de 'e-posta' kapsamlarına erişim istediğinizi söylediniz. Bu kapsamlar, kullanıcıdan talep ettiğiniz izinleri tanımlar ve aldığınız erişim belirtecinin yetkilendirmesini sınırlar.
Alındıktan sonra, kullanıcının tarayıcısı, OAuth 2 sağlayıcısının kontrol ettiği dinamik bir sayfaya yönlendirilir. OAuth 2 sağlayıcısı, devam etmeden önce geri arama URI'sinin ve istemci anahtarının birbiriyle eşleştiğini doğrular. Bunu yaparlarsa, akış, kullanıcının oturum belirteçlerine bağlı olarak kısa süreliğine farklılaşır.
Kullanıcı o anda bu hizmette oturum açmadıysa, oturum açması istenir. Oturum açtıktan sonra, kullanıcıya, uygulamanızın oturum açmasına izin vermek için izin isteyen bir iletişim kutusu sunulur.
Kullanıcının onayladığını varsayarsak, OAuth 2 sunucusu onları sorgu parametrelerinde bir yetkilendirme kodu da dahil olmak üzere sağladığınız geri arama URI'sine yönlendirir: GET https://api.yourapp.com/oauth2/callback/?code=AUTH_CODE
.
Yetkilendirme kodu, süresi hızla dolan, tek kullanımlık bir belirteçtir; alındıktan hemen sonra sunucunuz geri dönmeli ve hem yetkilendirme kodu hem de müşteri sırrınız dahil olmak üzere OAuth 2 sağlayıcısına başka bir istek yapmalıdır:
POST https://oauth2provider.com/token/? grant_type=authorization_code& code=AUTH_CODE& redirect_uri=CALLBACK_URI& client_id=CLIENT_KEY& client_secret=CLIENT_SECRET
Bu yetkilendirme kodunun amacı, yukarıdaki POST talebinin kimliğini doğrulamaktır, ancak akışın doğası gereği, kullanıcının sistemi üzerinden yönlendirilmesi gerekir. Bu nedenle, doğası gereği güvensizdir.
Yetkilendirme kodundaki kısıtlamalar (yani, süresinin hızla dolması ve yalnızca bir kez kullanılabilmesi), güvenilmeyen bir sistemden kimlik doğrulama bilgilerinin geçirilmesine ilişkin doğal riski azaltmak için vardır.
Doğrudan sunucunuzdan OAuth 2 sağlayıcısının sunucusuna yapılan bu çağrı, OAuth 2 sunucu tarafı oturum açma işleminin temel bileşenidir. Aramayı kontrol etmek, aramanın TLS ile güvenli olduğunu bildiğiniz anlamına gelir ve böylece telefon dinleme saldırılarına karşı korunmaya yardımcı olur.
Yetkilendirme kodunun dahil edilmesi, kullanıcının açıkça onay vermesini sağlar. Kullanıcılarınız tarafından hiçbir zaman görülemeyen istemci sırrının dahil edilmesi, bu isteğin, kullanıcının sisteminde bulunan ve yetkilendirme kodunu ele geçiren bazı virüs veya kötü amaçlı yazılımlardan kaynaklanmamasını sağlar.
Her şey eşleşirse, sunucu, kullanıcı olarak kimliği doğrulanırken bu sağlayıcıya arama yapabileceğiniz bir erişim belirteci döndürür.
Sunucudan erişim jetonunu aldıktan sonra, sunucunuz kullanıcının tarayıcısını bir kez daha yeni oturum açmış kullanıcılar için açılış sayfasına yönlendirir. Erişim jetonunu kullanıcının sunucu tarafı oturum önbelleğinde tutmak yaygındır, bu nedenle sunucunun gerektiğinde belirtilen sosyal sağlayıcıya çağrı yapabilmesi.
Erişim belirteci hiçbir zaman kullanıcının kullanımına sunulmamalıdır!
Dalabileceğimiz daha fazla ayrıntı var.
Örneğin, Google, erişim belirtecinizin ömrünü uzatan bir yenileme belirteci içerirken, Facebook kısa süreli erişim belirteçlerini daha uzun ömürlü bir şeyle değiş tokuş edebileceğiniz bir uç nokta sağlar. Ancak bu ayrıntılar bizim için önemli değil çünkü bu akışı kullanmayacağız.
Bu akış, bir REST API için zahmetlidir. Ön uç istemcinin ilk oturum açma sayfasını oluşturmasını ve arka ucun bir geri arama URL'si sağlamasını sağlayabilirsiniz, ancak sonunda bir sorunla karşılaşırsınız. Erişim belirtecini aldıktan sonra kullanıcıyı ön ucun açılış sayfasına yönlendirmek istiyorsunuz ve bunu yapmanın net, RESTful bir yolu yok.
Neyse ki, bu durumda çok daha iyi çalışan başka bir OAuth 2 akışı var.
İstemci Tarafı OAuth 2 Akışı
Bu akışta, ön uç, tüm OAuth 2 sürecini yönetmekten sorumlu olur. Önemli bir istisna dışında genellikle sunucu tarafı akışına benzer - ön uçlar, kullanıcıların kontrol ettiği makinelerde yaşar, bu nedenle istemci sırrı onlara emanet edilemez. Çözüm, sürecin tüm adımını basitçe ortadan kaldırmaktır.
İlk adım, sunucu tarafı akışında olduğu gibi, uygulamanın kaydedilmesidir.
Bu durumda, proje sahibi uygulamayı yine de kaydeder, ancak bir web uygulaması olarak. OAuth 2 sağlayıcısı yine de bir istemci anahtarı sağlar, ancak herhangi bir istemci sırrı sağlamayabilir.
Ön uç, kullanıcıya, OAuth 2 sağlayıcısının kontrol ettiği bir web sayfasına yönlendiren ve uygulamamızın kullanıcı profilinin belirli yönlerine erişmesi için izin isteyen bir sosyal oturum açma düğmesi sağlar.
URL bu sefer biraz farklı görünüyor:
https://oauth2provider.com/auth? response_type=token& client_id=CLIENT_KEY& redirect_uri=CALLBACK_URI& scope=profile& scope=email
URL'deki bu sefer response_type
parametresinin token
olduğuna dikkat edin.
Peki ya yönlendirme URI'si?
Bu, erişim belirtecini uygun şekilde işlemek için hazırlanmış ön uçtaki herhangi bir adrestir.
Kullanımdaki OAuth 2 kitaplığına bağlı olarak, ön uç, kullanıcının cihazında HTTP isteklerini kabul edebilen bir sunucuyu geçici olarak çalıştırabilir; bu durumda, yönlendirme URL'si http://localhost:7862/callback/?token=TOKEN
biçimindedir.
Kullanıcı kabul ettikten sonra OAuth 2 sunucusu bir HTTP yönlendirmesi döndürdüğünden ve bu yönlendirme kullanıcının cihazındaki tarayıcı tarafından işlendiğinden, bu adres doğru şekilde yorumlanarak belirteç için ön uç erişimi sağlar.
Alternatif olarak, ön uç uygun bir sayfayı doğrudan uygulayabilir. Her iki durumda da ön uç, bu noktada sorgu parametrelerini ayrıştırmaktan ve erişim belirtecini işlemekten sorumludur.
Bu noktadan itibaren, ön uç, belirteci kullanarak doğrudan OAuth 2 sağlayıcısının API'sini arayabilir. Ancak kullanıcılar bunu gerçekten istemiyor; API'nize kimliği doğrulanmış erişim istiyorlar. Arka ucun sağlaması gereken tek şey, ön ucun bir sosyal sağlayıcının erişim jetonunu API'nize erişim sağlayan bir jetonla değiş tokuş edebileceği bir uç noktadır.
Erişim belirtecini ön uca sağlamanın, doğası gereği sunucu tarafı akışından daha az güvenli olduğu göz önüne alındığında, buna neden izin vermelisiniz?
İstemci tarafı akışı, bir arka uç REST API ile kullanıcıya yönelik bir ön uç arasında daha katı bir ayrım sağlar. Arka uç sunucunuzu yeniden yönlendirme URI'si olarak belirtmenizi kesinlikle engelleyen hiçbir şey yoktur; nihai etki bir çeşit melez akış olacaktır.

Sorun, sunucunun daha sonra kullanıcıya yönelik uygun bir sayfa oluşturması ve ardından kontrolü bir şekilde ön uca geri vermesi gerektiğidir.
Modern projelerde, ön uç kullanıcı arayüzü ile tüm iş mantığını işleyen arka uç arasındaki endişeleri kesin olarak ayırmak yaygındır. Genellikle iyi tanımlanmış bir JSON API aracılığıyla iletişim kurarlar. Yukarıda açıklanan hibrit akış, endişelerin ayrılmasını bulandırıyor, ancak arka ucu hem kullanıcıya yönelik bir sayfaya hizmet etmeye hem de bir şekilde kontrolü ön uca geri döndürmek için bir akış tasarlamaya zorluyor.
Ön ucun erişim belirtecini işlemesine izin vermek, endişelerin ayrılmasını koruyan uygun bir tekniktir. Güvenliği ihlal edilmiş bir istemciden kaynaklanan riski biraz artırır, ancak genel olarak iyi çalışır.
Bu akış, ön uç için karmaşık görünebilir ve eğer ön uç ekibinin her şeyi kendi başlarına geliştirmesini istiyorsanız. Bununla birlikte, hem Facebook hem de Google, ön ucun tüm süreci minimum bir yapılandırmayla ele alan oturum açma düğmelerini içermesini sağlayan kitaplıklar sağlar.
İşte arka uçta belirteç değişimi için bir tarif.
İstemci akışı altında, arka uç, OAuth 2 sürecinden oldukça yalıtılmıştır. Aldanmayın: Bu basit bir iş değil. En azından aşağıdaki işlevleri desteklemesini isteyeceksiniz.
- OAuth 2 sağlayıcısına en az bir istek gönderin, yalnızca ön ucun sağladığı belirtecin geçerli olduğundan emin olmak için rastgele bir dize değil.
- Belirteç geçerli olduğunda, API'niz için geçerli bir belirteç döndürün. Aksi takdirde, bilgilendirici bir hata döndürün.
- Bu yeni bir kullanıcıysa, onlar için bir
User
modeli oluşturun ve uygun şekilde doldurun. - Bu, kendisi için bir
User
modelinin zaten mevcut olduğu bir kullanıcıysa, onları e-posta adresleriyle eşleştirin, böylece sosyal oturum açma için yeni bir hesap oluşturmak yerine doğru mevcut hesaba erişsinler. - Sosyal medyada sağladıkları bilgilere göre kullanıcının profil ayrıntılarını güncelleyin.
İyi haber şu ki, tüm bu işlevleri arka uçta uygulamak beklediğinizden çok daha basit.
İşte tüm bunları arka uçta sadece iki düzine kod satırında çalıştırmanın sihri. Bu, Python Social Auth kitaplığına (bundan böyle "PSA") bağlıdır, bu nedenle gereksinimlerinize hem social-auth-core
hem de social social-auth-app-django
requirements.txt
gerekir.txt .
Ayrıca kitaplığı burada belgelendiği gibi yapılandırmanız gerekir. Bunun, netlik için bazı istisna işlemlerini hariç tuttuğunu unutmayın.
Bu örnek için tam kod burada bulunabilir.
@api_view(http_method_names=['POST']) @permission_classes([AllowAny]) @psa() def exchange_token(request, backend): serializer = SocialSerializer(data=request.data) if serializer.is_valid(raise_exception=True): # This is the key line of code: with the @psa() decorator above, # it engages the PSA machinery to perform whatever social authentication # steps are configured in your SOCIAL_AUTH_PIPELINE. At the end, it either # hands you a populated User model of whatever type you've configured in # your project, or None. user = request.backend.do_auth(serializer.validated_data['access_token']) if user: # if using some other token back-end than DRF's built-in TokenAuthentication, # you'll need to customize this to get an appropriate token object token, _ = Token.objects.get_or_create(user=user) return Response({'token': token.key}) else: return Response( {'errors': {'token': 'Invalid token'}}, status=status.HTTP_400_BAD_REQUEST, )
Ayarlarınıza girmeniz gereken biraz daha var (tam kod) ve sonra hepiniz hazırsınız:
AUTHENTICATION_BACKENDS = ( 'social_core.backends.google.GoogleOAuth2', 'social_core.backends.facebook.FacebookOAuth2', 'django.contrib.auth.backends.ModelBackend', ) for key in ['GOOGLE_OAUTH2_KEY', 'GOOGLE_OAUTH2_SECRET', 'FACEBOOK_KEY', 'FACEBOOK_SECRET']: # Use exec instead of eval here because we're not just trying to evaluate a dynamic value here; # we're setting a module attribute whose name varies. exec("SOCIAL_AUTH_{key} = os.environ.get('{key}')".format(key=key)) SOCIAL_AUTH_PIPELINE = ( 'social_core.pipeline.social_auth.social_details', 'social_core.pipeline.social_auth.social_uid', 'social_core.pipeline.social_auth.auth_allowed', 'social_core.pipeline.social_auth.social_user', 'social_core.pipeline.user.get_username', 'social_core.pipeline.social_auth.associate_by_email', 'social_core.pipeline.user.create_user', 'social_core.pipeline.social_auth.associate_user', 'social_core.pipeline.social_auth.load_extra_data', 'social_core.pipeline.user.user_details', )
urls.py
dosyanıza bu işleve bir eşleme ekleyin ve hazırsınız!
Bu sihir nasıl çalışıyor?
Python Social Auth, çok havalı, çok karmaşık bir makine parçasıdır. Birkaç düzine sosyal kimlik doğrulama sağlayıcısından herhangi birine kimlik doğrulama ve erişim sağlamaktan son derece mutludur ve Django, Flask, Pyramid, CherryPy ve WebPy dahil olmak üzere en popüler Python web çerçevelerinde çalışır.
Çoğunlukla, yukarıdaki kod çok standart bir Django REST çerçevesi (DRF) işlev tabanlı görünümdür: urls.py'nizde hangi yolla urls.py
eşleştirin ve ona bir istek gönderdiğinizi varsayarak POST isteklerini dinler. beklediği biçimde biçimlendirir, ardından size bir User
nesnesi veya None
alır.
Bir User
nesnesi alırsanız, bu, projenizde başka bir yerde yapılandırdığınız model tipindedir ve zaten var olabilir veya olmayabilir. PSA, belirteci doğrulamak, bir kullanıcı eşleşmesinin olup olmadığını belirlemek, gerekirse bir kullanıcı oluşturmak ve sosyal sağlayıcıdan kullanıcı ayrıntılarını güncellemekle zaten ilgilendi.
Bir kullanıcının sosyal sağlayıcının kullanıcısından sizinkiyle nasıl eşleştirildiğine ve mevcut kullanıcılarla nasıl ilişkilendirildiğine ilişkin tam ayrıntılar, yukarıda tanımlanan SOCIAL_AUTH_PIPELINE
tarafından belirlenir. Tüm bunların nasıl çalıştığı hakkında öğrenilecek daha çok şey var, ancak bu yazının kapsamı dışında. Bununla ilgili daha fazla bilgiyi buradan okuyabilirsiniz.
Sihrin anahtar kısmı, görünümünüze iletilen request
nesnesine bazı üyeler ekleyen, görünümdeki @psa()
dekoratörüdür. Bizim için en ilginç olanı request.backend
(PSA'ya göre arka uç, herhangi bir sosyal kimlik doğrulama sağlayıcısıdır).
Uygun arka uç bizim için seçildi ve URL'nin kendisi tarafından doldurulan görünümün backend
argümanına dayalı olarak request
nesnesine eklendi.
backend
nesnesini elinize aldığınızda, erişim kodunuz göz önüne alındığında, bu sağlayıcıya karşı kimliğinizi doğrulamaktan son derece mutlu olur; do_auth
yöntemi budur. Bu da, SOCIAL_AUTH_PIPELINE
tamamını yapılandırma dosyanızdan alır.
Boru hattı, genişletirseniz oldukça güçlü şeyler yapabilir, ancak ihtiyacınız olan her şeyi varsayılan, yerleşik işlevselliği dışında hiçbir şey olmadan zaten yapar.
Bundan sonra, normal DRF koduna geri dönülür: geçerli bir User
nesneniz varsa, uygun bir API belirtecini çok kolay bir şekilde döndürebilirsiniz. Geçerli bir User
nesnesini geri almadıysanız, bir hata oluşturmak kolaydır.
Bu tekniğin bir dezavantajı, meydana geldikleri takdirde hataları geri döndürmenin nispeten basit olmasına rağmen, özellikle neyin yanlış gittiğine dair çok fazla fikir edinmenin zor olmasıdır. PSA, sorunun ne olduğu konusunda sunucunun geri vermiş olabileceği tüm ayrıntıları yutar.
Yine, hata kaynakları konusunda oldukça şeffaf olmak iyi tasarlanmış kimlik doğrulama sistemlerinin doğasında vardır. Bir uygulama, bir oturum açma denemesinden sonra bir kullanıcıya “Geçersiz Parola” söylerse, bu “Tebrikler! Geçerli bir kullanıcı adı tahmin ettiniz."
Neden sadece kendin yuvarlamıyorsun?
Tek kelimeyle: genişletilebilirlik. Çok az sayıda sosyal OAuth 2 sağlayıcısı, API çağrılarında tam olarak aynı bilgileri tam olarak aynı şekilde ister veya döndürür. Yine de her türlü özel durum ve istisna vardır.
Zaten bir PSA kurduktan sonra yeni bir sosyal sağlayıcı eklemek, ayar dosyalarınızda birkaç satırlık yapılandırma meselesidir. Herhangi bir kod ayarlamanız gerekmez. PSA tüm bunları özetler, böylece kendi uygulamanıza odaklanabilirsiniz.
Bunu nasıl test ederim?
İyi soru! unittest.mock
, bir kitaplığın derinliklerinde bir soyutlama katmanının altına gömülü API çağrılarını taklit etmek için pek uygun değildir; sadece alay etmenin kesin yolunu keşfetmek büyük çaba gerektirecektir.
Bunun yerine, PSA İstekler kitaplığının üzerine kurulduğundan, HTTP düzeyinde sağlayıcılarla alay etmek için mükemmel Yanıt kitaplığını kullanırsınız.
Testlerle ilgili tam bir tartışma bu makalenin kapsamı dışındadır, ancak testlerimizin bir örneği burada yer almaktadır. Dikkat edilmesi gereken belirli işlevler, mocked
bağlam yöneticisi ve SocialAuthTests
sınıfıdır.
Bırakın ağır işi PSA yapsın.
OAuth2 süreci, pek çok doğal karmaşıklıkla ayrıntılı ve karmaşıktır. Neyse ki, bu karmaşıklığı mümkün olduğunca acısız bir şekilde ele almaya adanmış bir kütüphane getirerek bu karmaşıklığın çoğunu atlamak mümkündür.
Python Social Auth bu konuda harika bir iş çıkarıyor. Yalnızca 25 satır kodda sorunsuz kullanıcı oluşturma ve eşleştirme elde etmek için istemci tarafı, örtük, OAuth2 akışını kullanan bir Django/DRF görünümü gösterdik. Bu çok perişan değil.