Django中的电子邮件+社交登录 – 一步一步的指南
这篇文章围绕着一个Django项目。它包括在任何Django项目中整合电子邮件和社会化登录。我们使用React作为前端来演示项目的工作。你也可以使用你自己的前端来实现。
需求–
1.我们需要注册一个用户,我们需要传递用户名,电子邮件,名字,密码(因为我们的模型)。
2.我们需要登录该用户并对其进行认证。
3.用户应该能够用多个社交平台登录。
4.我们需要为所有的要求制作API。
认证将如何发生。
在开始之前,我们需要知道,我们将通过在请求的授权头中发送一个Bearer令牌来发送认证请求。这个令牌将告诉服务器是哪个用户发送的请求。为此,我们要做以下工作。
1.我们将发送用户名和密码到一个端点,以获得令牌,这将作为我们的登录。
2.作为回报,我们将得到访问令牌和刷新令牌,我们将把所有请求的授权头设置为这个访问令牌。
3.然而,访问令牌将在短时间内过期,然后我们将向终端发送刷新令牌以获得新的访问令牌。
4.因此,我们将在内部重复步骤2和3,而不让用户知道访问令牌何时过期
第1步:在Django中创建自定义用户模型
在你创建了一个演示项目之后。创建一个应用程序账户。
python manage.py startapp accounts
然后你可以看到一个新的文件夹被创建了,名字是accounts,现在让我们把它添加到settings.py中的INSTALLED_APPS。所以它应该看起来像这样。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# add this
'accounts',
]
在models.py中,让我们创建模型Account和它的Manager,同时导入这些模型。
from django.db import models
from django.contrib.auth.models import AbstractBaseUser,PermissionsMixin,BaseUserManager
from django.utils.translation import gettext_lazy as _
class Account(AbstractBaseUser,PermissionsMixin):
email=models.EmailField(unique=True)
username= models.CharField(_('User Name'),max_length=150)
first_name = models.CharField(_('First Name'),max_length=150)
last_name = models.CharField(_('last Name'),max_length=150)
is_staff=models.BooleanField(default=False)
is_active=models.BooleanField(default=True)
objects=CustomAccountManager()
USERNAME_FIELD='email'
REQUIRED_FIELDS=['username','first_name']
def __str__(self):
return self.email
- 这里我们使用电子邮件作为默认的USERNAME_FIELD,我们希望用户名和名字作为必填字段(你也可以有任何其他字段,但用户名字段应该在那里
- is_active默认为True,is_staff默认为False。
- 为了创建对象(用户),我们需要一个自定义管理器(_我们正在创建这个管理器)。
- 写 “用户名”,而不是user_name或任何其他样式,因为facebook或google登录会返回用户名。
- 使用gettextlazy是可选的。
class CustomAccountManager(BaseUserManager):
def create_user(self,email,username,first_name,password,**other_fields):
if not email:
raise ValueError(_('Please provide an email address'))
email=self.normalize_email(email)
user=self.model(email=email,username=username,first_name=first_name,**other_fields)
user.set_password(password)
user.save()
return user
def create_superuser(self,email,username,first_name,password,**other_fields):
other_fields.setdefault('is_staff',True)
other_fields.setdefault('is_superuser',True)
other_fields.setdefault('is_active',True)
if other_fields.get('is_staff') is not True:
raise ValueError(_('Please assign is_staff=True for superuser'))
if other_fields.get('is_superuser') is not True:
raise ValueError(_('Please assign is_superuser=True for superuser'))
return self.create_user(email,username,first_name,password,**other_fields)
这里create_user将创建普通用户,而create_superuser是创建超级用户(管理员)。
create_user
- 为了创建用户,我们需要传递电子邮件、用户名、名字和密码以及其他字段。
- 如果没有电子邮件,则引发错误,否则将电子邮件正常化 ( 请阅读这里 )
- 然后创建一个具有电子邮件、用户名和其他字段的模型账户对象
- 然后设置密码,set_password实际上是将密码设置为模型对象中的散列密码,所以没有人可以看到实际的密码。
- 然后保存用户对象并将其返回
create_superuser
- is_staff, is_superuser, is_active默认应设置为True
- 如果没有设置为 “真 “或传递为 “假”,则引发错误,否则就用这个值和其他字段创建用户。
现在在settings.py中添加这个,以使用我们的自定义用户模型。
AUTH_USER_MODEL='accounts.Account'
然后我们需要将这个模型迁移到数据库中。
python manage.py makemigrations
python manage.py migrate
第2步:制作用于认证的REST API端点
为此,我们需要先安装一些库,下面我将解释为什么我们需要这些库。
pip install djangorestframework
pip install django-cors-headers
pip install drf_social_oauth2
- djangorestframework是用于REST API端点的。
- django-cors -headers是必需的,这样我们的React应用才能与django服务器通信。
- drf_social_oauth2 – 这是一个主要的库,使我们能够对电子邮件密码以及谷歌和Facebook进行基于oauth2令牌的认证。
现在,在settings.py中,我们应该把这些添加到INSTALLED_APPS中,以使我们的应用程序按预期工作。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# add these
'rest_framework',
'oauth2_provider',
'social_django',
'drf_social_oauth2',
'corsheaders',
# LOCAL
'accounts',
]
让我们也把这些配置添加到settings.py中,我将在下面解释。
AUTHENTICATION_BACKENDS = (
'drf_social_oauth2.backends.DjangoOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'drf_social_oauth2.authentication.SocialAuthentication',
)
}
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000"
]
- AUTHENTICATION_BACKENDS – 这样我们就可以用OAuth2(基于令牌)或基本认证(无令牌)进行认证。
- REST_FRAMEWORK端点请求只能使用令牌进行认证
- CORS_ALLOWED_ORIGINS将是我们前端的地址(这里是反应网站的地址)。
好了,现在让我们为MIDDLEWARE和TEMPLATES添加一些配置。
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# add these
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# add these
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
]
这些是社会登录和密码所要求的。
让我们在django项目的urls.py中添加一些URL,因为这些URL是rest框架和oauth都需要的。
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
# add these
path('api-auth/', include('rest_framework.urls')),
path('api-auth/', include('drf_social_oauth2.urls',namespace='drf')),
]
在按要求添加这个和那个之后,让我们再次开始编码。
为了注册/登记一个用户,我们需要写一个序列化器和视图。注意,在注册后,用户需要自己登录,这不会自动发生。然而,我也已经涵盖了这一点。因此,用户可以注册,我们的后端会登录该用户!!。
让我们来创建序列化器,我已经在 accounts 文件夹中创建了一个序列化器.py ,所以现在写这个代码。
from rest_framework import serializers
from .models import Account
class RegistrationSerializer(serializers.ModelSerializer):
class Meta:
model=Account
fields=('email','username','password','first_name')
extra_kwargs={'password':{'write_only':True}}
def create(self,validated_data):
password=validated_data.pop('password',None)
instance=self.Meta.model(**validated_data)
if password is not None:
instance.set_password(password)
instance.save()
return instance
在RegistrationSerializer里面:
- 类Meta有什么模型,字段将被序列化,密码不能被读取,只能被写入(对于表单)。
- 创建方法将创建新的账户实例,并将用我们从视图中传递的有效数据来填充它。
- 我们需要该密码,如果该密码不是无,我们可以设置散列的密码,这样我们就可以从验证的数据中跳出来。
- 然后我们将保存实例并返回它
现在我们将编写创建用户的视图。
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework import status,generics
from rest_framework.response import Response
from .serializers import RegistrationSerializer
from rest_framework import permissions
from .models import Account
class CreateAccount(APIView):
permission_classes=[permissions.AllowAny]
def post(self,request):
reg_serializer=RegistrationSerializer(data=request.data)
if reg_serializer.is_valid():
new_user=reg_serializer.save()
if new_user:
return Response(status=status.HTTP_201_CREATED)
return Response(reg_serializer.errors,status=status.HTTP_400_BAD_REQUEST)
- 我们导入必要的,允许任何人访问这个视图,并且只允许向这个视图发布请求。
- 当post request发生时,我们将用request.data初始化RegistrationSerializer,如果数据是有效的,则保存它,否则返回错误。
- 注意:我们将在以后保存序列化数据后重新访问这个视图以登录用户。
同时在账户目录下创建一个urls.py,并写上以下内容
from django.urls import path
from .views import CreateAccount
app_name = 'users'
urlpatterns = [
path('create/', CreateAccount.as_view(), name="create_user"),]
最后在项目的urls.py中加入这个网址
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
path('api-auth/', include('drf_social_oauth2.urls',namespace='drf')),
#add this
path('api-auth/', include('accounts.urls'))
]
现在,对于登录,我们不需要写任何额外的代码,因为登录只是从服务器获取访问令牌,所有这些都已经由drf-social-oauth2处理了。我们甚至不需要为此写任何端点!
我们需要创建一个应用程序,并获得client_id和client_secret以获得访问令牌,所以让我们访问运行在http://127.0.0.1:8000/admin/ 上的管理员,你应该看到像这样的东西。
Admin
在这里你可以看到应用程序,按其中的添加按钮并创建一个新的应用程序。不要碰客户ID和客户秘密,选择用户为你的管理员超级用户,客户类型将是保密的,授权授予类型将是基于资源所有者密码的,然后保存它。
就这样,我们已经准备好检查我们的应用程序了,所以让我们创建一个超级用户并传递所需的值,然后在虚拟环境中运行服务器。
python manage.py runserver
第3步 : 创建并使用电子邮件密码登录用户
现在打开Postman,写下这个URL,并在正文中写下这个,然后发送请求!
邮递员请求创建用户
你应该得到这样的空白回复
状态201创建
因此,用户被创建了,所以它工作得很好!
在发送令牌请求时,我们将传递client_id、client_secret、电子邮件作为用户名、密码和grant_type=password,让服务器知道这是为了获得新的访问令牌和刷新令牌。
现在我们可以发送token的请求,所以在postman中发送这个请求到http://127.0.0.1:8000/api-auth/token 。
要求返回代币
注意,我们在请求中把电子邮件传递给用户名字段,因为传递用户名是强制性的。
- 它应该返回访问令牌和刷新令牌以及200 OK的状态。
- 这个access_token可以被设置在请求的授权头信息中,以发送认证请求(我们将用react来做这个)。
记得我说过,我们将在保存序列化数据后重新访问CreateAccount视图来登录用户,让我们这样做吧
import requests # add this
class CreateAccount(APIView):
permission_classes=[permissions.AllowAny]
def post(self,request):
reg_serializer=RegistrationSerializer(data=request.data)
if reg_serializer.is_valid():
new_user=reg_serializer.save()
if new_user:
#add these
r=requests.post('http://127.0.0.1:8000/api-auth/token', data = {
'username':new_user.email,
'password':request.data['password'],
'client_id':'Your Client ID',
'client_secret':'Your Client Secret',
'grant_type':'password'
})
return Response(r.json(),status=status.HTTP_201_CREATED)
return Response(reg_serializer.errors,status=status.HTTP_400_BAD_REQUEST)
因此,在保存新用户后,我们将发送一个post请求,以获得令牌,并将其作为Response发送回来。这将在用户注册后自动登录!
谷歌和Facebook的认证需要一个前端来完成演示,所以我将用react向你展示,然而,如果你知道该怎么做,任何前端都可以做!(我会在必要的步骤后做)。(我会在必要的步骤后做)。
第4步:认证的请求示范准备
我们将创建一个不同的序列化器,它将通过两个不同的视图返回关于用户和当前用户的信息,一个是认证的请求,另一个是非认证的请求。
所以序列化器将看起来像这样。
class UsersSerializer(serializers.ModelSerializer):
class Meta:
model=Account
fields=('email','username','first_name')
我们将写下这两个观点。
- AllUsers返回所有用户,任何一个人都可以查看数据。
- 当前用户(CurrentUser),只返回当前用户,并且只允许经过验证的请求。
from rest_framework import status,generics
class AllUsers(generics.ListAPIView):
permission_classes=[permissions.AllowAny]
queryset=Account.objects.all()
serializer_class=UsersSerializer
class CurrentUser(APIView):
permission_classes = (permissions.IsAuthenticated,)
def get(self, request):
serializer = UsersSerializer(self.request.user)
return Response(serializer.data)
urls.py将看起来像这样。
from django.urls import path
from .views import CreateAccount,AllUsers,CurrentUser
app_name = 'users'
urlpatterns = [
path('create/', CreateAccount.as_view(), name="create_user"),
path('all/', AllUsers.as_view(), name="all"),
path('currentUser/', CurrentUser.as_view(), name="current"),
]
第5步:认证的请求演示
因此,让我们先发送非认证请求,它返回这个响应(注意,授权头中没有任何内容)。
现在让我们来获取我们之前在步骤3中收到的访问令牌
- 从下拉菜单中设置授权类型为承载令牌
- 发送一个经过验证的请求给http://127.0.0.1:8000/api-auth/currentUser/
它将返回当前的用户,现在如果你偶然发送了没有授权头的请求,它将会像这样返回
现在,如果访问令牌过期了怎么办?
为此,我们需要向同一个端点发送刷新令牌以获得新的访问和刷新令牌。
让我告诉你怎么做!因此,我们向/token发送请求,其中包括client_id、client_secret、grant_type = refresh_token(以便服务器理解请求有一个刷新令牌,然后将旧的访问和刷新令牌转换为新的访问和刷新令牌),刷新令牌将是我们在第三步收到的令牌。
它返回新的令牌作为响应_你可以使用这个新的访问令牌作为承载令牌来认证请求
第6步:Facebook和谷歌登录
在settings.py中添加这些内容,为了获得facebook和google所需的密钥,你需要访问这里的fb和这里的google,并在那里执行必要的步骤。
AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.facebook.FacebookAppOAuth2',
'social_core.backends.facebook.FacebookOAuth2',
'drf_social_oauth2.backends.DjangoOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
# Facebook configuration
SOCIAL_AUTH_FACEBOOK_KEY = 'your facebook key'
SOCIAL_AUTH_FACEBOOK_SECRET = 'your facebook secret'
# Define SOCIAL_AUTH_FACEBOOK_SCOPE to get extra permissions from Facebook.
# Email is not sent by default, to get it, you must request the email permission.
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email'
}
SOCIAL_AUTH_USER_FIELDS=['email','first_name','username','password']
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = "your google oauth2 key"
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = "your google oauth2 secret"
# Define SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE to get extra permissions from Google.
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
]
第7步 :使用React JS的谷歌和Facebook登录
在使用本部分教程之前,请查看React JS教程。在你制作了一个react App之后,请执行以下步骤
一旦你有了密钥,认证就非常容易了,现在我们需要安装axios,react-facebook-login,react-google-login。
然后创建一个登录组件,在那里我们呈现这些Fb和google的登录按钮,这些按钮将接受所需的密钥,每个用户在试图用这些按钮登录时,将返回一个访问令牌以及用户的信息。
import ReactFacebookLogin from "react-facebook-login";
import ReactGoogleLogin from "react-google-login";
import { facebookLogin, googleLogin } from "../axios";# I'll create this later
export default function LogIn() {
function responseFb(response) {
console.log(response);
facebookLogin(response.accessToken);
}
function responseGoogle(response) {
console.log(response);
googleLogin(response.accessToken);
}
return (
<>
<ReactFacebookLogin
appId="Your App Id"
fields="name,email"
callback={responseFb}
/>
<ReactGoogleLogin
clientId="your google client id"
buttonText="Login"
onSuccess={responseGoogle}
onFailure={responseGoogle}
cookiePolicy={"single_host_origin"}
/>
</>
);
}
然而,我们的服务器没有线索,我们已经登录了一个用户,因为这个访问令牌是从谷歌,Facebook返回,而不是从我们的服务器。
那么,如何让我们的服务器知道?发送这个令牌给我们的服务器或者将这个令牌设置为我们的授权承载令牌?在这种情况下,我们不能用我们的账户模型创建一个账户对象,或作为当前用户访问任何账户对象,因此我们的用户在技术上不会被注册或连接。
因此,我们需要将这个令牌转换为访问令牌,从我们的服务器上获得刷新令牌,(注册我们的用户),为此我创建了一个axios.js文件,在那里我放了这个代码。
export function facebookLogin(accessToken) {
axios
.post(`http://127.0.0.1:8000/api-auth/convert-token`, {
token: accessToken,
backend: "facebook",
grant_type: "convert_token",
client_id: "your client id",
client_secret:"your client secret ",
})
.then((res) => {
// Save somewhere these access and refresh tokens
console.log(res.data);
});
}
export function googleLogin(accessToken) {
axios
.post(`http://127.0.0.1:8000/api-auth/convert-token`, {
token: accessToken,
backend: "google-oauth2",
grant_type: "convert_token",
client_id: "your client id",
client_secret: "your client secret",
})
.then((res) => {
// Save somewhere these access and refresh tokens
console.log(res.data);
});
}
我们在这里所做的是。
- 向http://127.0.0.1:8000/api-auth/convert-token 发出一个帖子请求,以转换令牌
- 后端将是你获得访问令牌的后端,以进行转换
-
grant_type将是convert_token,让服务器知道我们希望这个令牌被转换为我们服务器的访问令牌。
- client_id和client_secret将与之前的步骤一样
那么,如果访问令牌过期,如何刷新令牌呢?我们想让它自动发生,而不让用户感到困扰。
- 为此,我们将使用axios拦截器,它将返回响应,但如果有错误,它将处理这些错误。
- 如果错误状态是401(未经授权的请求),那么如果本地存储中没有刷新令牌,那么它将要求用户登录,如果存在刷新令牌,那么它将把它发送到http://127.0.0.1:8000/api-auth/token,以获得新的访问和刷新令牌。
- 请注意,作为一个例子,我使用本地存储来存储访问令牌,但你应该使用安全的方法,可能是Web Cookies(安全,仅HttpOnly,同一站点)。
- 窗口需要重新加载,以便让反应器获取网站用最新的访问令牌进行认证。
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
const originalRequest = error;
console.log(originalRequest);
if (typeof error.response === "undefined") {
alert("a server error happNeD, we will fix it shortly");
return Promise.reject(error);
}
if (
error.response.status === 401 &&
!localStorage.getItem("refresh_token")
) {
window.location.href = "/login/";
return Promise.reject(error);
}
if (
error.response.status === 401 &&
error.response.statusText === "Unauthorized" &&
localStorage.getItem("refresh_token") !== undefined
) {
const refreshToken = localStorage.getItem("refresh_token");
return axios
.post("http://127.0.0.1:8000/api-auth/token", {
client_id: "Your client id ",
client_secret:
"Your client secret",
grant_type: "refresh_token",
refresh_token: refreshToken,
})
.then((response) => {
localStorage.setItem("access_token", response.data.access_token);
localStorage.setItem("refresh_token", response.data.refresh_token);
window.location.reload();
axiosInstance.defaults.headers["Authorization"] =
"Bearer " + response.data.access_token;
})
.catch((err) => console.log(err));
}
}
);
这样我们就可以使用社交登录从Django和React认证用户。