【Web全栈进阶】OAuth实战——GitHub第三方登录
“用GitHub账号登录”是每个网站都想要的体验用户不用注册、密码永远不会经过你的服务器。本篇走完授权码流程全链路并把JWT接进来——OAuth负责 “他是谁”JWT负责 “他还在”。本篇产出GitHub登录入口 回调处理 自动建本地用户 签发自有JWT。含代码约110行。 太长不看版给想快速上手的你项目信息一句话说明本篇目标给早报站加上GitHub第三方登录代码行数~110行含注释依赖requests第11篇老朋友核心功能授权码流程 find-or-create 签发自有JWT跑起来的命令/docs→ GET /auth/github → 浏览器走流程核心知识点OAuth授权码六步、client_secret只走服务器、find-or-create做完你能得到一套标准的第三方登录方案换平台只改端点核心认知OAuth只是 “认证”进了门之后发什么令牌、给什么权限完全是你自己说了算。一、先想清楚OAuth和JWT的分工JWT是 “自己发令牌”你的站有自己的用户体系登录后你发令牌。OAuth是 “别人帮你认证”GitHub确认了 “这人是谁”然后把这个结论告诉你。 两者不是二选一而是组合标准姿势一次OAuth换一张长期有效的 “本地通行证”这是全世界的标准做法。二、授权码流程六步走核心概念课主角有三个用户浏览器、你的站Client、GitHub授权服务器。 六步全流程 两个必须记住的安全点#安全点说明①第④步必须在服务器做换token需要client_secret——它和你的JWT_SECRET同级别永远只活在服务器和.env里②state参数防CSRF攻击者可以伪造 “回调你的服务器”——带一个自己生成的state回调时比对对不上就拒绝本篇先演示流程state完整校验放练习①三、第0步注册OAuth App要让你的早报站支持“用GitHub登录”首先得在GitHub上注册一个OAuth App——这相当于你在GitHub那里给早报站开了一个“官方认证通道”。 第1步进入Developer settings登录GitHub点击右上角你的头像在下拉菜单中点击Settings设置在左侧边栏一直往下拉找到Developer settings开发者设置并点击。找不到直接在浏览器地址栏输入https://github.com/settings/developers也可以直达。 第2步进入OAuth Apps在Developer settings页面左侧边栏有三个选项点击OAuth Apps。⚠️注意这里选OAuth Apps不是GitHub Apps。两者都基于OAuth 2.0但GitHub Apps的权限模型更细粒度配置也更复杂。本专栏教学场景用OAuth Apps最简单、最直接。 第3步新建OAuth App点击右上角的New OAuth App按钮。如果你从没创建过任何App这个按钮会显示为Register a new application注册新应用——意思完全一样点它就行。 第4步填写应用信息关键步骤这是最容易出错的一步每个字段都要仔细填字段填什么说明Application namePython早报或你喜欢的名字用户授权时会看到这个名字Homepage URLhttp://localhost:8000应用的“主页地址”。开发环境就填本地地址Application descriptionPython早报站的GitHub登录可选用户授权时看到的描述Authorization callback URLhttp://localhost:8000/auth/github/callback最关键的一个字段——GitHub授权完成后会把用户“送回”这个地址 Authorization callback URL为什么必须完全一致这个字段告诉GitHub“用户同意授权后把他重定向到我服务器的哪个地址”。它必须和你代码里settings.github_redirect_uri的值逐字一致——端口、路径、localhost与127.0.0.1的写法差一个字符都会导致redirect_uri_mismatch错误。⚠️OAuth App只能设置一个回调URL不像GitHub Apps支持多个或通配符匹配。所以在开发阶段固定用http://localhost:8000/auth/github/callback上线时再改。 第5步注册应用确认所有字段无误后点击底部的Register application注册应用按钮。 第6步获取Client ID和Client Secret注册成功后页面会自动跳转到该应用的设置页。你会看到两个关键凭证凭证在哪里性质Client ID页面顶部直接显示公开的可以出现在前端代码里Client Secret点击Generate a new client secret按钮生成机密只显示一次必须立即复制保存Client Secret只显示一次如果你关掉页面才发现没复制只能重新生成一个——旧的立即作废。把它写进.env文件绝不进Git仓库。 第7步写入 .env在项目根目录的.env文件里添加三行如果.env不存在就新建并确认它在.gitignore里GITHUB_CLIENT_ID你的Client_ID GITHUB_CLIENT_SECRET你的Client_Secret GITHUB_REDIRECT_URIhttp://localhost:8000/auth/github/callback三处对齐检查.env里的GITHUB_REDIRECT_URI、app/config.py里github_redirect_uri的默认值、以及GitHub OAuth App设置页里的Authorization callback URL——这三处必须是同一个地址。四、第1步配置Settingsconfig.py加三个字段.env自动注入classSettings(BaseSettings):...github_client_id:strgithub_client_secret:strgithub_redirect_uri:strhttp://localhost:8000/auth/github/callback五、第2步发起登录——拼授权URLapp/routers/github.pyapp/routers/github.py —— GitHub OAuth登录importsecretsimporturllib.parseimportrequestsfromfastapiimportAPIRouter,Depends,HTTPExceptionfromsqlalchemyimportselectfromsqlalchemy.ormimportSessionfromapp.configimportsettingsfromapp.depsimportget_sessionfromcore.modelsimportUser routerAPIRouter(prefix/auth/github,tags[github])GITHUB_AUTHORIZEhttps://github.com/login/oauth/authorizeGITHUB_TOKENhttps://github.com/login/oauth/access_tokenGITHUB_USERhttps://api.github.com/userrouter.get()defgithub_login():① 生成授权URL演示版直接返回URL生产用302 state存会话statesecrets.token_urlsafe(16)# 防伪造回调的随机串params{client_id:settings.github_client_id,redirect_uri:settings.github_redirect_uri,scope:read:user,# 只申请读用户信息state:state,}urlf{GITHUB_AUTHORIZE}?{urllib.parse.urlencode(params)}return{login_url:url}OAuth全程就是三次HTTP请求没有任何新技术。六、第3步回调——换令牌、取用户、建用户、签JWT先把create_token抽到共享模块避免两处重复定义app/security.py新建从auth.py里抽出来app/security.py —— 令牌签发与校验auth/github共用fromdatetimeimportdatetime,timedelta,timezoneimportjwtfromapp.configimportsettingsfromcore.modelsimportUserdefcreate_token(user:User,expires_minutes:int,token_type:str)-str:签发令牌payload只放身份标识和时间不放敏感信息nowdatetime.now(timezone.utc)payload{sub:str(user.id),type:token_type,iat:now,exp:nowtimedelta(minutesexpires_minutes),}returnjwt.encode(payload,settings.jwt_secret,algorithmHS256)auth.py改为from app.security import create_token删掉重复定义。app/routers/github.py继续fromapp.securityimportcreate_tokenrouter.get(/callback)defgithub_callback(code:str,state:str,session:SessionDepends(get_session),):③④⑤⑥ 回调四连换token → 取用户 → find-or-create → 签自己的JWT# 演示版不校验state完整校验见练习①# ④ code换access_token# ⚠️ GitHub的OAuth端点期望表单编码必须用data而不是jsontoken_resprequests.post(GITHUB_TOKEN,headers{Accept:application/json,},data{client_id:settings.github_client_id,client_secret:settings.github_client_secret,code:code,redirect_uri:settings.github_redirect_uri,},timeout10)access_tokentoken_resp.json().get(access_token)ifnotaccess_token:raiseHTTPException(status_code401,detailGitHub授权失败请重试)# ⑤ 取GitHub用户信息merequests.get(GITHUB_USER,headers{Authorization:fBearer{access_token},},timeout10).json()# ⑥ find-or-create按github_id找本地用户没有就建usersession.scalar(select(User).where(User.github_idstr(me[id])))ifuserisNone:userUser(usernamefgh_{me[login]},# 前缀隔离避免撞已有用户名github_idstr(me[id]),)session.add(user)session.commit()# 签发自己的JWT从此走第06篇的体系tokencreate_token(user,settings.jwt_expire_minutes,access)return{access_token:token,token_type:bearer,username:user.username} User模型加列alembic第三次兑现classUser(Base):...github_id:Mapped[str|None]mapped_column(uniqueTrue,defaultNone)alembic revision--autogenerate-madd user github_idalembic upgradehead 挂载路由app/main.py补一行fromapp.routersimportarticles,sources,auth,github app.include_router(auth.router)app.include_router(github.router)七、验收清单1. /docs → GET /auth/github → 复制login_url到浏览器2. 跳转GitHub授权页 → 确认授权 → 自动跳回localhost回调地址3. 回调返回JSONaccess_token 新用户名gh_xxx4. 用这个token调/auth/me → 能查到用户JWT体系无缝衔接5. psql看users表github_id已存password_hash为空OAuth用户无密码正常6. 再次走全流程 → 返回同一个用户find-or-create生效不重复建号7. 全程无报错后提交Gitgitadd.gitcommit-mGitHub OAuth登录授权码流程 find-or-create八、常见报错这6个OAuth的标配重点① 跳转授权页报redirect_uri_mismatch 原因注册回调地址与代码里的redirect_uri不完全一致——端口、路径、尾部斜杠差一个字符都不行。✅ 解法两处逐字对齐。localhost与127.0.0.1也视为不同。② 授权后回调报401或GitHub返回bad_verification_code 原因code是一次性的——刷新页面重复请求回调code已作废。✅ 解法不是bug重新发起登录即可。调试时记得每次走完整流程。③errorinvalid_client_id/ token换不到 原因Client ID抄错或Client Secret不是本App的或.env没生效。✅ 解法回GitHub OAuth App页核对重启uvicorn让.env重新加载第05篇⑥。④ 用户名撞车gh_xxx已存在unique约束报错 原因有人在你的站注册过同名用户或重复建号竞态。✅ 解法username冲突时追加github_id后缀gh_xxx_12345更稳的做法是用email做关联键生产方案。⑤ 回调地址用127.0.0.1时GitHub提示无法访问 原因GitHub对OAuth回调的localhost白名单有特殊处理——注册和代码必须统一用localhost或统一用127.0.0.1混用必mismatch。✅ 解法统一为localhost:8000。⑥ Client Secret泄露了 原因可能进过git、贴过群聊——它是你 “代表这个App发言”的凭证。✅ 解法GitHub OAuth App页直接点 “Regenerate client secret”换新旧的立即作废。密钥可重置前提是发现得早。九、课后练习#练习难度提示1state完整校验发起登录时把state存进短期缓存或签进2分钟JWT回调时比对⭐⭐⭐伪造回调就此失效2头像入库me[avatar_url]存进User⭐⭐加列 迁移 find-or-create赋值3邮箱关联scope加user:email调/user/emails拿主邮箱用它做find-or-create的键⭐⭐⭐生产级方案4选做平台对比看企业微信/飞书/微信开放平台的OAuth文档⭐⭐流程完全一样只是换端点换字段——学会一个全会 配套代码完整github.py、security.py与迁移脚本已上传Gitpython_daily/【gitee仓库地址】

