☰
Python统一身份认证服务设计与实现:JWT多端登录毕设项目全解析
2026/10/9 8:54:49 网站建设 项目流程

最近来问毕设选题的同学特别多,十个里有七八个都在犹豫做什么才能既好写又能顺利答辩。我个人的建议非常明确:如果你不想被简单管理系统卷死,又没精力挑战算法难度,那用 Python 做一个统一身份认证服务,绝对是性价比很高的选择。它听起来抽象,拆开看就是一套让多个系统共用同一份账号密码的登录鉴权中心。这篇就完整聊聊我做完这个项目的思路、代码、踩坑,以及如何把它和 JAVA、PHP、小程序、爬虫、数据可视化这些方向串起来,作为计算机毕设的完整作品。

标题里提到的"白嫖源码 + 演示录像"是真的,我把自己做完的这套工程整理成了一份可直接运行的毕设资源包,里面包含全部源码、数据库脚本、部署说明和演示视频。项目本身不复杂,但把认证、鉴权、多端接入、日志可视化这些点全打通之后,你会发现它远比"XX管理系统"这种传统毕设有讲头。下面我把整个项目的设计逻辑和实操过程拆开,一步步说清楚。

1. 为什么我推荐把"统一身份认证"作为毕设主项目

1.1 它到底解决的是什么问题

很多同学一开始不清楚统一身份认证的价值,我先用大白话解释一下。假设你同时做了三样东西:一个后台管理网站、一个微信小程序商城、一个爬虫采集脚本。按照常见的做法,每个系统各自建一张用户表,各搞一套注册登录。用户要在三个地方记三套账号密码,你自己也要维护三份用户数据,改密码得改三处。

统一身份认证服务做的事情,就是把"账号密码"这一套东西全部收拢到一个独立服务里。其他系统不再自己注册登录,而是通过接口把登录请求转给认证服务。认证服务确认用户身份后,签发一个 Token,各系统拿着这个 Token 请求受保护的接口。这样一来,用户只需要记一个账号,所有接入的系统都能用,你在任何一处修改密码,其他系统立刻生效。

这就像写字楼的统一门禁:你不需要在每个办公室门口各配一把钥匙,只要前台发一张工牌,所有能刷的门都能进。毕设答辩时把这个类比讲给老师听,对方很快就能 get 到你做的到底是什么。

1.2 答辩时的含金量体现在哪里

老师评判毕设,很看重题目本身有没有"技术纵深"。写一个"图书管理系统",讲的内容无非是增删改查,翻来覆去没有可扩展的点。但"统一身份认证服务"这个题目天然带有几个高频考点:

  • Token 的生成、解析、过期和刷新机制;
  • 密码的哈希存储与防暴力破解策略;
  • 基于角色的访问控制(RBAC)模型;
  • 多端接入时的跨域与请求头设计;
  • 认证日志的采集与可视化展示。

每一个拿出来都能单独扩展成一节答辩问题,而且都有学术界和工业界的成熟方案可以引用。老师一听就知道你不是在纯堆 CRUD,而是接触到了真实后端项目里最核心的模块。

1.3 它适配哪些毕设方向

我做过一个汇总表格,方便你判断自己的方向能不能挂靠这套服务:

方向在毕设中的角色如何与统一认证结合
JAVA / PHP业务系统子端接入认证服务,作为受保护的前后端项目
微信小程序移动端入口通过 wx.login 换取本地 Token,叫醒小程序去访问认证服务
爬虫数据采集工具使用认证服务签发的 Token 调用数据接口,校验脚本身份
APP(含 Android/iOS)移动客户端登录时调用认证接口,存储 Token,后续请求携带
数据可视化认证日志大屏通过 ECharts 等绘制登录趋势、用户活跃、来源地域分布
C# / C++ 桌面程序桌面客户端用 HTTP 请求接入认证服务,完成本地登录状态绑定

所以你看,自己用什么语言写子系统不关键,认证服务能作为公共底座把它们全部串联起来,整体作品的故事就完整了。

2. 项目功能拆解:一台认证服务管住所有系统

2.1 最小闭环长什么样

做毕设不用一上来就实现 OAuth2、单点登录协议、双因素认证这些东西,先跑通一个最小闭环,后面再逐步加功能。我建议的最小功能集是:

  • 注册:用户提交用户名和密码,服务端校验并存储;
  • 登录:校验用户名密码,签发 Access Token 和 Refresh Token;
  • 校验:受保护接口解析请求头里的 Token,判断是否有效;
  • 刷新:Access Token 过期后,用 Refresh Token 换新的;
  • 登出:将 Token 加入黑名单或删除刷新 Token,使其失效;
  • 用户信息:通过 Token 获取当前登录用户的资料。

