如何使用Djoser和Django的React社会认证

499 阅读12分钟

使用Djoser和Django进行React社交认证

Djoser是一个强大的认证库。它提供了注册、账户激活、登录、密码重置和注销等功能。

简介

Djoser提供了社交认证,这将是本文的主要焦点。我们将用Django后端和React前端创建一个处理谷歌社交认证的应用程序。

前提条件

读者应该具备这些先决条件才能跟上本文的进度。

  1. 在你的机器上安装了Python。
  2. 安装了django,djangorestframework,djangorestframework-simplejwt,djoser,social-auth-app-django,django-cors-headers 。你可以使用pip 来安装它们。
  3. 为我们的前端部分安装好npm

开始使用

首先创建一个新的目录,然后在这个目录下运行django-admin startapp backend ,创建一个新的Django应用程序,名为backend

Djoser 在Django应用程序中使用自定义用户模型,所以让我们在项目中创建一个新的应用程序来包含自定义用户模型。浏览后台文件夹并运行 。python3 manage.py startapp users

我们需要扩展Django自带的默认用户模型。在users 文件夹中,编辑models.py ,如图所示。

users/models.py

from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from django.db import models


# Create your models here.
class UserManager(BaseUserManager):
    def create_user(self, email,  password=None, **kwargs):
        if not email:
            raise ValueError("Users must have an email address")
        email = self.normalize_email(email)
        user = self.model(email=email, **kwargs)
        user.set_password(password)
        user.save()
        return user
    def create_superuser(self, email,  password=None, **kwargs):
        kwargs.setdefault('is_active', True)
        kwargs.setdefault('is_staff', True)
        kwargs.setdefault('is_superuser', True)
        if kwargs.get('is_active') is not True:
            raise ValueError('Superuser must be active')
        if kwargs.get('is_staff') is not True:
            raise ValueError('Superuser must be staff')
        if kwargs.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True')
        return self.create_user(email, password, **kwargs)
class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(max_length=255, unique=True)
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['first_name', 'last_name']

    def get_full_name(self):
        return f"{self.first_name}{self.last_name}"

    def get_short_name(self):
        return self.first_name

    def __str__(self):
        return self.email

我们已经创建了一个新的用户模型,并指定用户名字段为电子邮件地址。我们还创建了一个UserManager 模型,处理用户和超级用户的创建。

接下来,我们必须编辑我们的settings.py ,以设置djoser 和谷歌认证。

backend/settings.py 文件中添加以下内容。

from datetime import timedelta
AUTH_USER_MODEL = 'users.User'
# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'users',
    'rest_framework',
    'djoser',
    'corsheaders',
    'social_django',
    'rest_framework_simplejwt',
    'rest_framework_simplejwt.token_blacklist'
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'social_django.middleware.SocialAuthExceptionMiddleware',
    
]
CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
]
CORS_ORIGIN_WHITELIST = [
     "http://localhost:3000",
     "http://127.0.0.1:3000", 
]
CORS_ALLOW_CREDENTIALS = True

ROOT_URLCONF = 'auth_system.urls'

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',
                'social_django.context_processors.backends',
                'social_django.context_processors.login_redirect'
            ],
        },
    },
]


REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',

    ),
}
SIMPLE_JWT = {
    'AUTH_HEADER_TYPES': ('JWT',),
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
}

DJOSER = {
    'LOGIN_FIELD': 'email',
    'SOCIAL_AUTH_TOKEN_STRATEGY': 'djoser.social.token.jwt.TokenStrategy',
    'SOCIAL_AUTH_ALLOWED_REDIRECT_URIS': ['http://127.0.0.1:3000', 'http://127.0.0.1:3000/home','http://127.0.0.1:3000/login'],
    'SERIALIZERS': {},
}
AUTHENTICATION_BACKENDS = (
    'social_core.backends.google.GoogleOAuth2',
    'django.contrib.auth.backends.ModelBackend'
)
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your_client_id_key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your_secret_key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [
    'https://www.googleapis.com/auth/userinfo.email',
    'https://www.googleapis.com/auth/userinfo.profile',
    'openid'
]
SOCIAL_AUTH_GOOGLE_OAUTH2_EXTRA_DATA = ['first_name', 'last_name']

我们已经在INSTALLED_APPS 中列出了我们的应用程序所需的所有应用程序。接下来,我们指定了认证所需的User 模型,并为CORSdjango_social 设置了中间件。

