【Web全栈进阶】OAuth实战——GitHub第三方登录

发布时间:2026/10/11 6:39:02
【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仓库地址】

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询