Python 统一身份认证协议设计:CAS、OAuth2 与 JWT 实站指南
简介基于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老牌重协议适合明确的多系统对接CASCentral Authentication Service是耶鲁大学开源的那套单点登录协议核心是“票据Ticket”机制。用户访问业务系统 AA 发现未登录重定向到认证中心认证中心校验账号密码后生成一个 service ticketST带着 ST 跳回 AA 拿着 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_tokenOIDC 再额外发一个 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(hours2) # 过期时间 } token jwt.encode(payload, SECRET_KEY, algorithmHS256) print(token)这段代码里有两个参数属于“选错就翻车”级别的aud和exp。aud是用来限制 token 只能在指定接收方使用的如果你在认证中心签发时不带aud资源服务端校验时又强制校验它两边对不上请求就必然失败。exp建议设置在两小时左右不要为了演示方便设成 30 天答辩时如果讲不清长期 token 的安全风险反而会被追问卡住。校验端用jwt.decode(token, SECRET_KEY, algorithms[HS256], audienceweb-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_keyTrue) username db.Column(db.String(64), uniqueTrue, nullableFalse) password_hash db.Column(db.String(128), nullableFalse) # 只存哈希绝不存明文 email db.Column(db.String(120)) active db.Column(db.SmallInteger, default1) # 1:启用 0:禁用 created_at db.Column(db.DateTime, defaultdatetime.utcnow) class Client(db.Model): __tablename__ sso_clients id db.Column(db.Integer, primary_keyTrue) client_id db.Column(db.String(32), uniqueTrue, nullableFalse) client_secret db.Column(db.String(64), nullableFalse) # 业务系统密钥 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_keyTrue) jti db.Column(db.String(36), uniqueTrue, nullableFalse) # JWT 的唯一 ID用于撤回 user_id db.Column(db.Integer, db.ForeignKey(sso_users.id)) client_id db.Column(db.String(32), nullableFalse) issued_at db.Column(db.DateTime, defaultdatetime.utcnow) expires_at db.Column(db.DateTime, nullableFalse) revoked db.Column(db.SmallInteger, default0) # 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 思路登录链路会分成两步。第一步是客户端把用户重定向到认证中心认证中心核验密码后在服务端会话里种下一个 TGTTicket Granting Ticket代表这个用户已经通过了认证。第二步是认证中心生成一个一次性票据 STService 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( namesso, client_idweb-client-a, client_secretclient-a-secret, server_metadata_urlhttp://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, _externalTrue) # 第一步跳转到认证中心引导用户登录并授权 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 fOAuth 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], audienceweb-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, _externalTrue)却生成了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换成环境变量读取而不是硬编码在源码里这个细节虽然不参与演示但写进论文的技术安全分析里很加分。希望这篇能把你从“配置怎么都对不上”的血泪排查里捞出来把精力真正花在协议理解和设计本身祝你顺利。本文还有配套的精品资源点击获取