然后,我们指定了允许访问我们应用程序的网站起源。这种情况下,我们将使用localhost:3000 ,因为我们以后将使用React。

然后,我们对Django REST框架和Django REST simple-jwt进行了设置。注意允许重定向的URLs部分。这些URL应该类似于我们将在谷歌云控制台中为应用程序设置的URL。

我们将实现谷歌的认证,所以我们也需要ModelBackend ,以登录到管理面板。然后,我们为SOCIAL_AUTH_GOOGLE_OAUTH2_KEY,SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET 进行设置。

现在我们需要配置URL,使后端应用程序工作。

编辑backend/urls.py 文件,如下所示。

from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('admin/', admin.site.urls),
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.jwt')),
    path('auth/', include('djoser.social.urls')),#Needed for social authentication
]

运行python3 manage.py makemigrationspython3 manage.py migrate 来完成后端设置。

创建谷歌OAuth凭证

访问Google Cloud Plaform并创建一个新项目,如下所示。

Create Project

选择项目后,点击Credentials ,然后在导航后点击OAuth Client ID ,创建凭证。

Create OAuth Client ID

你可能需要设置OAuth同意,所以选择网络应用,然后输入http://127.0.0.1:3000 ,用于授权的JavaScript起源。

对于授权重定向,URI有这些URL。http://127.0.0.1:3000,http://127.0.0.1:3000/home, 和http://127.0.0.1:3000/login

Set up the uris

在你点击创建后,你将得到你的client idclient secret ,你将在你的settings.py 中使用,如下所示。

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'your_client_id_key'
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'your_secret_key'

创建前端应用程序

在一个新的终端会话中,运行npx create-react-app frontend ,创建一个新的React应用程序。

导航到frontend并运行以下命令npm install axios redux react-redux redux-devtools-extension-redux-thunk styled-components

这些命令安装了我们在构建这个应用程序时需要的所有依赖项。

你的项目结构应该是这样的。

├── node_modules
├── package.json
├── package-lock.json
├── public
│   ├── favicon.ico
│   ├── images
│   │   ├── google.svg
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── README.md
└── src
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    │   ├── auth.js
    │   └── index.js
    ├── reportWebVitals.js
    └── setupTests.js

创建组件

src 文件夹下,创建一个名为components 的新目录来存放所有需要的组件。

在该文件夹中,创建以下组件。

1.组件/Home.js

import React from 'react'
import {Link} from "react-router-dom";

const Home = () => {
    return (
        <div className="container">
            <div className="card"style={{ width: 700}}>
                <div className="card-body">
                    <h5 className="card-title">Welcome to The Auth with React & Djoser</h5>
                    <p className="card-text">Thank you for using this authentication system.</p>
                </div>
            </div>
        </div>
    )
}

export default Home

2.组件/Layout.js

import React, from 'react'

const Layout = (props) => {
    return (
        <div className="container">
            <Navbar/>
            {props.children}
        </div>
    )
}

export default Layout

3.组件/Login.js

import React from 'react'
import {Link, Navigate} from 'react-router-dom'
import styled from "styled-components";

const Login = () => {
    return (
        <div className="container mt-4">
            <h1>Sign In</h1>
            <p>Log into your account now.</p>
           
            <Google className="btn btn-secondary" >
                <img src="/images/google.svg" alt=""/>
                Continue with Google.
            </Google>
            <p className="mt-3">Don't have an account? <Link to="/signup">Register</Link></p>

        </div>
    )
}
const Google = styled.button`
  display: flex;
  justify-content: center;
  background-color: #fff;
  align-items: center;
  height: 46px;
  width: 30%;
  border-radius: 28px;
  box-shadow: inset 0 0 0 1px rgb(0, 0, 0, 60%), inset 0 0 0 2px rgb(0, 0, 0, 0%), inset 0 0 0 1px rgb(0, 0, 0, 0);
  vertical-align: middle;
  z-index: 0;
  transition-duration: 167ms;
  font-size: 20px;
  color: rgbe(0, 0, 0, 0.6);

  &:hover {
    background-color: rgba(207, 207, 207, 0.25);
    color: rgba(0, 0, 0, 0.75);
  }
`
export default Login

4.组件/Navbar.js

import React from 'react'
import {Link} from "react-router-dom";