相关新闻

高校OJ平台STL专题AC代码库:容器选型、避坑与刷题实战

高校OJ平台STL专题AC代码库:容器选型、避坑与刷题实战

简介:这是东北大学OJ平台上C语法与STL练习的AC代码合集,面向正在刷题备赛的高校学生,以及刚接触标准模板库、希望系统提升容器与算法选型能力的C初学者。压缩包内共44个cpp源文件,整体仅29KB,内容覆盖vector、set、map…

2026/10/11 6:39:22 阅读更多 →
趣博思AI格式排版:那些让编辑在初审阶段就皱眉的“格式暗病“

趣博思AI格式排版:那些让编辑在初审阶段就皱眉的“格式暗病“

一篇论文被退稿,理由可以有很多:选题不够新颖、方法不够严谨、论证不够充分。 但还有一种退稿,理由不会写进意见里,却真实存在:编辑打开你的稿子,第一眼就觉得"不对劲"。 页边距太窄,…

2026/10/11 6:39:22 阅读更多 →
趣博思 AI|文献综述不是文献盘点,教你构建层层递进的学术论证脉络

趣博思 AI|文献综述不是文献盘点,教你构建层层递进的学术论证脉络

在论文写作流程中,文献综述常常被误解为 “文献盘点”。不少学生花费大量时间搜集文献,堆砌大量引用,篇幅达标,但整篇综述没有论证主线,各个段落彼此割裂,无法向评审说明为什么要开展这项研究。本质上来说&…

