240512_Django实现基于cookie与session的用户登录

Haoliang Tang Lv3

用户登录鉴权一直都是一个头疼的点。3月就开始着手搞了(了解基本原理,以及分布式的服务器情况,在一台服务器上登录后,其他服务器如果没有同步这条session记录的话,用户又需要重新登录,比如换了国家使用应用。而jwt服务端不需要保存,服务器验证是不是自己发行的token就行了)

4月头上也实现出来了,现在来记录下。

可以使用django自带的

1
2
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, permission_required

但不知道这些自带函数到底做了什么,这时就需要去看源码了。

先来看Django自带的功能

当创建Django项目,migrate了后,初始数据库自带有auth_user表,django_session表。

然后就主要使用Django内置的用户认证系统: django.contrib.auth

Django自带的auth登录认证

关于User表:

  • 可以直接使用自带的auth_user用户表(就是我们createsuperuser也是写入这个表里,这个表其实直接继承自AbstractUser) from django.contrib.auth.models import AuthUser
  • 也可以自己定义用户表,但继承自内置用户类AbstractUser class User(AbstractUser):以此增加一些自定义字段

以上都可以利用到Django内置的用户认证系统: django.contrib.auth,包括authenticate, login, logout, create_user…等方法

如果用户表是class User(models.Model):而非继承于AbstractUser的话,那所有的一切都需要自己实现,就像做PFN的oa题一样。

所以我可能倾向用继承AbstractUser自定义用户表,而不是直接用auth_user表


OK,我们先来写一个注册函数吧,在先不自定义用户表的情况下。

1
2
3
4
5
6
7
8
9
from django.contrib.auth.models import User as AuthUser # 使用自带的auth_user表

@builtin_auth_api.post("/register/")
def auth_register(request, payload: RegisterIn):
if AuthUser.objects.filter(username=payload.username).exists():
return {"message": "Username already exists"}
user = AuthUser.objects.create_user(username=payload.username, password=payload.password)
# create_user()密码会自动哈希, 但这个create_user()只有继承自AbstractUser的类有
return {"message": "User created successfully", "username": user.username, "pwd": user.password}

用django-ninja自带的docs测试下

1
2
3
4
5
6
7
8
curl -X 'POST' \
'http://127.0.0.1:8000/builtin/register/' \
-H 'accept: */*' \
-H 'Content-Type: application/json' \
-d '{
"username": "string",
"password": "string"
}'

Response body

1
2
3
4
5
{
"message": "User created successfully",
"username": "string",
"pwd": "pbkdf2_sha256$720000$Mamt4AC2zgB7YBqywmOMsY$gQ0xk4f1/CABfcfn2ulWbYB4UxEapnV76tgoqjEeXXY="
}

然后数据库里也有了这条记录

create_user()会自动哈希pwd存到数据库里

然后来尝试自定义User表:

1
2
3
4
5
from django.contrib.auth.models import AbstractUser

# Create your models here.
class User(AbstractUser):
phone = models.CharField(max_length=11, null=True, unique=True)

注意:按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的User表来做用户认证。django.contrib.auth的login…函数就作用在自定义表而不是auth_user表了

相当于默认的设置 AUTH_USER_MODEL = ‘auth.user’

1
AUTH_USER_MODEL = 'builtin_auth.User'

迁移一下数据库

1
2
python manage.py makemigrations
python manage.py migrate

出问题了就删掉migrations目录下的所有0001_initial.py之类的迁移文件&数据库文件

原来数据库中的auth_user表没了,只有builtin_auth_user表了,而且有自定义的phone字段

所以在第一次migrate时就要确定好User表用什么

然后修改register函数

1
2
3
4
5
6
7
8
9
10
# from django.contrib.auth.models import User as AuthUser # 使用自带的auth_user表
from .models import User

@builtin_auth_api.post("/register/")
def auth_register(request, payload: RegisterIn):
if User.objects.filter(username=payload.username).exists():
return {"message": "Username already exists"}
user = User.objects.create_user(username=payload.username, password=payload.password)
# create_user()密码会自动哈希, 但这个create_user()只有继承自AbstractUser的类有
return {"message": "User created successfully", "username": user.username, "pwd": user.password}

仅仅就是换了个数据库表,改了表名

然后数据都在builtin_auth_user表里了


OK,用户已经注册后然后来处理login

register就仅仅是往数据库加一条用户数据

login若就是简单基于session的话,那就先检验前端发来的username和pwd能不能匹配得上(前端发来的pwd哈希后==用户表里存着的username对应的已哈希了的pwd),验证成功就session表里添加一条记录表明用户已登录,然后发给前端sessionID(一般前端设置cookie,也可以随便前端存在哪)

核心就是数据库加一条session记录,把session记录的id(也可以理解为token)发给前端,以便让前端之后再发请求时带着这个token(此处即sessionID)来保持登录(cookie作为http请求头),能和数据库里存的对得上

前端或者postman把cookie给清空了,就丢失了token(sessionID),就丢失了对这个会话的标示,那就需要重新登录了,即使后端数据库可能还存着原先的sessionID

还有不少的网站,可能给游客用户也发一个sessionID,然后前端一般就把这个sessionID存在cookie里,来记录这个游客的会话,服务端还可以设置其他的cookie比如对某个endpoint的访问次数。浏览器会把存着的cookie都放到http请求头里。

logout就把django_session里的对应记录删除

但实际浏览器的cookie在刷新后会丢失

1
2
3
import axios from "axios";
// axios.defaults.baseURL = "http://127.0.0.1:8000"; //浏览器刷新后cookie丢失,但localhost却可以
axios.defaults.baseURL = "http://localhost:8000";

可能还是自带auth的cookie设置问题,因为我自己后端设置的response.set_cookie("cookie", "delicious"),浏览器刷新还是在的

⚠️注意⚠️

axios.post("http://127.0.0.1:8000/api/login/" 浏览器刷新后cookie丢失

axios.post("http://localhost:8000/api/login/" 浏览器刷新后cookie还在

即cookie的Domin要是localhost,不懂为什么127.0.0.1丢失了

axios.defaults.baseURL =http://localhost:8000“; 那改成localhost就好了

就是因为node起的服务器地址是localhost:5173,同源的cookie自然好设置。127.0.0.1都跨站了就需要cookie的跨站处理,什么cookie的SameSite


本质原因在于cookie的SameSite属性有问题:

this set cookie didn t specify a samesite

https://andrewlock.net/understanding-samesite-cookies/

https://stackoverflow.com/questions/46288437/set-cookies-for-cross-origin-requests

==根本还是Django自带的登录功能的cookie设置不对,设置了samesite: “Lax”==

最好还是自己实现吧,还能把session存在redis缓存里,亲自给前端发cookie

全都自定义实现

可以参考django.contrib.auth的源码

https://www.bilibili.com/video/BV1XQ4y1o7Nq?p=74

  • Title: 240512_Django实现基于cookie与session的用户登录
  • Author: Haoliang Tang
  • Created at : 2024-05-12 00:00:00
  • Updated at : 2025-04-29 23:49:43
  • Link: https://hl-tang.github.io/2024/05/12/240512_Django实现基于cookie与session的用户登录/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
240512_Django实现基于cookie与session的用户登录