const Navbar = () => {
    return(
        <nav className="navbar navbar-expand-lg navbar-light bg-light">
            <div className="container-fluid">
                <a className="navbar-brand" href="#">The Auth</a>
                <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                        aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                    <span className="navbar-toggler-icon"></span>
                </button>
                <div className="collapse navbar-collapse" id="navbarNav">
                    <ul className="navbar-nav">
                        <li className="nav-item">
                            <Link className="nav-link active" aria-current="page" to="/">Home</Link>
                        </li>
                         <li className="nav-item">
                            <Link className="nav-link active" aria-current="page" to="/login">Home</Link>
                        </li>
                         <li className="nav-item">
                            <Link className="nav-link active" aria-current="page" to="/signup">Home</Link>
                        </li>
              
                    </ul>
                </div>
            </div>
        </nav>
    )
}
export Navbar

5.Components/Signup.js

import React from 'react'
import {Link, Navigate} from 'react-router-dom'

import styled from "styled-components";

const Signup = () => {
    return (
        <div className="container mt-4">
            <h1>Sign Up</h1>
            <p>Create into your account now.</p>
         

            <Google className="btn btn-secondary">
                <img src="/images/google.svg" alt=""/>
                Continue with Google.
            </Google>
            <p className="mt-3">Have an account? <Link to="/login">Sign In</Link></p>

        </div>
    )
}
const Google = styled.button`
  display: flex;
  justify-content: center;
  background-color: #fff;
  align-items: center;
  height: 46px;
  width: 30%;
  border-radius: 28px;
  box-shadow: inset 0 0 0 1px rgb(0, 0, 0, 60%), inset 0 0 0 2px rgb(0, 0, 0, 0%), inset 0 0 0 1px rgb(0, 0, 0, 0);
  vertical-align: middle;
  z-index: 0;
  transition-duration: 167ms;
  font-size: 20px;
  color: rgbe(0, 0, 0, 0.6);

  &:hover {
    background-color: rgba(207, 207, 207, 0.25);
    color: rgba(0, 0, 0, 0.75);
  }
`

export default Signup

6.组件/Welcome.js

import React from 'react'
import {Link} from "react-router-dom";

const Welcome = () => {
    return (
        <div className="container">


            <div className="card"style={{ width: 700}}>

                    <div className="card-body">
                        <h5 className="card-title">Welcome to The Auth</h5>
                        <p className="card-text">Click to Login.</p>
                        <Link to="/login"  className="btn btn-primary">Login</Link>&nbsp;&nbsp;&nbsp;&nbsp;
                        <p className="mt-3">Don't have an account? <Link to="/signup">Register</Link></p>
                    </div>
            </div>
        </div>

)
}

export default Welcome

现在我们需要为我们的组件配置路由。编辑App.js 文件,如下所示。

/* import logo from './logo.svg';*/
/* import './App.css'; */
import React from 'react'
import {BrowserRouter, Route, Routes} from "react-router-dom";
import Welcome from "./components/Welcome";
import Signup from "./components/Signup";
import Login from "./components/Login";

import Layout from "./components/Layout";
import store from "./store";

import Home from "./components/Home";


function App() {
  return (
    <div>
      <BrowserRouter>
        <Layout>
        <Routes>
          <Route exact path="/" element={<Welcome />} />
            <Route exact path="/home" element={<Home />} />
            <Route path="/login" element={<Login />} />
            <Route path="/signup" element={<Signup />} />
        </Routes>
        </Layout>
      </BrowserRouter>
    </div>
  );
}

export default App;

使用命令npm start ,可以看到这个欢迎页面。

Home Page

你也应该能够导航到登录和注册页面。

设置认证

前端应用程序将使用redux ,用于我们的应用程序。Redux是一个用于JavaScript应用程序的状态容器。

我们将为我们的应用程序设置动作还原器和存储文件,但是,首先,在src 下创建以下文件和文件夹,文件夹结构如下。

├── node_modules
├── package.json
├── package-lock.json
├── public
│   ├── favicon.ico
│   ├── images
│   │   ├── google.svg
│   │   ├── haidong.jpg
│   │   ├── jaspergeys.jpg
│   │   └── robson.jpg
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── README.md
└── src
    ├── actions
    │   ├── auth.js
    │   └── types.js
    ├── App.css
    ├── App.js
    ├── App.test.js
    ├── components
    │   ├── Home.js
    │   ├── Layout.js
    │   ├── Login.js
    │   ├── Navbar.js
    │   ├── Signup.js
    │   └── Welcome.js
    ├── index.css
    ├── index.js
    ├── logo.svg
    ├── reducers
    │   ├── auth.js
    │   └── index.js
    ├── reportWebVitals.js
    ├── setupTests.js
    └── store.js
  1. actions - 这个文件夹处理如何指定不同的动作。
  2. reducers - 处理在不同行动中如何操作状态的问题。
  3. store.js - 我们用来定义我们的商店的一个文件。
  4. .env文件 - 我们用这个文件来定义我们的默认API URL。