功能列表比想象中短,但每一个都可以讲出很多细节。我当时的做法是先把这 6 个接口跑通,再补充分页、密码修改、角色分配和日志记录,工作量安排非常合理。

2.2 多端接入的界面形态

为了让老师觉得系统真实可用,建议至少做两个端:一个 PC 网页管理端,一个微信小程序端。网页端用来演示后端管理员功能,小程序端用来演示 C 端用户登录。爬虫脚本和数据可视化可以作为补充展示。

演示的时候,先打开 PC 端的登录页,输入刚才注册的账号密码;同一个账号再在小程序里登录也能成功,而且两边看到的个人信息完全一致,这就直观体现了"统一"的含义。图表数据可以由认证服务记录的日志实时生成,展示最近一小时内登录成功的次数和失败次数。

2.3 演示录像怎么编排更抓人

标题里强调"演示录像",我建议录像不要从头到尾录操作,那样冗长且没有重点。我给你一个可以照抄的脚本:

  1. 痛点引入:一句话说"传统多系统登录账号不统一的问题";
  2. 启动服务:命令行展示认证服务启动,数据库连接成功;
  3. 注册账号:录注册过程,故意输入一次错误密码,展示校验逻辑;
  4. 登录成功:PC 端登录,F12 打开开发者工具,展示请求头里的 Token;
  5. 多端同步:切换到小程序模拟器,用同样的账号登录,展示两者信息一致;
  6. 访问受保护接口:用 Postman 模拟爬虫脚本调用接口,不带 Token 返回 401,带 Token 返回数据;
  7. 数据可视化:打开日志大屏,展示刚才产生的登录记录。

这一套录下来大概十分钟,节奏紧凑,信息量大,老师看完就会觉得项目完整度非常高。

3. 技术选型与数据库模型:不堆技术,把基础打结实

3.1 Flask 还是 Django

很多同学纠结用 Flask 还是 Django,我的选择是 Flask。理由是 Flask 足够轻,路由和视图函数直观,Token 的签发和校验完全由自己控制,方便在答辩时把原理说清楚。Django 虽然内置了用户认证系统和 Admin 后台,但它把太多细节封装掉了,你很难在答辩时解释"Token 到底在里面是怎么流转的"。

如果学校要求必须用企业级框架,Django 也完全可以跑,只是需要在配置里替换默认的 session 认证方案,改成自定义的 JWT 中间件。两者对比大致如下:

对比项Flask + JWTDjango + 自定义认证
上手速度快,核心逻辑全透明较快,但嵌套封装多
默认功能少,自己补多,自带 Admin
答辩讲述容易讲清 Token 原理容易扯到 Django 内部机制
适合场景演示为主加分项很多的项目

我自己最终选了 Flask + SQLAlchemy + PyJWT,代码量不大,架构足够清晰。

3.2 数据库表设计

数据库我设计成五张核心表:用户表(users)、角色表(roles)、权限表(permissions)、用户角色关联表(user_roles)、角色权限关联表(role_permissions)。另外加了两张辅助表:客户端应用表(applications)和认证日志表(auth_logs)。

用户表主要字段如下:

CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username VARCHAR(64) UNIQUE NOT NULL, password_hash VARCHAR(128) NOT NULL, email VARCHAR(120), is_active BOOLEAN DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );

password_hash 存的是哈希后的密码,绝不存明文。auth_logs 表用来记录每次登录/登出的 IP、时间、结果、操作类型,后面可视化就用它当数据源。

3.3 密码加密方案

千万不要用 MD5 或者明文存密码,这是毕设答辩里最容易被老师一眼看穿的扣分点。我用的是 Werkzeug 自带的generate_password_hash,默认算法是 pbkdf2:sha256,内部自动加盐。这样做的好处是:

  • 每次哈希结果都带随机盐,同样密码两次哈希结果不一样;
  • 校验时调用check_password_hash,自动把盐提取出来重新计算;
  • 攻击者即使拿到数据库,也无法反推明文。

如果你出于学习目的想自己实现,至少也要用hashlib.pbkdf2_hmac做几千次迭代加盐,不要把简单sha256(password)这种东西写进项目。

