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 None
Step 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._username
Step 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 decorator
Step 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
沒有留言:
張貼留言