我们首先在.env 文件中定义我们的API URL。

REACT_APP_API_URL = 'http://127.0.0.1:8000'

为了定义我们的动作,我们需要不同的动作类型,所以编辑types.js 文件,如下所示。


export const GOOGLE_AUTH_SUCCESS = 'GOOGLE_AUTH_SUCCESS'
export const GOOGLE_AUTH_FAIL = 'GOOGLE_AUTH_FAIL'
export const LOGOUT = 'LOGOUT'

然后我们在auth.js 文件中定义我们的动作,如下所示。

import {

    GOOGLE_AUTH_FAIL,
    GOOGLE_AUTH_SUCCESS,
    LOGOUT,
 
} from "./types";

import axios from "axios";
axios.defaults.withCredentials = true;

export const googleAuthenticate = (state, code) => async dispatch =>{
    if( state && code && !localStorage.getItem('access')){
        const config = {
            headers: {
                'Content-Type':'application/x-www-form-urlencoded'
            }}
        const details = {
                'state': state,
                'code':code
        }
        const formBody = Object.keys(details).map(key=> encodeURIComponent(key)+'='+encodeURIComponent(details[key])).join('&')
        try{
            const res = await axios.post(`${process.env.REACT_APP_API_URL}/auth/o/google-oauth2/?${formBody}`, config);
            console.log(res.data)
            dispatch({
                type:GOOGLE_AUTH_SUCCESS,
                payload: res.data
            })
        }catch(err){
            dispatch({
                type:GOOGLE_AUTH_FAIL
            })
            console.log(err)
        }
    }
}

export const logout = () => dispatch => {
    dispatch({
        type: LOGOUT
    })
}

当用户使用谷歌进行认证时,应用程序将发送一个包含状态和代码的重定向URI。我们将使用这个代码和状态来获取用户信息,如访问和刷新令牌、电子邮件和姓名。

googleAuthenticate 函数用代码和状态作为数据处理post请求,然后将用户数据作为一个响应返回。

这些结果被派发到reducer,动作类型是GOOGLE_AUTH_SUCCESS 。然而,如果出现任何错误,那么派发的动作类型是GOOGLE_AUTH_FAIL

我们也有logout 动作,派发的动作类型是LOGOUT 。接下来,我们需要处理还原器。我们必须在src/reducers/ 中创建一个index.js 文件,其中有两个以上的还原器,如下所示。

import { combineReducers } from 'redux';
import auth from './auth';

const rootReducer = combineReducers({
    auth
})

export default rootReducer

我们在reducers目录下有auth.js 文件,以根据类型触发一个动作。

import {

    GOOGLE_AUTH_FAIL, 
    GOOGLE_AUTH_SUCCESS,
    LOGOUT,
    
} from "../actions/types";


const initialState = {
    access: localStorage.getItem('access'),
    refresh: localStorage.getItem('refresh'),
    isAuthenticated: false,
  

}
export default function(state=initialState,action){
    switch (action.type){
        case GOOGLE_AUTH_SUCCESS:
            console.log(action.payload)
            localStorage.setItem('access',action.payload.access)
            return{
                ...state,
                isAuthenticated: true,
                access: action.payload.access,
                refresh: action.payload.refresh
            }
        case GOOGLE_AUTH_FAIL:
        case LOGOUT:
            console.log(action.payload)
            localStorage.removeItem('access')
            localStorage.removeItem('refresh')
            return{
                ...state,
                isAuthenticated: false,
                access: null,
                refresh: null
            }
        default:
            return state
    }
}

我们需要设置一个初始状态,为访问和刷新令牌设置默认值。我们还为isAuthenticated 设置了一个默认的布尔值false。这个值以后将被用来处理认证用户的路由。

我们将本地存储中的访问和刷新令牌值设置为认证成功后派发的值。isAuthenticated 为真;否则,令牌值为空,isAuthenticated 仍为假。

如果没有其他动作,我们返回当前状态并创建我们的存储。编辑src/store.js ,使其类似于这个。