2026/10/11 6:39:22 阅读更多 →

最新新闻

AI Agent外挂记忆系统mem0实战:可演化的语义记忆架构

AI Agent外挂记忆系统mem0实战:可演化的语义记忆架构

1. 项目概述:为什么一个“外挂记忆”能让AI Agent真正像人一样思考?最近在几个技术社区里,几乎每天都能看到有人问:“我的AI Agent跑着跑着就忘了上一轮聊了什么,指令一多就开始自相矛盾,这到底算智能还是人…

2026/10/12 10:05:55 阅读更多 →
对比 Claude Code 与 Cursor 后,DeepSeek Harness 的插件化架构优势在哪

对比 Claude Code 与 Cursor 后,DeepSeek Harness 的插件化架构优势在哪

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 10:05:55 阅读更多 →
Gemini 是什么?kimi K2.5 的区别是什么——用 TaoToken 统一 Key 实测对比

Gemini 是什么?kimi K2.5 的区别是什么——用 TaoToken 统一 Key 实测对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 10:05:55 阅读更多 →
大模型融资热潮背后的技术选型与落地避坑指南

大模型融资热潮背后的技术选型与落地避坑指南

最近圈子里都在传某头部大模型团队的新一轮融资消息,传闻金额直接站上了八百亿元量级,参投名单里既有互联网平台的影子,也有新能源产业链里的巨头。很多朋友看到这种新闻的第一反应是“AI行业真有钱”,但作为常年在一线做技术选型…

2026/10/12 10:05:55 阅读更多 →
S3C2440裸机开发实战:从启动流程到第一个可运行程序

S3C2440裸机开发实战:从启动流程到第一个可运行程序

1. 拿到一块裸机开发板,先别急着写代码S3C2440 这颗芯片在嵌入式圈子里算是老资历了,ARM9 架构、主频 400MHz 起步、外设接口丰富,当年多少人的第一个裸机程序就是在它上面跑起来的。现在回头看,拿它入门裸机开发依然是个相当扎实…

2026/10/12 10:05:55 阅读更多 →
写给自己的话:如何用结构化自我对话破解学习困惑

写给自己的话:如何用结构化自我对话破解学习困惑

1. 为什么“写给自己的话”反而是最难写的一种内容你有没有过这种体验:打开备忘录,手指悬在键盘上半天,脑子里翻江倒海,最后只憋出一句“今天好累”。明明是想写给自己的话,想梳理一下学习中的困惑,结果写出…

2026/10/12 10:04:54 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →