尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于Django与SSO的统一身份认证系统设计:单点登录与用户管理实战

发布时间:2026/9/28 15:54:21

资讯中心
01
ARTICLE

基于Django与SSO的统一身份认证系统设计:单点登录与用户管理实战

基于Django与SSO的统一身份认证系统设计:单点登录与用户管理实战
简介这是一份面向计算机专业毕业设计的Web服务统一身份认证协议设计项目基于PythonDjangoMySQL实现B/S架构。系统重点解决多网站重复登录问题包含用户信息管理、单点登录及权限分配策略覆盖认证协议的核心流程与落地实现。资源共603个文件压缩包8.34MB其中105个Python源码与89个pyc编译文件构成后端逻辑13个HTML及52个JS、21个CSS支撑前端界面另有198个PNG、96个GIF演示截图、7个数据库文件和SQL脚本方便直接部署与二次开发。压缩包还附带说明文档、演示视频与一键启动bat脚本帮助读者快速跑通环境并理解设计思路。已有904人学习适合正在准备毕业设计或需要快速搭建统一认证系统的开发者参考。1. 统一身份认证协议这东西不是登录页是登录的“总闸”做毕业设计或者接手实际项目时很多人把“统一身份认证”想简单了以为就是做一个长得好看的登录页面。实际上这套基于 Python Django MySQL B/S 架构的毕业设计资源核心解决的是两个真问题一是用户信息散落在多个系统里每套系统各管各的账号密码改起来想骂人二是用户每访问一个子站就得重新登录一次体验割裂得像闯关。这个项目做的就是“总闸”——统一身份认证和单点登录SSO用户在主站登录一次再去访问相互信任的其他站点时不再需要重复输入账号密码。适合两类人一类是拿它当毕业设计模板的在校生另一类是想搞懂 SSO 落地逻辑、准备把多系统账号体系收拢起来的后端开发者。它给的不只是代码是一套能讲清楚“认证到底怎么统一”的思路。接下来我会从 Django 的认证机制入手把它掰开揉碎讲清楚。2. Django 认证体系拆解session、cookie 和 auth 中间件的协作逻辑2.1 为什么选 Django 的 auth 模块而不是自己从零写认证做统一身份认证最容易犯的错就是一上来就自己造轮子写一个 User 表然后自己加密密码、自己维护登录状态。实际做下来你会发现坑非常多密码怎么存才安全、session 怎么管理才能做到单点登录、跨域认证怎么办。这个项目选择 Django 自带的django.contrib.auth是非常重要的一个决定因为 Django 的认证体系已经把“登录状态如何保持”这个最棘手的问题处理好了。Django 的认证流程核心是django.contrib.sessions和django.contrib.auth两个模块配合。用户提交用户名和密码后authenticate()函数负责校验凭证login()函数负责把用户的登录状态写进 session。这里的 session 不是简单的 cookie而是服务端保留的一份数据客户端只拿到一个 sessionid 的标识。正因如此当用户访问多个“互相信任”的站点时只要这些站点共享同一个 session 存储就能天然实现单点登录而不需要用户重复提交密码。这个项目在 settings.py 里对 session 的配置也是围绕这个目标展开的。默认情况下 Django 把 session 存在数据库的django_session表里这对毕业设计和中小型系统完全够用。从代码里可以看到项目用的是数据库 session 存储没有引入 Redis 之类的额外组件好处是部署简单MySQL 里建好库就能跑代价是 session 查询频繁时数据库压力会大一些但作为课程设计或者企业内部小规模系统这个选型是合理的。2.2 项目认证链路的完整数据流从浏览器请求到 session 建立我拆这个项目源码的时候最关心的就是一条完整的认证链路到底怎么走通。把代码理完之后整个流程非常清晰你可以按照下面这个路径去读相关文件用户访问任意一个受保护的 URL比如/index/Django 的AuthenticationMiddleware检查 request 里有没有合法的 sessionid若没有中间件将request.user设为AnonymousUser并被login_required装饰器拦下浏览器重定向到/login/用户输入账号密码并提交视图层调用authenticate(username..., password...)校验校验成功后调用login(request, user)Django 生成新的 session_key 并写入django_session表浏览器拿到 sessionid 后后续所有请求都自动带上不再被拦截。# 这是项目里登录视图的核心逻辑我把关键部分整理出来 from django.contrib.auth import authenticate, login from django.shortcuts import render, redirect def login_view(request): if request.method POST: username request.POST.get(username) password request.POST.get(password) # authenticate 会去 Django 的 auth_user 表里校验用户名和密码 user authenticate(request, usernameusername, passwordpassword) if user is not None: # login 会把用户状态写入 session这是单点登录的基础 login(request, user) # 登录成功后跳转到页面实际项目中这里会去取用户有权限的站点列表 return redirect(/index/) else: # 校验失败返回错误提示 error 用户名或密码错误 return render(request, login.html, {error: error}) return render(request, login.html)这段代码里有两个参数值得注意。authenticate的第一个参数是request在 Django 2.0 以后这个参数是可选的但建议保留因为某些后端认证插件会用到它。login函数的第二个参数是user对象这个对象必须是通过authenticate校验过的不能是直接从数据库里查出来的 User 实例这是新手经常翻车的地方——如果你直接User.objects.get(...)然后传给login()Django 会抛出异常因为 User 对象的backend属性没有被设置登录状态无法写入。登录完成后Django 会在响应里通过 Set-Cookie 把 sessionid 写回浏览器。如果你打开浏览器的开发者工具切到 Application 面板能看到这个 cookie 的HttpOnly属性默认是 True这意味着 JavaScript 无法读取它能有效防止 XSS 攻击窃取会话。这是 Django 默认的安全配置项目里没有去改动它是正确的做法。3. 用户管理模块实战从数据表设计到增删改查的实现细节3.1 数据库层面如何支撑“统一身份”单点登录的前提是有“统一身份”。这听起来像一句废话但很多项目的失败恰恰是败在这里——每个子系统的用户表字段都不一样有的叫user_name有的叫account有的用手机号做登录名有的用邮箱。这个项目的做法是把用户统一收敛到 Django 默认的auth_user表里所有子站共享这一份用户数据。Django 的auth_user表核心几个字段是username、password、is_active、is_superuser。这里有一个在设计上需要注意的地方password字段存的是哈希值不是明文。Django 默认使用 PBKDF2 算法加盐后对密码进行哈希这个算法在安全性上比 MD5 和 SHA1 强得多。项目里没有去改默认的PASSWORD_HASHERS配置我建议也不要改除非你明确知道自己在做什么。用户管理功能的页面端用了 layui 的前端框架这是从项目源码的文件列表里能直接看到的layui.css、layer.css这些文件都在。layui 的表单和数据表格组件很适合这种后台管理系统不需要引入复杂的 npm 构建链路直接静态引用就能用。3.2 用户增删改查的代码实现与权限控制用户管理视图围绕“查看、修改、删除”三个能力展开。看源码的时候用户列表页做的是数据表格渲染管理员可以搜索用户、查看详情、跳转到编辑页。实际上你只需要把 Django 的User模型和ModelForm配合起来就能把增删改查写得非常干净。# 用户管理相关视图中编辑用户的核心逻辑 from django.shortcuts import get_object_or_404, redirect, render from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.contrib import messages login_required def user_edit(request, user_id): # 根据 URL 中的 user_id 获取用户对象不存在则返回 404 user get_object_or_404(User, iduser_id) if request.method POST: # 从 POST 数据中取出表单字段 username request.POST.get(username) email request.POST.get(email) is_active request.POST.get(is_active) on # 对字段做基本校验 if not username: messages.error(request, 用户名不能为空) return render(request, user_edit.html, {user: user}) # 更新用户信息 user.username username user.email email user.is_active is_active user.save() messages.success(request, 用户信息更新成功) return redirect(/user/list/) return render(request, user_edit.html, {user: user})这段代码里有几个细节值得展开。第一login_required装饰器在这个项目里被大量使用但要注意它的默认行为是未登录时重定向到/accounts/login/。如果你的登录页 URL 不是这个路径需要在settings.py里设置LOGIN_URL /login/否则登录后跳转路径会不对。这个项目在设置里做了这个配置属于必须的操作。第二is_active字段控制账号能否登录。把is_active置为 False 并不会删除用户数据但用户下次登录时authenticate()会返回 None这是一个“软删除”的思路比直接删除用户更安全因为用户的历史数据还在。删除用户的操作要特别注意外键关联。如果auth_user表被其他表以ForeignKey引用了直接删除用户会触发数据库的级联删除on_deletemodels.CASCADE把所有关联数据一起删掉。项目里如果遇到删除用户特别慢或者偶发报错的情况大概率是这里的关联没有梳理清楚。建议你在做这一块的时候对有关联的数据先做转移或者备份再执行删除操作。3.3 密码重置的隐藏逻辑表单不直接存密码存的是哈希很多第一次写 Django 用户管理的人会在编辑用户时顺手把密码拿过来直接赋值给user.password request.POST.get(password)。这是严重的错误因为User.password字段存的是哈希值如果你把明文密码直接赋进去Django 在后续的authenticate()校验时会用 PBKDF2 算法去哈希你赋进去的明文结果是哈希值永远匹配不上用户再也无法登录。正确的做法是用set_password()方法。项目源码里对于密码重置的处理如果你去翻views.py能看到是单独走了一个视图函数没有和用户信息编辑搅在一起。这是很好的习惯我把核心逻辑整理如下# 密码重置视图独立于资料编辑 from django.contrib.auth.models import User def user_reset_password(request, user_id): user get_object_or_404(User, iduser_id) if request.method POST: new_password request.POST.get(new_password) confirm_password request.POST.get(confirm_password) if new_password ! confirm_password: # 两次输入的密码不一致 error 两次密码输入不一致 return render(request, reset_password.html, {error: error, user: user}) # set_password 方法会对明文进行加盐哈希 user.set_password(new_password) user.save() return redirect(/user/list/) return render(request, reset_password.html, {user: user})在 Django 中set_password()和直接赋值password的区别就在这里前者会自动调用make_password()生成带盐的哈希值后者只是把字符串原样存进去。我一般会在代码审查时重点盯这个位置因为十个人里至少有两个人会在这里翻车。另外要注意的是重置密码后该用户在所有设备上的 session 仍然是有效的如果需要强制下线需要清掉该用户的 session 记录项目里没有做这个处理如果你要部署到生产环境建议补上。4. 单点登录实现同一个身份如何在多个“互相信任”的站点间穿梭4.1 SSO 的核心机制不是密码共享是会话共享单点登录这个功能最容易出现的误区是认为用户在 A 站登录后把用户名和密码传给 B 站B 站再自动登录。这是错误的传输密码会带来巨大的安全隐患。真正的实现思路是多个站点共享同一个认证中心也就是这个项目本身认证中心维护用户的登录 session各站点通过验证这个 session 是否有效来判定用户是否已登录。用这个项目来举例A 站和 B 站都指向同一个 Django 服务它们共用同一个数据库里的 session 表。用户在认证中心登录后浏览器存储了 sessionid 的 cookie。当用户带着这个 cookie 访问 A 站时A 站的后端会去 session 表里查这个 sessionid 是否存在且有数据存在则视为已登录不存在则跳转到认证中心。这涉及到 Cookie 的 Domain 属性问题我这里多说一句。如果 A 站是a.example.comB 站是b.example.com那么认证中心设置在example.com域名下的 cookie 是可以被两个子站共享的只要 cookie 的 Domain 设置为.example.com即可。但如果两个站点是完全没有关联的域名比如a.com和b.com那浏览器不可能把a.com的 cookie 自动发给b.com这时候就要引入 token 机制通过 URL 参数或前端 JavaScript 跨域传递 token。这个项目作为课程设计处理的是同一个 Django 服务下的多站点场景所以要清楚它的边界在哪里。4.2 项目里的认证协议设计思路用一张表维护“信任关系”项目标题里写着“协议设计”这块对应的就是信赖站点与认证中心的信任关系如何维护。从设计的角度一般会有一张表记录哪些站点是被认证中心信任的这张表至少包含站点名称、站点 URL、站点密钥。用户登录认证中心后认证中心生成一个临时 ticket带上这个 ticket 跳转到目标站点目标站点拿着 ticket 到认证中心验证验证通过后建立本地会话。下面是这种 ticket 交互的典型步骤用户访问业务系统 B 的受保护页面B 检测用户未登录重定向到认证中心 A 的登录页同时携带一个回调地址redirect_url用户在 A 登录成功后A 生成一个随机 ticket 并保存A 重定向到redirect_url?ticket随机串B 收到请求后携带 ticket 调用 A 的验证接口A 验证 ticket 有效且未过期返回用户身份信息B 建立自己的本地 session用户后续访问 B 不再需要跳转。在 Django 项目里这个 ticket 可以使用django.core.signing模块生成带签名的 token避免引入额外的第三方库。一个简单的实现思路是这样# 生成 ticket 和验证 ticket 的核心逻辑 from django.core.signing import Signer, BadSignature import time # 初始化签名器SECRET_KEY 从 settings 中读取 signer Signer() def generate_ticket(user_id, target_site): # 把用户 ID、目标站点、时间戳组合起来签名 payload f{user_id}:{target_site}:{int(time.time())} # dumps 会生成带签名和 Base64 编码的字符串 return signer.sign(payload) def verify_ticket(ticket, max_age60): # 验证 ticket 是否有效 try: # unsign 会校验签名防止伪造 original signer.unsign(ticket) user_id, target_site, timestamp original.split(:) # 检查 ticket 是否过期这里限制为 60 秒 if int(time.time()) - int(timestamp) max_age: return None return user_id, target_site except BadSignature: # 签名不合法说明 ticket 被篡改 return None参数说明Signer()默认使用 Django 的SECRET_KEY作为签名密钥所以部署到生产环境前一定要修改 settings.py 里的SECRET_KEY否则攻击者可以伪造任意用户身份的 ticket。max_age60表示 ticket 的有效期只有 60 秒这个时间窗口是合理的太短容易导致网络慢的用户来不及跳转太长则给了攻击者重放攻击的机会。ticket 用完即废认证中心验证一次之后就应失效防止被多次使用。4.3 认证协议中的“信任边界”哪些站点可以接入设计统一身份认证协议时最重要的不是“怎么实现”而是“怎么定义信任”。项目里的协议设计本质上是把“信任”具象化为一张站点白名单。认证中心只给白名单内的站点签发 ticket也只接受来自白名单站点的验证请求。实际业务中我在做这类系统的对接时会要求业务站点提供一个固定回调地址和专属密钥。不回泄露给他人使用的专属密钥。密钥的作用是双向确认业务站点确认认证中心是真的认证中心认证中心确认请求来自已登记的业务站点。实现上认证中心提供一个 view 专门处理 ticket 校验请求请求方需要带上自己的 app_id 和签名参数。核心校验逻辑整理如下# 认证中心的 ticket 校验接口 from django.http import JsonResponse from django.views.decorators.http import require_POST import hashlib # 模拟应用注册表实际项目中应存数据库 TRUSTED_APPS { app_b: {secret: app_b_secret_key, callback: https://b.example.com/callback/}, } require_POST def validate_ticket(request): app_id request.POST.get(app_id) ticket request.POST.get(ticket) sign request.POST.get(sign) # 1. 检查应用是否在白名单中 if app_id not in TRUSTED_APPS: return JsonResponse({code: 4001, msg: 未知的应用ID}) app TRUSTED_APPS[app_id] # 2. 校验签名防止请求被伪造 raw f{app_id}{ticket}{app[secret]} expected_sign hashlib.md5(raw.encode()).hexdigest() if sign ! expected_sign: return JsonResponse({code: 4002, msg: 签名校验失败}) # 3. 校验 ticket 并返回用户信息 result verify_ticket(ticket) if result is None: return JsonResponse({code: 4003, msg: ticket无效或已过期}) user_id, target_site result if target_site ! app_id: return JsonResponse({code: 4004, msg: ticket目标站点不匹配}) # 4. 从数据库获取用户基本信息返回 from django.contrib.auth.models import User user User.objects.get(iduser_id) return JsonResponse({ code: 0, data: { user_id: user.id, username: user.username, } })这段代码展示了协议中“验证”这一步的数据契约。比较关键的是sign参数的设计它是一次 MD5 签名由业务方用自己的密钥计算出来具体算法是对app_id ticket app_secret做拼接后哈希。认证中心用同样的算法再算一遍能对上就说明请求确实来自登记过的业务方。这样即使 ticket 被截获攻击者也没有对应的密钥来伪造sign安全性就有了基础保障。5. 避坑指南跟着源码复现时最容易翻车的五个细节5.1 坑点一登录后跳转路径不对现象用户输入正确的用户名密码登录成功后没有进入主页面而是跳回登录页或者跳到 Django 默认的/accounts/login/页面。原因settings.py中没有配置LOGIN_URL。项目源码里虽然有设置但很多人在复制代码时漏掉了这一行。Django 默认的login_required重定向路径是/accounts/login/如果你的登录页在别的位置自然找不到页面。解决在settings.py中明确加上LOGIN_URL /login/同时确认 URL 配置里login的路径与你设置的一致。改完重启服务再试。5.2 坑点二MySQL 数据库连不上提示认证插件错误现象项目跑起来后第一次访问数据库相关页面直接报错比如django.db.utils.OperationalError: Authentication plugin caching_sha2_password cannot be loaded。原因MySQL 8.0 默认的认证插件是caching_sha2_password而 Django 的 MySQL 客户端库可能版本过旧不认这个插件。解决两种做法任选。一是升级 MySQL 客户端库pip install mysqlclient --upgrade二是在 MySQL 里给用户改回mysql_native_password认证插件。我建议直接升级客户端库因为改认证插件会降低安全性毕业设计答辩时也容易被老师追问。5.3 坑点三set_password和直接赋值不区分现象用户在后台被修改了密码后再也无法登录提示用户名或密码错误。原因开发者在编辑用户时用了user.password request.POST.get(password)直接赋值。Django 校验密码时会用check_password()方法它对存入的哈希值再做一次算法匹配而直接赋值的明文永远无法匹配上。解决强制使用user.set_password(明文)方法来更新密码。代码审查时看到任何对user.password直接赋值的操作一律驳回。5.4 坑点四session 表数据量膨胀导致登录越来越慢现象系统运行一段时间后登录响应速度明显变慢数据库的django_session表里有大量过期数据。原因Django 默认的 session 清理机制依赖clearsessions命令定时执行。如果你没有配置定时任务过期的 session 记录会一直留在表里。每小时登录几千次的系统一周就能积累几万条废数据。解决配置系统的定时任务比如 Linux 的 crontab 每天执行一次python manage.py clearsessions。如果你用的是 Windows 服务器可以用计划任务。命令本身很安全只会删除已过期的 session不影响在线用户。5.5 坑点五统一身份认证时的跨域问题现象在本地测试时单点登录一切正常部署到两台不同域名的服务器后A 站登录成功跳转到 B 站还是提示未登录。原因cookie 的 Domain 设置不对。浏览器只会按照 cookie 的 Domain 属性决定是否发送该 cookie。如果 A 站和 B 站的域名完全不同你需要在认证中心设置SESSION_COOKIE_DOMAIN为共同的父级域名例如.example.com。解决在settings.py中设置SESSION_COOKIE_DOMAIN .example.com并且保证 A、B 站点都使用.example.com作为二级域名。如果两个站点完全没有域名关系这套方案不够用需要引入 CAS 或 OAuth2 协议来完成跨域这就超出本项目的能力边界了。6. 验证与调试技巧用 Django 自带能力和浏览器开发者工具把认证链路看穿这个项目做完之后如何证明单点登录是真的生效的而不是表面能跳转我每次做完认证类项目都有一套固定的验证流程确认每一环都没有漏。第一步是验证 session 是否真正写入。启动项目后在浏览器里完成一次登录然后打开django_session表正常情况下会看到一条新的记录。记录里的session_data是一串 Base64 编码的数据你可以用 Python 把它解出来看内容。在 Django shell 里执行下面这段代码# 在项目根目录执行 python manage.py shell from django.contrib.sessions.models import Session from django.contrib.auth.models import User # 取出最近一条 session 记录 latest_session Session.objects.latest(expire_date) # session_data 是编码后的数据 print(latest_session.session_data) print(过期时间:, latest_session.expire_date) # 解码 session 数据查看里面存了什么 from django.contrib.sessions.serializers import JSONSerializer import base64 decoded JSONSerializer().loads(base64.b64decode(latest_session.session_data.encode())) # 打印 session 内容正常情况下包含 _auth_user_id 和 _auth_user_backend print(session 内容:, decoded)如果解码后能看到_auth_user_id这个键说明 Django 的login()已经把用户 ID 写进了 session认证链路的第一步是通的。如果这里只有_auth_user_backend而缺少_auth_user_id说明login()没有正确绑定用户回到前面检查authenticate()的返回值。第二步是验证受限页面是否真的被拦截。在未登录状态下直接访问/index/观察浏览器的网络请求。正常情况下会发生 302 重定向最终落在登录页。用 Django 测试客户端也能做到同样的验证不需要打开浏览器# 在项目根目录执行 python manage.py shell from django.test import Client # 模拟未登录用户访问受限页面 c Client() # 访问首页或任意受保护的 URL response c.get(/index/) # 打印状态码302 表示被重定向 print(未登录时状态码:, response.status_code) # 打印重定向的目标地址应该指向登录页 print(重定向到:, response.url) # 模拟登录后再访问 from django.contrib.auth.models import User # 创建一个测试用户 user User.objects.create_user(test_debug, testexample.com, test_password_123) # 客户端模拟登录 c.login(usernametest_debug, passwordtest_password_123) response c.get(/index/) print(登录后状态码:, response.status_code)需要说明的是Client.login()在测试时会直接调用authenticate()并写入 session效果等于走了一遍正常登录。这种方法非常适合自动化验证不需要每次都手动在浏览器里点一遍。第三步是检查 cookie 的安全属性。打开浏览器开发者工具切到 Application 面板找到 Cookies查看项目域名下的 cookie。我通常关注三个点HttpOnly是否为 True、Secure是否按需设置、SameSite的属性值。开发环境下Secure一般是 False这个没问题但部署到 HTTPS 环境时必须改为 True。SameSite默认是 Lax这个配置能防止 CSRF 攻击不要轻易改成 None。最后说一个我自己的习惯每次改完认证相关的代码我都会强制走一遍“清 cookie → 登录主站 → 跳转子站 → 刷新子站页面”的完整流程确认没有出现任何一次重复输入密码的操作。如果子站页面里嵌入了 iframe 或者有跨域的 Ajax 请求我还会把浏览器的 Network 面板打开逐个请求检查有没有返回 401 或 302 的异常状态。统一身份认证这个项目表面上是给用户减少一次登录操作实际上是要求系统内部确保每一次身份校验都能快速、准确地走完。从那以后我每次做认证类开发都会把这套验证流程固定下来不走完不提交代码。这个项目在 SSO 的完整实现上覆盖了核心链路适合做课设也适合做起步参照希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。