import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const initialState = {};

const middleware = [thunk];

const store = createStore(
    rootReducer,
    initialState,
    composeWithDevTools(applyMiddleware(...middleware))
);

export default store;

所有的组件将从这个文件中读取状态。我们需要使我们的商店对我们的组件可用。

让我们把它添加到src/App.js

// All imports remain the same

function App() {
  return (
    <div>
      <Provider store={store}>
      <BrowserRouter>
        // All code remain the same
      </BrowserRouter>
      </Provider>
    </div>
  );
}

export default App;

<Provider store={store}> 这一行使我们的应用程序中的所有组件都可以使用这些状态。现在,我们必须把我们的组件连接到商店。所以让我们从src/components/Layout.js 文件开始。

import React, {useEffect} from 'react'
import Navbar from "./Navbar";
import {googleAuthenticate} from "../actions/auth";
import {connect} from "react-redux";
import {useLocation} from "react-router-dom";
import queryString from "query-string";

const Layout = (props) => {
    const location = useLocation()
    useEffect(() => {
        const values = queryString.parse(location.search)
        const state = values.state ? values.state : null
        const code = values.code ? values.code : null
        console.log('State: '+ state)
        console.log('Code: '+code)
        if (state && code){
            props.googleAuthenticate(state, code)
        }
    }, [location])

    return (
        <div className="container">
            <Navbar/>

            {props.children}
        </div>
    )
}

export default connect(null, { googleAuthenticate})(Layout)

我们使用connect 函数来连接到我们的商店。然后,我们调度googleAuthenticate 动作,并将其作为道具传递给Layout 函数。

我们需要获得当前页面的URL和URL的键值对。如果代码和状态存在,我们调用googleAuthenticate 动作,该动作将代码和状态作为参数。

当我们被重定向时,URL有一个状态和代码,获得并传递给googleAuthenticate 函数。

接下来,我们把我们的Signup 连接到商店。编辑src/components/Signup.js ,如下所示。

import React, {useEffect, useState} from 'react'
import {Link, Navigate} from 'react-router-dom'
import {connect, useDispatch, useSelector} from 'react-redux'

import styled from "styled-components";
import axios from "axios";