4. 核心代码实现:从注册登录到单点登录的完整链路

4.1 注册接口与密码哈希

注册接口的核心逻辑非常简单:接收用户名、密码,判断用户名是否已存在,然后用哈希函数处理密码,存入数据库。示例代码如下:

from werkzeug.security import generate_password_hash from flask import request, jsonify from models import User, db @app.route('/api/auth/register', methods=['POST']) def register(): data = request.get_json() username = data.get('username') password = data.get('password') if not username or not password: return jsonify({'code': 400, 'msg': '用户名和密码不能为空'}), 400 if User.query.filter_by(username=username).first(): return jsonify({'code': 400, 'msg': '用户名已存在'}), 400 user = User( username=username, password_hash=generate_password_hash(password) ) db.session.add(user) db.session.commit() return jsonify({'code': 201, 'msg': '注册成功'}), 201

我建议在注册接口里加上用户名长度检查、密码必须包含字母和数字等校验逻辑,因为老师在答辩时经常先看接口健壮性,而不是先看前端界面。

4.2 登录接口与 JWT 签发

登录接口执行两件事:验证凭证,签发 Token。JWT 我用 PyJWT 库生成,PayLoad 里放用户 ID、用户名、角色信息,设置过期时间。关键代码如下:

import jwt import datetime from werkzeug.security import check_password_hash SECRET_KEY = 'your_secret_key' ACCESS_TOKEN_EXPIRES_HOURS = 1 REFRESH_TOKEN_EXPIRES_DAYS = 7 @app.route('/api/auth/login', methods=['POST']) def login(): data = request.get_json() username = data.get('username') password = data.get('password') user = User.query.filter_by(username=username).first() if not user or not check_password_hash(user.password_hash, password): return jsonify({'code': 401, 'msg': '用户名或密码错误'}), 401 if not user.is_active: return jsonify({'code': 403, 'msg': '账号已被禁用'}), 403 access_payload = { 'sub': user.id, 'username': user.username, 'type': 'access', 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=ACCESS_TOKEN_EXPIRES_HOURS), 'iat': datetime.datetime.utcnow() } refresh_payload = { 'sub': user.id, 'type': 'refresh', 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=REFRESH_TOKEN_EXPIRES_DAYS), 'iat': datetime.datetime.utcnow() } access_token = jwt.encode(access_payload, SECRET_KEY, algorithm='HS256') refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm='HS256') return jsonify({ 'code': 200, 'msg': '登录成功', 'access_token': access_token, 'refresh_token': refresh_token, 'user': {'id': user.id, 'username': user.username} }), 200

这里的小细节是把iat设置为签发时间,exp设置为过期时间。后面校验时 PyJWT 会自动校验exp,一旦过期会抛出ExpiredSignatureError。

4.3 鉴权装饰器与接口保护

受保护接口的鉴权逻辑可以封装成一个装饰器,这样每个视图只需要在上面加一行@require_auth即可。装饰器解析请求头里Authorization: Bearer <token>,解码后把用户信息挂到request.current_user上。

from functools import wraps from flask import request, jsonify import jwt def require_auth(f): @wraps(f) def decorated(*args, **kwargs): auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({'code': 401, 'msg': '缺少访问令牌'}), 401 token = auth_header.split(' ')[1] try: payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) if payload.get('type') != 'access': return jsonify({'code': 401, 'msg': '令牌类型错误'}), 401 request.current_user = { 'id': payload.get('sub'), 'username': payload.get('username') } except jwt.ExpiredSignatureError: return jsonify({'code': 401, 'msg': '令牌已过期'}), 401 except jwt.InvalidTokenError: return jsonify({'code': 401, 'msg': '无效令牌'}), 401 return f(*args, **kwargs) return decorated

在做接口保护演示时,先不带 Token 请求,返回 401;再带 Token 请求,返回正常数据。这一步非常直观,建议录像时专门录一小段。

4.4 单点登录的实操方案

真正的企业级单点登录(SSO)涉及 CAS、OAuth2、OIDC 等协议,比较复杂。毕设阶段只需要实现"多个应用共用一个认证中心"的精简版即可。我的实现思路是这样的:

  • 所有接入系统把 Token 存放在各自的前端存储中(网页是 localStorage,小程序是 wx.storage);
  • 每个受保护接口都统一调用认证服务的/api/auth/introspect接口或本地验证 JWT 签名;
  • 只要所有系统的 JWT 使用同一个 SECRET_KEY 签发和验证,任意一个系统签发的 Token,其他系统都能识别。

