有关[React + Django]的登录功能的创建方法,请进行详细解释
总结
使用React和Django来创建登录页面,并使用自己编写的登录API来实现登录成功后的页面跳转处理。
前提 tí)
-
- Djangoのプロジェクトを作成済み
-
- Reactのアプリケーションを作成済み
-
- Formの作成はReact Hook Formを使用
-
- RestAPIを作成するためDjango Rest Frameworkを使用
- 今回はReactメインで説明するのでDjangoについてはCSRFとCORS以外詳しく説明しません
文件架构
tree
・
├── .gitignore
├── README.md
├── backend
│ ├── application
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── fixtures
│ │ │ └── fixture.json
│ │ ├── migrations
│ │ │ ├── __init__.py
│ │ │ └── 0001_initial.py
│ │ ├── models
│ │ │ ├── __init__.py
│ │ │ └── user.py
│ │ ├── permissions.py
│ │ ├── serializers
│ │ │ ├── __init__.py
│ │ │ └── user.py
│ │ ├── urls.py
│ │ └── views
│ │ ├── __init__.py
│ │ ├── login.py
│ │ └── user.py
│ ├── manage.py
│ ├── poetry.lock
│ ├── project
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ └── pyproject.toml
├── containers
│ ├── django
│ │ ├── Dockerfile
│ │ ├── Dockerfile.prd
│ │ ├── entrypoint.prd.sh
│ │ └── entrypoint.sh
│ ├── front
│ │ └── Dockerfile
│ ├── nginx
│ │ ├── Dockerfile
│ │ └── nginx.conf
│ └── postgres
│ ├── Dockerfile
│ └── init.sql
├── docker-compose.yml
├── frontend
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── pages
│ │ ├── 404
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── login_success
│ │ │ └── index.js
└── static
这次我们要解释的是以下的文件
-
- docker-compose.yml
-
- nginx.conf
-
- settings/base.py
-
- settings/local.py
-
- pages/index.js
- pages/login_success/index.js
我会逐一解释
Docker的配置
让前端和后端能够互相通信。
-
- docker-compose.yml
- nginx.conf
进行创建
请参考以下文章以获得有关本次关于创建登录功能的说明的详细信息。
version: '3.9'
services:
db:
container_name: db
build:
context: .
dockerfile: containers/postgres/Dockerfile
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U "${POSTGRES_USER:-postgres}" || exit 1
interval: 10s
timeout: 5s
retries: 5
environment:
- POSTGRES_NAME
- POSTGRES_USER
- POSTGRES_PASSWORD
ports:
- '5432:5432' # デバッグ用
app:
container_name: app
build:
context: .
dockerfile: containers/django/Dockerfile
volumes:
- ./backend:/code
- ./static:/static
ports:
- '8000:8000'
# デバッグ用ポート
- '8080:8080'
command: sh -c "/usr/local/bin/entrypoint.sh"
stdin_open: true
tty: true
env_file:
- .env
depends_on:
db:
condition: service_healthy
mail:
container_name: mail
image: schickling/mailcatcher
ports:
- '1080:1080'
- '1025:1025'
nginx:
container_name: web
build:
context: .
dockerfile: containers/nginx/Dockerfile
volumes:
- ./static:/static
ports:
- 80:80
depends_on:
- app
front:
container_name: front
build:
context: .
dockerfile: containers/front/Dockerfile
volumes:
- ./frontend:/code
- node_modules_volume:/frontend/node_modules
command: sh -c "npm run dev"
ports:
- '3000:3000'
environment:
- CHOKIDAR_USEPOLLING=true
- NEXT_PUBLIC_RESTAPI_URL=http://localhost/back
volumes:
db_data:
static:
node_modules_volume:
networks:
default:
name: testnet
upstream front {
server host.docker.internal:3000;
}
upstream back {
server host.docker.internal:8000;
}
server {
listen 80;
server_name localhost;
client_max_body_size 5M;
location / {
proxy_pass http://front/;
}
location /back/ {
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://back/;
}
location /upload/ {
proxy_pass http://back/upload/;
}
location /_next/webpack-hmr {
proxy_pass http://front/_next/webpack-hmr;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Django: 别理那些破事。
跨域资源共享(CORS)和跨站请求伪造(CSRF)的配置。
前端和后端不可或缺的协作
-
- CORS
- CSRF
请参考以下文章以获取关于CORS的详细信息。
ALLOWED_HOSTS = ["http://localhost", "http://127.0.0.1"]
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
# 今回はログイン認証の方法としてSession認証を採用
"rest_framework.authentication.SessionAuthentication",
]
}
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"application.apps.ApplicationConfig",
# CORS用のパッケージ
"corsheaders",
]
MIDDLEWARE = [
# CORS用のMiddleware
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
# CSRF用のMiddleware
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
# 自身以外のオリジンのHTTPリクエスト内にクッキーを含めることを許可する
CORS_ALLOW_CREDENTIALS = True
# アクセスを許可したいURL(アクセス元)を追加
CORS_ALLOWED_ORIGINS = django_settings.TRUSTED_ORIGINS.split()
# プリフライト(事前リクエスト)の設定
# 30分だけ許可
CORS_PREFLIGHT_MAX_AGE = 60 * 30
# CSRFの設定
# これがないと403エラーを返してしまう
# https://docs.djangoproject.com/en/4.2/ref/settings/#csrf-trusted-origins
CSRF_TRUSTED_ORIGINS = ["http://localhost", "http://127.0.0.1"]
创建登录API
根据以下文章的参考,我们将创建一个登录API。
该API的路径是/api/login/。
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, JsonResponse
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.permissions import AllowAny
from rest_framework.viewsets import ViewSet
from application.models.user import User
from application.serializers.user import LoginSerializer
class LoginViewSet(ViewSet):
serializer_class = LoginSerializer
permission_classes = [AllowAny]
@action(detail=False, methods=["POST"])
def login(self, request):
"""ユーザのログイン"""
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
employee_number = serializer.validated_data.get("employee_number")
password = serializer.validated_data.get("password")
user = authenticate(employee_number=employee_number, password=password)
if not user:
return JsonResponse(
data={"msg": "社員番号またはパスワードが間違っています"},
status=status.HTTP_400_BAD_REQUEST,
)
else:
login(request, user)
return JsonResponse(data={"role": user.Role(user.role).name})
@action(methods=["POST"], detail=False)
def logout(self, request):
"""ユーザのログアウト"""
logout(request)
return HttpResponse()
回应
根页面
在根页面上设置用于登录的组件。
import React from 'react';
import Link from "next/link";
import Login from '../components/elements/Form/Login'
const Login = () => {
return (
<>
<div>
<Login/>
</div>
</>
);
}
export default Login;
登录功能
请参考下面的文章,我使用了ReactHookForm来创建了登录表单。
import { useForm } from 'react-hook-form';
import Cookies from 'js-cookie';
import router from 'next/router';
function Login() {
type LoginDataType = {
employee_number: string;
password: string;
};
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginDataType>({
// ログインボタンを押した時のみバリデーションを行う
reValidateMode: 'onSubmit',
});
const onSubmit = async (data) => {
// Nginxとdocker-compose.ymlで設定したAPIのパス
// http://localhost/back/api/login/
const apiUrl = process.env.NEXT_PUBLIC_RESTAPI_URL + 'http://localhost/back/api/login/';
const csrftoken = Cookies.get('csrftoken') || '';
// ログイン情報をサーバーに送信
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
// ユーザー名(社員番号)とパスワードをJSON形式で送信
body: JSON.stringify(data),
});
if (response.ok) {
// ログイン成功時に/login_successへ遷移
console.log('ログイン成功');
router.push('/login_success');
// リダイレクトなど、ログイン後の処理を追加
} else {
// ログイン失敗時にバックエンドのエラーをアラートで表示("社員番号またはパスワードが間違っています")
response.json()
.then(data => {
const msg = data.msg;
alert(msg)
})
}
};
return (
<div className="Login">
<h1>ログイン</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<input
id="employee_number"
name="employee_number"
placeholder="社員番号"
{...register('employee_number', {
required: {
value: true,
message: '社員番号を入力してください',
},
pattern: {
value: /^[0-9]{8}$/,
message: '8桁の数字のみ入力してください。',
},
})}
/>
{errors.employee_number?.message && <div>{errors.employee_number.message}</div>}
</div>
<div>
<input
id="password"
name="password"
placeholder="パスワード"
type="password"
{...register('password', {
required: {
value: true,
message: 'パスワードを入力してください'
},
/>
{errors.password?.message && <div>{errors.password.message}</div>}
</div>
<button type="submit">ログイン</button>
</form>
</div>
);
}
export default Login;
我将逐个解释。 (Wǒ zhú gè jiě shì.)
FetchAPI 取得API
使用FetchAPI将数据POST到自定义的登录API上。
在进行登录之前,没有SessionID和CSRF令牌。
csrftoken的值将为空。
Content-Type将设置为application/json。
请求的BODY将是JSON格式的。
// Nginxとdocker-compose.ymlで設定したAPIのパス
// http://localhost/back/api/login/
const apiUrl = process.env.NEXT_PUBLIC_RESTAPI_URL + '/api/login/';
const csrftoken = Cookies.get('csrftoken') || '';
// ログイン情報をサーバーに送信
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken,
},
// ユーザー名(社員番号8桁)とパスワードをJSON形式で送信
body: JSON.stringify(data),
});
处理响应
当回复收到时,将使用状态码进行处理。
如果收到200系列的响应,将使用next/router将页面push到/login_success。
如果登录失败(收到的不是200系列的响应),将通过警报提示显示错误消息。
if (response.ok) {
// ログイン成功時に/login_successへ遷移
console.log('ログイン成功');
router.push('/login_success');
// リダイレクトなど、ログイン後の処理を追加
} else {
// ログイン失敗時にバックエンドのエラーをアラートで表示("社員番号またはパスワードが間違っています")
response.json()
.then(data => {
const msg = data.msg;
alert(msg)
})
}
成功登录后的导航目标
目的地是下列简单的选项
import Link from "next/link";
const LoginSuccess = () => {
return (
<>
<h1>ログイン成功!</h1>
<div>
<Link href="/"><h1>Homeへ</h1></Link>
</div>
</>
);
};
export default LoginSuccess;
让我们亲身尝试登录吧!

成功登录后,将会显示以下页面,并将SessionID和CSRF令牌保存在Cookie中。


postgres=# \d django_session
session_key | character varying(40) | | not null |
session_data | text | | not null |
expire_date | timestamp with time zone | | not null |
postgres=# select * from django_session;
fjnp91385waomnbjeyrj9d5xv1yghwhu |.eJxtjEsOAiEQRO_CWgm_6UGX7j0DaWiQUQPJfFbGuzskLDSxFpVKql69mMNtzW5b4uwmYmcmuo5_rEuywzfmMTxiaSzdsdwqD7Ws8-R5m_DeLvxaKT4vfftzkHHJjSZAYEkDAqyQfuYRhIBtA0K9iohyFGBMkNKSRjac7RehkErTxI8e38AJ_E-gQ:1quXMh:m_UZbRXoULHFeFe6lyeb52dJw0Cpuq6VxCr8U8eOWCk | 2023-11-05 12:15:31.119386+00
如果登录失败,会显示以下提示框。

以上是上面所提到的。
请参考