In this article, I want to customize Django authentication without creating any user model...
Step 1.
Customize your own authentication middleware.
auth.middleware.py
from django.conf import settings from auth.backend import CamelotBackend from auth.user import AnonymousUser class CamelotMiddleware: def __init__(self, get_response): self.get_response = get_response # One-time configuration and initialization. def __call__(self, request): # Code to be executed for each request before # the view (and later middleware) are called. self.process_request(request) response = self.get_response(request) # Code to be executed for each request/response after # the view is called. return response def process_request(self, request): assert hasattr(request, 'session'), ( "The Django authentication middleware requires session middleware " "to be installed. Edit your MIDDLEWARE%s setting to insert " "'django.contrib.sessions.middleware.SessionMiddleware' before " "'auth.middleware.CamelotMiddleware'.") % ( "_CLASSES" if settings.MIDDLEWARE is None else "") token = request.session.get('oauth_token') if token: access_token = token.get('access_token') request.user = CamelotBackend.authenticate(request=request, token=access_token) else: request.user = AnonymousUser() return NoneStep 2.
Customize your own authentication backend.
For user authentication, you need to write your own backend class and implementing the methods for authenticate and get_user
auth.backend.py
class CustomizedAuthBackend: @staticmethod def authenticate(request, token=None): if token: return CamelotBackend.get_user(request=request, token=token) else: return AnonymousUser() @staticmethod def get_user(request, token=None): if token: camelot_oauth = OAuthHelper() try: user_info = camelot_oauth.get_user_info_by_oauth_token( oauth_token=token) return OAuthUser( account_id=user_info.get('account_id'), username=user_info.get('first_name'), email=user_info.get('email')) except (BusinessException, Exception): # clear session try: del request.session['oauth_token'] except ( KeyError, Exception, ): return AnonymousUser() else: return AnonymousUser() @staticmethod def logout(request): """ Remove the authenticated user's ID from the request and flush their session data. """ # Dispatch the signal before the user is logged out so the receivers have a # chance to find out *who* logged out. user = getattr(request, 'user', None) if not getattr(user, 'is_authenticated', True): user = None request.session.flush() if hasattr(request, 'user'): from auth.user import AnonymousUser request.user = AnonymousUser()Step 3.
Replace the original authentication model.
auth.user.py
class OAuthUser(object): _username = str() _account_id = str() _email = str() def __init__(self, account_id, username, email): self._account_id = account_id self._username = username self._email = email def __eq__(self, other): return isinstance(other, self.__class__) @property def is_anonymous(self): return False @property def is_authenticated(self): return True def get_username(self): return self._username class AnonymousUser: _username = str() def __str__(self): return 'AnonymousUser' def __hash__(self): return 1 # instances always return the same hash value @property def is_anonymous(self): return True @property def is_authenticated(self): return False def get_username(self): return self._usernameStep 4.
Write your own decorator to check if the user is logged in.
auth.decorators.py
from functools import wraps from django.conf import settings from django.shortcuts import redirect, reverse def member_required(func): @wraps(func) def decorator(request, *args, **kwargs): assert hasattr(request, 'user'), ( "The Django authentication middleware requires session middleware " "to be installed. Edit your MIDDLEWARE%s setting to insert " "'auth.middleware.CamelotMiddleware'") % ( "_CLASSES" if settings.MIDDLEWARE is None else "") if request.user and request.user.is_authenticated: return func(request, *args, **kwargs) else: return redirect(reverse('storefront:login')) return decoratorStep 5.
Configure the settings.py
INSTALLED_APPS = [ 'myapp.MyAppConfig', # remove the django.contrib.auth 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', # your customized middleware 'auth.middleware.CamelotMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # remember to replaced it by your own user model AUTH_USER_MODEL = 'auth.user.OAuthUser' # rember to replaced it by your own authentication backend AUTHENTICATION_BACKENDS = ['auth.backend.CamelotBackend'] SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"Step 6.
Check your member in views.py.
Implement the OAuth client by "requests-oauthlib"
from requests_oauthlib import OAuth2Session from auth.decorators import member_required @member_required def refresh_token(request): my_auth = OAuth2Session(client_id, token=request.session['oauth_token']) token = my_auth.refresh_token(token_url=token_url) request.session['oauth_token'] = token return JsonResponse(token)
Source code of the views.py
from Camelot import settings from django.http import JsonResponse from django.shortcuts import redirect, reverse from requests_oauthlib import OAuth2Session from auth.backend import CamelotBackend from apps.utility.helpers.profile_helper import OAuthHelper from auth.decorators import member_required client_id = settings.env.str('OAUTH_CLIENT_ID') client_secret = settings.env.str('OAUTH_CLIENT_SECRET_KEY') authorization_login_url = settings.env.str('OAUTH_AUTHORIZATION_URL') token_url = settings.env.str('OAUTH_TOKEN_URL') authorization_logout_url = settings.env.str('OAUTH_TOKEN_REVOKE') redirect_path = 'callback' def login(request): redirect_url = f'{request.scheme}://{request.get_host()}/{redirect_path}' myzyxel = OAuth2Session(client_id, redirect_uri=redirect_url) authorization_url, state = myzyxel.authorization_url( authorization_login_url) request.session['oauth_state'] = state return redirect(authorization_url) def callback(request): """ Step 3: Retrieving an access token. The user has been redirected back from the provider to your registered callback URL. With this redirection comes an authorization code included in the redirect URL. We will use that to obtain an access token. """ redirect_url = f'{request.scheme}://{request.get_host()}/{redirect_path}' myzyxel = OAuth2Session(client_id, state=request.session['oauth_state'], redirect_uri=redirect_url) token = myzyxel.fetch_token(token_url, client_secret=client_secret, authorization_response=request.get_raw_uri(), include_client_id=True) # At this point you can fetch protected resources but lets save # the token and show how this is done from a persisted token # in /profile. request.session['oauth_token'] = token return redirect(reverse('storefront:profile')) @member_required def profile(request): """Fetching a protected resource using an OAuth 2 token. """ access_token = request.session.get('oauth_token').get('access_token') camelot_oauth = OAuthHelper() user_info = camelot_oauth.get_user_info_by_oauth_token( oauth_token=access_token) return JsonResponse(user_info) @member_required def refresh_token(request): myzyxel = OAuth2Session(client_id, token=request.session['oauth_token']) token = myzyxel.refresh_token(token_url=token_url) request.session['oauth_token'] = token return JsonResponse(token) def logout(request): redirect_url = f'{request.scheme}://{request.get_host()}/' current_token = request.session.get('oauth_token') logout_url = f"{authorization_logout_url}" if current_token: access_token = current_token.get('access_token') logout_url = f"{authorization_logout_url}?access_token={access_token}&logout_redirect_uri={redirect_url}" try: del request.session['oauth_token'] except ( KeyError, Exception, ): pass CamelotBackend.logout(request=request) return redirect(logout_url)All installed packages
asgiref==3.2.7 Django==3.0.5 pytz==2019.3 sqlparse==0.3.1 django-environ==0.4.5 django-sslserver==0.22 requests==2.23.0 requests-oauthlib==1.3.0 oauthlib==3.1.0
沒有留言:
張貼留言