这样"同一账号在多系统间保持登录"的效果就实现了。更简单的方式是在同一个域名下部署多个前端子应用,这样 localStorage 本来就是共享的。如果要做跨域名,可以用 iframe + postMessage 实现 Token 中转,但那里坑比较多,我建议毕设别做那么深,除非老师明确要求。

4.5 刷新 Token 与自动登录

Access Token 设置 1 小时过期,Refresh Token 设置 7 天过期。前端发请求时如果收到 401,再用 Refresh Token 调用刷新接口换新 Token。代码如下:

@app.route('/api/auth/refresh', methods=['POST']) def refresh(): data = request.get_json() refresh_token = data.get('refresh_token') if not refresh_token: return jsonify({'code': 400, 'msg': '缺少刷新令牌'}), 400 try: payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=['HS256']) if payload.get('type') != 'refresh': return jsonify({'code': 401, 'msg': '刷新令牌类型错误'}), 401 except jwt.InvalidTokenError: return jsonify({'code': 401, 'msg': '刷新令牌无效'}), 401 user = User.query.get(payload.get('sub')) if not user: return jsonify({'code': 401, 'msg': '用户不存在'}), 401 new_access_payload = { 'sub': user.id, 'username': user.username, 'type': 'access', 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=ACCESS_TOKEN_EXPIRES_HOURS), 'iat': datetime.datetime.utcnow() } new_access_token = jwt.encode(new_access_payload, SECRET_KEY, algorithm='HS256') return jsonify({'code': 200, 'access_token': new_access_token}), 200

要注意的是 Refresh Token 过期后不能自动续,否则会出现"永远不用重新登录"的安全隐患。刷新接口也要加频率限制,防止被刷。

5. 小程序 / 爬虫 / 数据可视化如何接入这套认证服务

5.1 微信小程序端接入流程

小程序端不能直接把用户名密码明文放在代码里,推荐走wx.login流程:

  1. 前端调用wx.login拿到一次性 code;
  2. 把 code 发给后端的/api/auth/wx_login接口;
  3. 后端用这个 code 向微信服务器换 openid;
  4. 后端查询这个 openid 是否已有绑定用户,没有就自动创建;
  5. 后端签发 JWT 返回给小程序;
  6. 小程序把 Token 存到wx.setStorageSync,后续请求带上 Authorization 头。

这样用户在小程序里点击"微信一键登录"就能完成认证,体验非常贴合真实产品。小程序端发请求时可以用wx.request的 header:

wx.request({ url: 'https://your-auth-service.com/api/auth/userinfo', method: 'GET', header: { 'Authorization': 'Bearer ' + wx.getStorageSync('token') }, success(res) { console.log(res.data); } });

5.2 爬虫脚本携带 Token 调接口

如果你的毕设里包含爬虫方向,最简单的方式是让爬虫先调登录接口换取 Token,再带着 Token 去请求受保护的数据接口。用 requests 实现:

import requests auth_host = 'http://127.0.0.1:5000' # 登录获取 token login_data = { 'username': 'your_username', 'password': 'your_password' } resp = requests.post(f'{auth_host}/api/auth/login', json=login_data) tokens = resp.json() access_token = tokens['access_token'] # 携带 token 请求受保护数据接口 headers = { 'Authorization': f'Bearer {access_token}' } data_resp = requests.get(f'{auth_host}/api/scrapy/data', headers=headers) print(data_resp.json())

这个模式的讲解重点是"身份认证和业务采集分离":爬虫只负责采集,认证服务负责判断这个采集脚本是不是授权客户端。完整代码里我还加了 Token 过期自动刷新逻辑,爬虫脚本每跑一段就判断一次是否掉线。

这里提醒一句:爬虫脚本只能用于你自己的毕设数据采集,或者明确允许访问的公开接口,一定要遵守目标网站的使用协议和 robots 规则,别拿这套去搞违规采集。

5.3 用认证日志做数据可视化大屏

统一认证服务天生带有日志优势,因为所有系统的登录行为都会经过你,所以能统计出很多有价值的图表。我用 Flask 把 auth_logs 表的数据开放成 JSON 接口,再通过 ECharts 渲染:

  • 折线图:最近 24 小时登录成功/失败趋势;
  • 饼图:登录来源渠道分布(网页、小程序、爬虫、APP);
  • 柱状图:各用户登录次数排名;
  • 地图:按 IP 归属地展示登录地区分布(这个可以借助 IP 定位库实现)。

