احراز هویت Google OAuth2 را در چارچوب django-rest ادغام کنید

پس زمینه ها
- جداسازی front-end و back-end، django-rest-framework به عنوان back-end، به عنوان front-end واکنش نشان می دهد. این مقاله در مورد سایر فریم ورک های فرانت اند نیز کاربرد دارد.
- برنامه دارای مدل حساب و تولید توکن خاص خود است، سپس باید با احراز هویت google oauth2 ادغام شود.
ایده هایی برای اجرا
// these two created in back-end
// short as access_url
google_auth_access_url = "https://localhost:8000/google/access/";
// short as callback_url
google_auth_callback_url = "https://localhost:8000/google/callback/";
// these two created in front-end
// short as google_success_page
google_auth_success_page = "https://localhost:5173/google-success";
// short as google_fail_url
google_auth_fail_page = "https://localhost:5173/google-fail";
-
access_url را که در back-end در مرورگر نوشته شده است باز کنید.
window.open("http://localhost:8000/google/access");
-
در بکاند، هنگام دسترسی به access_url، URL احراز هویت گوگل تولید میشود و به سرور google oauth2 هدایت میشود، همچنین باید callback_url را به سرور google oauth2 منتقل کنید، callback_url برای رسیدگی به پاسخ از سرور google oauth2 است.
-
در callback_url، اگر اعتبارنامه ها با موفقیت بازیابی شدند، به google_success_page ایجاد شده در frontend تغییر مسیر دهید، در غیر این صورت به google_fail_url نیز که در frontend ایجاد شده است، هدایت شوید.
-
در google_success_page، توکن ارسال شده توسط query_params مجدداً اعتبارسنجی میشود و اگر توکن نامعتبر باشد، به google_auth_fail_page هدایت میشود.
پیوندها
کد برای مرجع
pip install google-auth-oauthlib
https://pypi.org/project/google-auth-oauthlib/#description
برای روشن تر شدن موضوع، واردات و متغیرهای رایج مورد استفاده در زیر را چسبانده ام.
import google_auth_oauthlib.flow
from django.shortcuts import redirect
from django.conf import settings
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status, serializers
import requests
import os
import jwt
from datetime import datetime
from account.views import login_token_logic
from account.serializers import UserSerializer
from account.models import User
from rest_framework.exceptions import ValidationError
from django.http import HttpResponseRedirect
from urllib.parse import urlencode
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"
CLIENT_SECRETS_FILE = "client_secret.json"
SCOPES = [
"openid",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive.metadata.readonly",
]
GOOGLE_USER_INFO_URL = "https://www.googleapis.com/oauth2/v3/userinfo"
domain = settings.BASE_BACKEND_URL
API_URI = "account/google/callback"
callback_uri = f"{domain}{API_URI}"
اجرای نمای دسترسی که به سرور Google oauth2 هدایت می شود.
این access_view برای access_url “http://localhost:8000/google/access” است.
وقتی روی «ورود به سیستم با google» در برنامه frontend کلیک میکنید، access_url را در مرورگر باز میکنید، سپس به url احراز هویت Google ایجاد شده در access_view هدایت میشود.
@api_view(["get"])
def access_view(request):
try:
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE,
scopes=SCOPES,
)
flow.redirect_uri = callback_uri
authorization_url, state = flow.authorization_url(
access_type="offline",
include_granted_scopes="true",
prompt="consent",
)
request.session["state"] = state
return redirect(authorization_url)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
اجرای نمای برگشت به تماس، مدیریت پاسخ سرور google oauth2.
چند مرحله کلیدی در نمای برگشت به تماس وجود دارد:
پارامترهای پرس و جو را بررسی کنید
class InputSerializer(serializers.Serializer):
code = serializers.CharField(required=False)
error = serializers.CharField(required=False)
state = serializers.CharField(required=False)
input_serializer = InputSerializer(data=request.query_params)
input_serializer.is_valid(raise_exception=True)
validated_data = input_serializer.validated_data
code = validated_data.get("code")
error = validated_data.get("error")
state = validated_data.get("state")
if error is not None:
return Response({"message": error}, status=status.HTTP_400_BAD_REQUEST)
if code is None or state is None:
return Response(
{"message": "Missing code or state"}, status=status.HTTP_400_BAD_REQUEST
)
state = request.session.get("state")
if not state:
return Response(
{"message": "not valid request"}, status=status.HTTP_400_BAD_REQUEST
)
واکشی توکن و دریافت اعتبار
autorization_response ur کاملی است که پاسخ oauth2 شامل پارامترهای پرس و جو می باشد
def credentials_to_dict(credentials):
return {
"token": credentials.token,
"refresh_token": credentials.refresh_token,
"id_token": credentials.id_token,
"token_uri": credentials.token_uri,
"client_id": credentials.client_id,
"client_secret": credentials.client_secret,
"scopes": credentials.scopes,
}
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
CLIENT_SECRETS_FILE, scopes=SCOPES, state=state
)
flow.redirect_uri = callback_uri
authorization_response = request.build_absolute_uri(request.get_full_path())
flow.fetch_token(authorization_response=authorization_response)
credentials = flow.credentials
credentials_dict = credentials_to_dict(credentials)
id_token را رمزگشایی کنید تا زمان منقضی شده را دریافت کنید و اطلاعات حساب گوگل را جستجو کنید
def get_user_info(token):
response = requests.get(GOOGLE_USER_INFO_URL, params={"access_token": token})
if not response.ok:
raise Exception("Failed to obtain user info from Google")
return response.json()
def decode_id_token(id_token):
decoded_tokem = jwt.decode(jwt=id_token, options={"verify_signature": False})
return decoded_tokem
user_info = get_user_info(credentials.token)
user_email = user_info["email"]
id_token_decoded = decode_id_token(credentials.id_token)
exp = id_token_decoded["exp"]
حساب Google را با مدل حساب خود برنامه ادغام کنید، باید این را مطابق با منطق ورود خود تغییر دهید.
ببینید آیا این حساب Google قبلاً در مدل حساب من وجود دارد یا خیر، اگر نه، یکی ایجاد کنید. سپس منطق ورود را انجام دهید، توکن تولید کنید.
now = int(datetime.now().timestamp())
user = User.objects.filter(email=user_email).first()
if user is None:
# create user
create_user_data = {
"email": user_email,
"image": user_info["picture"],
"type": "google",
"password": "",
}
create_serializer = UserSerializer(data=create_user_data)
if create_serializer.is_valid():
user = create_serializer.save()
else:
return Response(
{
"message": "create user from google failed",
**create_serializer.errors,
},
status=status.HTTP_400_BAD_REQUEST,
)
"""
login logic
"""
token = login_token_logic(user, valid_seconds=exp - now)
تغییر مسیر به صفحه جلویی
من دو صفحه در قسمت جلویی ایجاد می کنم، یکی برای ورود موفقیت آمیز به گوگل و دیگری برای شکست.
موفقیت: “http://localhost:5173/google-success/?token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”
شکست: “http://localhost:5173/google-fail/?message=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx”
صفحه موفقیت، رمز را مجدداً تأیید می کند، اگر توکن معتبر نباشد، به صفحه شکست هدایت می شود.
باز هم، این مرحله کاملاً به طراحی شما بستگی دارد.
GOOGLE_OAUTH2_REDIRECT_SUCCESS_URL = "http://localhost:5173/google-success/"
GOOGLE_OAUTH2_REDIRECT_FAIL_URL = "http://localhost:5173/google-fail/"
query_params = urlencode(
{
"token": token,
}
)
url = f"{settings.GOOGLE_OAUTH2_REDIRECT_SUCCESS_URL}?{query_params}"
return HttpResponseRedirect(url)
except Exception as e:
query_params = urlencode({"message": str(e)})
url = f"{settings.GOOGLE_OAUTH2_REDIRECT_FAIL_URL}?{query_params}"
return HttpResponseRedirect(url)
نکات
- مسئله:
(insecure_transport) OAuth 2 MUST utilize https.
راه حل:
os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"