简介:基于Python与Django开发的毕业设计项目,面向计算机相关专业学生和Web后端开发者,完整提供web服务统一身份认证协议设计方案。项目采用B/S架构与MySQL数据库,围绕用户管理和单点登录两大核心功能展开:用户模块支持身份信息的查看、修改与删除;单点登录模块通过一次服务端认证即可自动访问多信任站点,从根源解决多系统重复输入账号密码、身份数据分散维护等痛点。压缩包共包含603个文件,包体约8.34MB,文件类型以Python源码(.py/.pyc)、前端页面(.html/.css/.js)、数据库文件(.db/.sqlite3)为主,另有198张界面截图和96个演示动图展示页面与交互流程,并附带说明文档、演示视频及启动脚本,便于快速运行和对照学习。资源已有904人学习下载。从中可获取完整Django工程代码、数据库表结构、登录认证流程设计说明,以及多站点自动登录的权限分配策略,适合毕业设计参考、课程项目复现或统一认证系统二次开发。
1. 一个毕业设计标题背后的真需求:统一身份认证协议到底要解决什么问题
“统一身份认证协议设计”翻译成大白话,就是让两个以上互不相干的 Web 系统,共用同一个登录入口,用户在一个系统登录后,再去访问其他系统不用重新输账号密码。这个场景在学校、企业、政务类系统里到处都是,而 Python 是实现它的最短路径——用 Flask 或 Django 搭一个认证中心,再带两个模拟客户端,就能把整条链路跑通,这也是这类毕业设计常年被选的原因。源码、说明文档、演示视频三件套,恰好覆盖了从代码到答辩展示的完整交付物。我会用这套标题常见的实现思路,从协议选型讲到数据模型,再到登录链路细节和几个容易在答辩现场翻车的坑,帮你把整个方案真正盘清楚。
2. 选型先于编码:Python 环境下统一身份认证的三种协议路线
动手写代码之前,第一个绕不开的问题是:统一身份认证到底用哪种协议来实现。很多毕业设计最后做成了“一个登录页面 + 一张用户表”,那不是统一身份认证,只是普通登录。真正意义上的方案至少有三种常见路线,选型不同,代码量和答辩深度完全不同。
2.1 CAS:老牌重协议,适合明确的多系统对接
CAS(Central Authentication Service)是耶鲁大学开源的那套单点登录协议,核心是“票据(Ticket)”机制。用户访问业务系统 A,A 发现未登录,重定向到认证中心;认证中心校验账号密码后,生成一个 service ticket(ST),带着 ST 跳回 A;A 拿着 ST 再向认证中心后台接口校验一次,校验通过才算登录完成。
这个协议的思想非常清晰——业务系统不直接触碰用户密码,密码只经过认证中心。它的缺点是重定向很多,每个对接的系统都要实现 CAS Client 端逻辑。Python 里常见做法是用python-cas库,或者干脆在 Flask 里自己写两个端点的跳转逻辑。对于一个毕业设计来说,CAS 的好处是答辩时很容易讲清楚:图一画,跳转关系一目了然。它的坏处是你需要至少实现三个角色:认证中心、客户端 A、客户端 B,工程量大一点,但演示效果扎实。
我一般会建议:如果你的目标系统是两个以上、且你希望展示“传统单点登录”的教科书流程,选 CAS。它的参数集中在三个地方:认证中心地址、客户端标识、ticket 校验回掉地址。这三个配置在部署时最容易出错,后面避坑章节会专门说。
2.2 OAuth2.0 / OpenID Connect:现代轻协议,适合开放平台与认证中台
另一个大方向是 OAuth2.0,甚至直接上 OpenID Connect。OAuth2 的本意是授权,不是标准意义上的“认证”,所以实际工程里通常把 OAuth2 和 OIDC 结合使用,OAuth2 负责颁发 access_token 和 refresh_token,OIDC 再额外发一个 ID Token,里面带用户身份信息。这套组合是现在互联网应用对接微信、GitHub、企业微信登录时的事实标准。
它的核心流程是授权码模式(authorization code):客户端跳转到认证中心,用户登录并授权,认证中心带着授权码(code)回跳客户端;客户端再用 code 加上自己的 client_id、client_secret 向认证中心换 token。token 可能是 JWT 格式,也可能是不透明字符串搭配 introspection 接口。如果你是毕业设计,用 OAuth2 + JWT 的组合相当合适,因为现在主流方案都这么做,简历上也能多条“熟悉 OAuth2 授权码流程和 JWT 令牌机制”的亮点。
OAuth2 有一个特别容易混淆的点:它定义的角色有四个——资源所有者、客户端、授权服务器、资源服务器。在统一身份认证的场景里,授权服务器就是认证中心,资源服务器是你要对接的业务系统,客户端是浏览器或某个应用。这个角色关系理不顺,后面看报错信息会非常痛苦。我在实际项目里习惯画一张角色对应表再动手,强烈建议你也这么干。
| 协议角色 | 本项目中的对应物 | 职责 |
|---|---|---|
| 资源所有者 | 用户 | 提供账号密码授权登录 |
| 客户端 | 模拟业务系统 A/B | 发起登录请求、持有 token |
| 授权服务器 | Python 认证中心服务 | 校验身份、颁发 code 和 token |
| 资源服务器 | 模拟业务系统 A/B 的业务接口 | 校验 token、返回用户数据 |
2.3 JWT 落地细节:令牌格式、过期时间、签名算法的一次选对
不管选 OAuth2 还是自己简化做,JWT 在 Python 中的落地都是躲不开的环节。JWT 由三部分构成:Header(算法信息)、Payload(用户信息和声明)、Signature(签名)。Python 端最常见的库是PyJWT,一行签发,一行校验。
import jwt from datetime import datetime, timedelta SECRET_KEY = "your-256-bit-secret" payload = { "user_id": 10001, "username": "demo_user", "scope": ["profile", "email"], "iss": "auth-center", # 签发者:认证中心标识 "aud": "web-service-demo", # 接收方:只允许目标 web 服务校验 "iat": datetime.utcnow(), # 签发时间 "exp": datetime.utcnow() + timedelta(hours=2) # 过期时间 } token = jwt.encode(payload, SECRET_KEY, algorithm="HS256") print(token)这段代码里有两个参数属于“选错就翻车”级别的:aud和exp。aud是用来限制 token 只能在指定接收方使用的,如果你在认证中心签发时不带aud,资源服务端校验时又强制校验它,两边对不上,请求就必然失败。exp建议设置在两小时左右,不要为了演示方便设成 30 天,答辩时如果讲不清长期 token 的安全风险,反而会被追问卡住。
校验端用jwt.decode(token, SECRET_KEY, algorithms=["HS256"], audience="web-service-demo"),需要注意algorithms必须显式传入,而且要和签发时保持一致。工作中见过太多因为签发用的 HS256、校验时默认 RS256 导致签名验证失败的例子,这属于“配置黑匣子”,报错信息又不直接说清是算法不匹配,排查起来特别耗时间。
3. 把源码跑起来:目录结构、数据模型与最小可运行配置
拿到一个 Python 毕设的压缩包,第一步不是看代码,而是看目录结构。说明文档和演示视频是给你看效果的,源码才是你要改的东西。我打开这类项目压缩包时,习惯先按下面的顺序理清“哪些文件是核心、哪些是演示辅助”。
3.1 目录结构与 config:源码包里通常有什么,哪些文件不能随便删
一个规范的统一身份认证协议毕设,源码部分通常包含认证中心、模拟业务系统、公共配置三块。常见目录如下:
| 路径 | 作用 | 是否可以调整 |
|---|---|---|
app.py/server.py | 认证中心与业务系统入口,一般 Flask 实现 | 核心,重命名需同步改启动方式 |
models.py/db.py | 用户、客户端、令牌的数据表定义 | 核心,改字段需同步迁移数据库 |
config.py | 公私钥、JWT 密钥、过期时间、数据库连接 | 核心,部署时必改 |
templates/ | 登录页和回调页 | 可按需改样式 |
client_a/client_b/ | 两个模拟业务系统 | 演示链路不可或缺 |
README.md+ 说明文档 | 环境依赖和启动步骤 | 按实际环境核对 |
演示视频.mp4 | 答辩用的效果展示 | 别急着打开,先看文档 |
我的建议是:先打开config.py和README.md,把端口号、密钥、数据库路径抄出来,再启动项目。这个习惯能帮你快速判断项目跑不起来到底是环境问题还是代码问题。别一上来就运行app.py,报错信息铺天盖地时你根本不知道从哪查。
3.2 数据模型:用户表、客户端表、令牌表怎么设计才不留坑
统一身份认证的数据表设计,三个关键表就够了:用户表、客户端注册表、令牌表。用户表存账号密码哈希;客户端表存接入的业务系统标识和密钥;令牌表用于记录签发的 JWT 状态,方便后续踢人下线、做退出登录。下面这份 Flask-SQLAlchemy 模型是这类毕设里最稳妥的设计,字段不多,但覆盖了核心场景。
from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() class User(db.Model): __tablename__ = "sso_users" id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, nullable=False) password_hash = db.Column(db.String(128), nullable=False) # 只存哈希,绝不存明文 email = db.Column(db.String(120)) active = db.Column(db.SmallInteger, default=1) # 1:启用 0:禁用 created_at = db.Column(db.DateTime, default=datetime.utcnow) class Client(db.Model): __tablename__ = "sso_clients" id = db.Column(db.Integer, primary_key=True) client_id = db.Column(db.String(32), unique=True, nullable=False) client_secret = db.Column(db.String(64), nullable=False) # 业务系统密钥 name = db.Column(db.String(64)) redirect_uris = db.Column(db.Text) # 逗号分隔的回跳地址列表 class TokenRecord(db.Model): __tablename__ = "sso_token_records" id = db.Column(db.Integer, primary_key=True) jti = db.Column(db.String(36), unique=True, nullable=False) # JWT 的唯一 ID,用于撤回 user_id = db.Column(db.Integer, db.ForeignKey("sso_users.id")) client_id = db.Column(db.String(32), nullable=False) issued_at = db.Column(db.DateTime, default=datetime.utcnow) expires_at = db.Column(db.DateTime, nullable=False) revoked = db.Column(db.SmallInteger, default=0) # 1:已注销这份模型有一个很容易被你忽略的细节:jti字段。JWT 本身是无状态的,服务端如果完全不留痕,你没法让你签发的某个 token 在过期前失效。加了TokenRecord表后,每次签发 JWT 就往表里插一条记录,退出登录时把revoked置 1,资源服务端校验时随手查一下这张表,就能实现“踢人下线”。这个点要是做进毕设里,答辩时非常加分,因为大部分同学根本没想到 token 撤回这件事。
3.3 最小可运行配置:没有演示环境也能先跑通的两条命令
这类项目拿到手,第一件事是在本地把环境搭起来。我习惯用虚拟环境装依赖,避免和系统 Python 版本冲突。以 Windows 或 Linux 都兼容为例:
# 建议 Python 3.8~3.10 环境,太新的版本某些依赖还没适配 python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt python app.py运行后看到类似Running on http://127.0.0.1:5000的输出,说明认证中心已经起来。绝大多数毕设都会把认证中心和模拟客户端放在不同端口,比如认证中心 5000,客户端 A 5001,客户端 B 5002。如果只有一个端口,说明它可能用了代理转发或同一个 APP 里的多蓝图实现。这里记住一个点:统一身份认证的演示场景一定需要两个以上服务配合跳转,单进程单端口跑通的“统一登录”,本质上只是普通登录。
如果启动报错,先确认三件事:依赖是否装全、数据库文件是否存在、config.py里的绝对路径是否指向当前环境。我见过最典型的翻车就是毕设源码里写死了 SQLite 的相对路径sso.db,换个目录启动后数据库重建了,之前文档里写的测试账号全部失效,登录页永远提示密码错误。
4. 登录流的核心链路:从 Redirect 到 Token 再到用户信息
统一身份认证的全部戏肉,都在“跳转 + 回跳 + 换票 + 验票”这个过程里。演示视频看起来只是输入一次账号密码就进入了两个系统,但背后的协议交互必须分步讲清楚,因为答辩老师的问题几乎全集中在这条链路上。
4.1 CAS 登录票据 TGT 与 ST:一次跳转背后的协议顺序
如果采用的是 CAS 思路,登录链路会分成两步。第一步是客户端把用户重定向到认证中心,认证中心核验密码后,在服务端会话里种下一个 TGT(Ticket Granting Ticket),代表这个用户已经通过了认证。第二步是认证中心生成一个一次性票据 ST(Service Ticket),带着它重定向回刚才那个客户端的回调地址。注意 ST 是单次有效的,一旦被客户端拿去校验过,第二次再用就会被拒绝。
CAS 的服务端实现里,/login和/serviceValidate是两个最核心的端点。/login负责展示登录页并处理账号密码,/serviceValidate负责校验客户端拿来的 ST 是否有效、是否过期、是否对得上service参数。如果你是手写这套逻辑,代码主体是重定向和请求转发,几乎没有复杂计算,但服务端的 session 管理不能省——不然用户登录后一刷新页面,登录态就丢了。我在评估一个 CAS 类毕设源码时,一般直接搜索这两个端点的处理函数,如果找得到并且 session 有维护,说明协议流程是完整的。
4.2 OAuth2 授权码模式的完整时序与代码骨架
OAuth2 授权码模式的相对现代且演示效果清晰。我之前用Authlib库的 Flask 集成来实现,只需要注册一个远程应用和两个路由,就能把整套跳转流程托管起来。
from flask import Flask, redirect, session, url_for from authlib.integrations.flask_client import OAuth from authlib.integrations.base_client import OAuthError app = Flask(__name__) app.secret_key = "!replace-this-with-random-string" oauth = OAuth(app) oauth.register( name="sso", client_id="web-client-a", client_secret="client-a-secret", server_metadata_url="http://127.0.0.1:5000/.well-known/openid-configuration", client_kwargs={"scope": "openid profile email"}, ) @app.route("/login") def login(): redirect_uri = url_for("callback", _external=True) # 第一步:跳转到认证中心,引导用户登录并授权 return oauth.sso.authorize_redirect(redirect_uri) @app.route("/callback") def callback(): try: # 第二步:认证中心回跳带 code,这里用 code 换取 access_token token = oauth.sso.authorize_access_token() # 第三步:解析 ID Token 拿到用户基本信息 user_info = oauth.sso.parse_id_token(token) session["user"] = user_info return redirect("/dashboard") except OAuthError as e: return f"OAuth error: {e}", 400这里有两个参数必须解释清楚:redirect_uri和scope。redirect_uri必须和认证中心里注册的回跳地址完全一致,注意协议、域名、端口都不能多一个字符,否则认证中心会直接拒绝回跳,浏览器卡在空白页。scope决定了你能拿到哪些用户数据,openid是 OIDC 必须包含的,profile和email是额外申请的用户信息,认证中心没配置这些 scope 的授权范围,解析时会报错。
这个代码骨架的价值在于:它把复杂的协议跳转封装成了authorize_redirect和authorize_access_token两个调用,但你自己心里必须清楚每一步对应什么。答辩时如果被问“token 在哪里换的”,你要能定位到回调路由里的那一行请求代码,而不是只回答“用了 Authlib”。
4.3 演示视频里不会告诉你的:Token 校验要在资源服务端做一次
很多毕设演示视频里,登录之后直接跳转业务系统首页,看起来一切正常。但这里藏着一个最容易在答辩时被追问的点:业务系统怎么确认用户身份是真的?如果业务系统只是“信任”认证中心签发的 token,而不去校验签名和过期时间,那整个认证体系等于没有。
真正的落地必须做到:业务系统在受保护接口的入口处校验 token 的签名、过期时间、aud声明。用 PyJWT 的话,代码只有几行,但责任重大。
def verify_token(token: str) -> dict: try: payload = jwt.decode( token, PUBLIC_KEY, # 如果使用 RS256,这里放公钥;HS256 则放对称密钥 algorithms=["RS256"], audience="web-service-demo", options={"require": ["exp", "iss", "aud"]} ) return payload except jwt.ExpiredSignatureError: raise PermissionError("token expired") except jwt.InvalidAudienceError: raise PermissionError("wrong audience") except jwt.InvalidTokenError: raise PermissionError("invalid token")这段代码里的PUBLIC_KEY是关键:如果毕设用的是非对称加密算法 RS256,认证中心持有私钥签发 token,各个业务系统持有公钥做校验。私钥绝不能在业务系统代码里出现,否则一个业务系统被攻破,所有认证体系全废。代码里options={"require": ["exp", "iss", "aud"]}的作用是强制要求 token 带三个声明,缺少任何一个直接认定非法,这是防止伪造 token 的兜底手段。
你需要特别注意:校验逻辑必须放在每个受保护接口的开始处,而不是只写一个verify_token函数留着不用。毕业设计源码里要么用一个装饰器统一给路由加校验,要么在每个视图函数开头调用。如果只是写完校验函数但没有任何路由用它,那演示视频里的登录态其实等同虚设,这是答辩时老师一眼就能戳穿的点。
5. 常见问题与避坑指南:能让答辩现场翻车的五个细节
这套方案我实际搭过不止一次,下面这些坑都是踩过或看同学踩过的,每条按“现象、原因、解决”的顺序说清楚,给你做一次排雷。
5.1 登录后无限重定向循环,浏览器一直在两个地址间跳
现象就是访问客户端后,URL 在127.0.0.1:5001/login和127.0.0.1:5000/login之间来回跳,最后浏览器提示“此页面无法正常工作”。原因通常是重定向回跳地址拼错了,比如认证中心里注册的回调地址是http://127.0.0.1:5001/callback,客户端代码里url_for("callback", _external=True)却生成了http://localhost:5001/callback,主机名和 IP 不一致导致认证中心根本不认这个回跳地址。解决方法是统一使用 localhost 或统一使用 127.0.0.1,不要去猜,在配置文件和客户端代码里搜索所有localhost和127.0.0.1,全部改为同一个值。
5.2 ST 票据第二次访问就报无效
CAS 模式下,刷新一次页面就提示 ticket invalid。原因是 ST 设计为单次有效,客户端回跳后首次校验即被标记为已使用,再刷新等于拿一个已经作废的票据去校验。解决思路是校验成功后立即把浏览器地址栏里的 tick 参数去掉,后续访问走 session 里的登录态,不再依赖 URL 上的票据参数。你可以在校验成功代码里使用redirect(url_for("dashboard")),而不是继续停留在带 ticket 的回跳地址上。
5.3 JWT 明明刚签发,校验却一直报签名错误
现象是认证中心签发的 token,拿到资源服务端校验时抛出InvalidSignatureError。原因大概率是签发和校验两个地方用的算法不一致,或者密钥有问题。最常见的是签发用HS256、校验代码却写死RS256;还有一种可能是有两个config.py,认证中心和模拟客户端各用各的配置文件,密钥串不对付。解决方法是把签名算法和密钥统一放一份公共配置里,两边都从同一份配置读取,并显式打印 token 的前几个字符确认是不同的 token。这个坑排查相当耗时间,属于“黑匣子”式问题,建议直接在认证中心加一行日志记录algorithm和密钥指纹。
5.4 登录页能打开,但 demo 用户永远密码错误
现象是文档里给的测试账号无法登录,或者第一次能登录,重启后失效。原因基本是 SQLite 数据库文件的路径问题。很多毕设代码里写的是相对的数据库路径sqlite:///sso.db,项目换目录启动时,SQLite 会在当前工作目录重新建一个空库,原账号全不见。解决方法是改成绝对路径,或者在 README 里明确说明用flask init-db之类的初始化命令。另外也检查一下是不是有数据库初始化脚本没有执行。
5.5 文档和源码版本对不上
这是毕设源码里非常普遍的情况:说明文档里写的是老版本的接口名,代码已经重构成新写法,照着文档操作自然跑不通。解决方法是拿到项目后先通读一遍源码里认证中心的路由,找到实际存在的/login、/callback、/logout端点,再回头对比文档,而不是机械执行文档里的 curl 命令。如果答辩前发现文档和源码的确不一致,必须自己重新写一份最小可运行的说明,把端口、依赖、账号三个信息补全,这是保证演示现场不翻车的最后一道防线。
6. 从“能跑通”到“值得讲”:三个进阶验证方向与演示脚本的设计
当你把统一身份认证的主流程跑通,离一个高完成度的毕业设计还差一步——把安全性和可验证性补上。这里给你一个优先级很高的增强方案:实现 token 的主动撤回与退出登录。JWT 无状态是优点也是缺点,你没法让一个已经签发的 token 立刻失效。配合前面数据模型里的TokenRecord表,在退出登录的视图函数里把对应jti的 revoked 字段置为 1,然后在资源服务端校验时加一次查表判断,就能做到真正意义的“踢人下线”。这个功能在校验接口里只需要多写两行查询,但答辩演示时效果非常直观——用户退出后,另一个业务系统立刻无响应。
验证方法我建议走一条固定演示路径:先在浏览器打开业务系统 A,未登录时会被重定向到认证中心登录页,输入测试账号密码后跳回 A 的首页;然后新开标签页直接访问业务系统 B,确认无需重复登录;最后点击退出登录,再访问 A 或 B,确认被重新拦截回认证中心。这条路径覆盖了“认证、单点登录、退出”三条核心链路,比单纯的登录演示有说服力得多。
另一个小而有效的检查点是从协议角度验证重放攻击:把之前登录成功后的 callback 完整 URL 复制出来,重新访问一次,正常情况应该要么报 ticket 已使用,要么被重定向到登录页重新走流程。如果这个 URL 还能直接进入系统,说明你的票据状态管理或登录态校验有漏洞,需要回到第 4 章的逻辑补强。
我自己的习惯是最后再把SECRET_KEY换成环境变量读取,而不是硬编码在源码里,这个细节虽然不参与演示,但写进论文的技术安全分析里很加分。希望这篇能把你从“配置怎么都对不上”的血泪排查里捞出来,把精力真正花在协议理解和设计本身,祝你顺利。
本文还有配套的精品资源,点击获取