答辩讲"数据可视化"方向的时候,不需要重新做一个复杂系统,直接把这一层做成"认证安全监控中心",把这个可视化界面当做一个子系统来演示,老师就会觉得项目的整体性和实用价值特别高。海量想法等都来自这些数据,你在答辩时可以说:"通过监控中心可以实时发现异常登录行为,比如同一账号在短时间内从不同地区登录,系统会标记为风险事件。"

6. 我在实际开发中踩过的坑和你大概率也会遇到

6.1 跨域问题导致前端一直请求失败

前端网页和后端认证服务如果不是同域,就会出现跨域。最常见的情况是 Vue 前端在 8080 端口,Flask 后端在 5000 端口,直接请求会被浏览器拦截。解决方案是给 Flask 应用配置 CORS。

我用 Flask-CORS 扩展,一行代码解决:

from flask_cors import CORS CORS(app, resources={r'/api/*': {'origins': '*'}})

但这里有个隐藏坑:如果你同时允许跨域和携带 Cookie,就不能用*通配符,需要指定具体域名,并且还要设置supports_credentials=True。否则前端登录时明明成功了,下一次请求却带不上凭证。

小程序测试还好,因为小程序的 request 不受浏览器跨域限制,但网页端测试一定逃不开这个坑,建议提前在配置文件中把允许的域名列表写好。

6.2 密码哈希算法让人看不出来对不对

第一次做的时候我图省事用hashlib.md5加密,同学直接笑我"这密码拿去答辩会被老师当场否掉"。后来换成werkzeug.security后,密码字段变长了,数据库存储没有问题。但注意,如果你用 Flask-SQLAlchemy 的模型,password_hash字段如果定义为String(128),pbkdf2 产生的哈希结果是够放的,可别只给String(50),否则入库时报数据超长,你排查半天后才想起来是字段长度的问题。

6.3 Token 过期时间设计的学问

Access Token 太短(比如 5 分钟),演示时容易中途掉线;太长(比如 24 小时),安全性被质疑。我最终设置成 Access Token 1 小时、Refresh Token 7 天,但演示录像里我会临时把 Access Token 过期时间调成 10 分钟,以便中途展示"过期刷新"这一操作。

前端也要做好逻辑:一般在请求拦截器里判断 401,然后调用刷新接口,刷新成功后重放原请求。这部分代码在项目里面我写好了,但你要理解它为什么要这样做:防止用户操作到一半突然被后台踢出,体验非常差。

6.4 演示录像里最容易翻车的操作顺序

录过一遍你就知道,最怕的是演示到一半触发数据库约束错误,或者 Token 正好过期。我的经验是:

  • 录像前先在本地完整跑一遍流程,把注册、登录、受保护接口访问、跨端同步这几个步骤截图确认;
  • 演示用的账号密码提前固定且用简单值(比如 admin / 123456),避免现场输错;
  • 数据库先导入几条预设数据,日志表也有记录,这样可视化大屏一打开就有图形,不用现场等数据;
  • 录屏工具用 OBS,选择 1080p,录完后剪辑掉等待部分,最终导出建议压缩到 50MB 以内,方便放到毕设说明页面里。

我踩的最丢人的坑是录像时忘记清空浏览器缓存,导致 Token 被从旧域名自动带过来,明明是用 B 账号登录的,界面却显示 A 账号的信息。后来我每次录屏前都会先把浏览器缓存和 localStorage 清理一遍。

还有一个小技巧:演示时不要只开一个终端窗口。我会把认证服务、前端服务、数据库三个窗口并排排列,分别打上不同的标题色,录像时用鼠标点击不同窗口切换,老师一看就知道整个项目是你自己从零搭建的。资源包里我附带了这套编排模板,直接照着放就能用。

做完这个项目,我个人最大的体会是:统一身份认证这个东西,表面看起来只是一个登录模块,但真做起来会发现它牵扯到了前端、后端、数据库、安全、跨端、多人协作、日志监控几乎每一个软件工程环节。它不炫技,但非常考察基本功。如果你正在为毕设选头发愁,或者手头已经定了这个方向但不知道代码该怎么组织,不妨直接把我这份白嫖源码拿去当底子。你需要的核心代码、SQL 建表脚本、小程序登录流程、可视化页面素材都在里面,照着跑一遍,再按自己的理解改一改,答辩时就能讲得清清楚楚了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询