const Signup = ({ isAuthenticated}) => {
    const signupWithGoogle = async () => {
        try {
            const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/o/google-oauth2/?redirect_uri=${process.env.REACT_APP_API_URL}/login`)

            window.location.replace(res.data.authorization_url)

        } catch (err) {
            console.log("Error logging in")
        }
    }
    
    if (isAuthenticated) {
        return <Navigate to='/home'/>
    }

    return (
        <div className="container mt-4">
            <h1>Sign Up</h1>
            <p>Create into your account now.</p>


            <Google className="btn btn-secondary" onClick={signupWithGoogle}>
                <img src="/images/google.svg" alt=""/>
                Continue with Google.
            </Google>
            <p className="mt-3">Have an account? <Link to="/login">Sign In</Link></p>

        </div>
    )
}
const Google = styled.button`
  display: flex;
  justify-content: center;
  background-color: #fff;
  align-items: center;
  height: 46px;
  width: 30%;
  border-radius: 28px;
  box-shadow: inset 0 0 0 1px rgb(0, 0, 0, 60%), inset 0 0 0 2px rgb(0, 0, 0, 0%), inset 0 0 0 1px rgb(0, 0, 0, 0);
  vertical-align: middle;
  z-index: 0;
  transition-duration: 167ms;
  font-size: 20px;
  color: rgbe(0, 0, 0, 0.6);

  &:hover {
    background-color: rgba(207, 207, 207, 0.25);
    color: rgba(0, 0, 0, 0.75);
  }
`
const mapStateToProps = state => ({
    isAuthenticated: state.auth.isAuthenticated,
})

export default connect(mapStateToProps, null)(Signup);

当用户在注册时点击继续使用谷歌,signupWithGoogle 函数被调用。这个函数向后端发送一个带有指定重定向URI的帖子请求。

这个URI必须是Djoser设置中所允许的重定向URI之一。此外,允许的URI必须与你的谷歌云控制台中的URI相同。

用户会被引导到授权页面,以及指定的重定向URI,URL中包含状态和代码。

我们还传递了isAuthenticated 状态,以将用户重定向到主页,如果他们已经通过了认证,则无需再进行认证。

现在我们对登录页面做同样的工作。

import React, {useEffect, useState} from 'react'
import {Link, Navigate} from 'react-router-dom'
import {connect, useDispatch} from 'react-redux'
import {login} from "../actions/auth";
import styled from "styled-components";
import axios from "axios";

const Login = ({ isAuthenticated}) => {
    const loginWithGoogle = async () =>{
        try{
            const res = await axios.get(`${process.env.REACT_APP_API_URL}/auth/o/google-oauth2/?redirect_uri=${process.env.REACT_APP_API_URL}/home`)
            window.location.replace(res.data.authorization_url)

        }catch(err){
            console.log("Error logging in")
        }
    }
  
    if(isAuthenticated){
        return <Navigate to="/home" />
    }
    return (
        <div className="container mt-4">
            <h1>Sign In</h1>
            <p>Log into your account now.</p>


            <Google className="btn btn-secondary" onClick={loginWithGoogle} >
                <img src="/images/google.svg" alt=""/>
                Continue with Google.
            </Google>
            <p className="mt-3">Don't have an account? <Link to="/signup">Register</Link></p>

        </div>
    )
}
const Google = styled.button`
  display: flex;
  justify-content: center;
  background-color: #fff;
  align-items: center;
  height: 46px;
  width: 30%;
  border-radius: 28px;
  box-shadow: inset 0 0 0 1px rgb(0, 0, 0, 60%), inset 0 0 0 2px rgb(0, 0, 0, 0%), inset 0 0 0 1px rgb(0, 0, 0, 0);
  vertical-align: middle;
  z-index: 0;
  transition-duration: 167ms;
  font-size: 20px;
  color: rgbe(0, 0, 0, 0.6);

  &:hover {
    background-color: rgba(207, 207, 207, 0.25);
    color: rgba(0, 0, 0, 0.75);
  }
`
const mapStateToProps = state => ({
    isAuthenticated: state.auth.isAuthenticated,

})

export default connect(mapStateToProps, null)(Login);

我们已经完成了与注册相同的程序,只是这一次,当用户登录时,重定向URI将是主页。

我们还传递了状态isAuthenticated ,以确保认证的用户被重定向到主页。

最后,我们处理注销过程。编辑src/components/Navbar.js ,如下所示。

import React from 'react'
import {Link} from "react-router-dom";
import {logout} from "../actions/auth";
import {connect} from "react-redux";

const Navbar = ({isAuthenticated, logout}) => {
    return(

        <nav className="navbar navbar-expand-lg navbar-light bg-light">
            <div className="container-fluid">
                <a className="navbar-brand" href="#">The Auth</a>
                <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                        aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
                    <span className="navbar-toggler-icon"></span>
                </button>
                <div className="collapse navbar-collapse" id="navbarNav">
                    <ul className="navbar-nav">
                        <li className="nav-item">
                            <Link className="nav-link active" aria-current="page" to="/">Home</Link>
                        </li>
                        {isAuthenticated ?
                            <li className="nav-item">
                                <a className="nav-link" href="/" onClick={logout}>Logout</a>
                            </li>

                            :
                            <div className="navbar-nav">
                                <li className="nav-item">
                                    <Link className="nav-link" to="/login">Log In</Link>
                                </li>
                                <li className="nav-item">
                                    <Link className="nav-link" to="/signup">Sign Up</Link>
                                </li>
                            </div>

                        }
                    </ul>
                </div>
            </div>
        </nav>
    )
}
const mapStateToProps = state => ({
    isAuthenticated: state.auth.isAuthenticated
})
export default connect(mapStateToProps, { logout })(Navbar)

再一次,我们将isAuthenticated 状态和注销函数作为道具传递给Navbar函数。接下来,我们检查用户是否经过认证,如果是,则将用户注销;否则,用户可以选择注册或登录。

当用户点击注销链接时,就会调用注销函数。现在你应该能够使用谷歌账户进行认证。

Authenticating using Google

你将被重定向到主页,可以选择注销。

The home page after authentication

请注意,只要用户登录了他们的谷歌账户,就会保持认证状态。这样,用户就不必在每次想登录时不断输入电子邮件和密码。

我建议运行npm run build ,将构建文件夹移到后台目录,设置必要的查看URL和静态文件的设置,使你的项目在localhost:8000上运行以避免认证错误。

总结

你现在已经用Djoser处理了Google的认证。你可以对其他社交账户,如Facebook、Twitter或其他账户做同样的处理。