当前位置: 首页 > news >正文

OAuth2 GitHub 登录实现

OAuth2 GitHub 登录实现

文章目录

  • OAuth2 GitHub 登录实现
    • 1. 概述
      • 整体流程
    • 2. 配置
      • 2.1 application.yml
      • 2.2 GitHub OAuth App 配置
    • 3. 后端核心实现
      • 3.1 SecurityConfig — OAuth2 登录配置
      • 3.2 UserDetailsServiceImpl — 双重角色
      • 3.3 OAuth2 成功处理器
      • 3.4 GitHub 用户属性映射
      • 3.5 本地用户创建
      • 3.6 数据库表结构
    • 4. 前端实现
      • 4.1 登录页面 —— 触发 GitHub 登录
      • 4.2 OAuth2 回调页面 —— 接收 Token
    • 5. 完整数据流
    • 6. 安全性分析
    • 7. 注意事项

1. 概述

本系统使用Spring Security OAuth2 Client实现 GitHub 第三方登录。用户通过 GitHub 授权后,系统自动拉取 GitHub 用户信息,转换为本地用户,并签发 JWT 双 Token 供后续鉴权。

整体流程

┌──────────┐ ┌──────────────┐ ┌──────────┐ ┌───────────┐ │ 前端 │ │ 后端 │ │ GitHub │ │ 数据库 │ │(Vue 3) │ │(Spring Boot) │ │ │ │ (MySQL) │ └────┬─────┘ └──────┬───────┘ └────┬─────┘ └─────┬─────┘ │ │ │ │ │ 1. 点击 GitHub │ │ │ │ 登录按钮 │ │ │ │────────────────>│ │ │ │ │ │ │ │ 2. 重定向到 │ │ │ │ GitHub 授权页│ │ │ │<────────────────│ │ │ │ │ │ │ │ 3. 用户授权 │ │ │ │──────────────────────────────────>│ │ │ │ │ │ │ 4. GitHub 回调 │ │ │ │ 带上授权码 │ │ │ │────────────────>│ │ │ │ │ │ │ │ │ 5. 用授权码 │ │ │ │ 换取 Access │ │ │ │ Token │ │ │ │────────────────>│ │ │ │ │ │ │ │ 6. 返回用户信息 │ │ │ │<────────────────│ │ │ │ │ │ │ │ 7. 查找/创建 │ │ │ │ 本地用户 │ │ │ │────────────────>│ │ │ │ │ │ │ │ 8. 签发 JWT │ │ │ │ 重定向前端 │ │ │ 9. 前端收到 │ │ │ │ Token, 保存 │ │ │ │ 跳转主页 │ │ │ │<────────────────│ │ │

2. 配置

2.1 application.yml

spring:security:oauth2:client:registration:github:client-id:Ov23liRI4TMbCclient-secret:c0a2194994743d17d856b5ee49scope:read:user,user:email
配置项说明获取方式
client-idGitHub OAuth App 的 Client IDGitHub Settings → Developer settings → OAuth Apps
client-secretGitHub OAuth App 的 Client Secret同上
scope请求的权限范围read:user获取用户基本信息,user:email获取邮箱

2.2 GitHub OAuth App 配置

在 GitHub 上创建 OAuth App 时,Authorization callback URL必须设置为:

http://localhost:8080/login/oauth2/code/github

这是 Spring Security OAuth2 Client 的默认回调路径,由框架自动处理,无需手动实现。


3. 后端核心实现

3.1 SecurityConfig — OAuth2 登录配置

@BeanpublicSecurityFilterChainsecurityFilterChain(HttpSecurityhttp)throwsException{http// ... 其他配置 ....oauth2Login(oauth2->oauth2.userInfoEndpoint(userInfo->userInfo.userService(userDetailsService)// ← 自定义 OAuth2UserService).successHandler(this::oauth2SuccessHandler)// ← 自定义成功处理器)// ...}

配置说明:

方法作用
.userInfoEndpoint().userService()指定自定义的OAuth2UserService,用于处理 GitHub 返回的用户信息
.successHandler()自定义登录成功后的处理逻辑(签发 JWT + 重定向前端)

3.2 UserDetailsServiceImpl — 双重角色

@ServicepublicclassUserDetailsServiceImplextendsDefaultOAuth2UserServiceimplementsUserDetailsService{// 角色1: UserDetailsService — 用户名密码登录时使用@OverridepublicUserDetailsloadUserByUsername(Stringusername){// 从数据库查询本地用户}// 角色2: OAuth2UserService — OAuth2 登录时使用@OverridepublicOAuth2UserloadUser(OAuth2UserRequestuserRequest){OAuth2UseroAuth2User=super.loadUser(userRequest);// 调用 GitHub API// 包装为 DefaultOAuth2User,指定用户名字段为 "id"returnnewDefaultOAuth2User(Collections.singletonList(newSimpleGrantedAuthority("ROLE_USER")),oAuth2User.getAttributes(),"id"// nameAttributeKey = GitHub 用户 ID);}}

为什么继承DefaultOAuth2UserService

Spring Security OAuth2 Client 通过OAuth2UserService使用 GitHub 返回的 Access Token 调用 GitHub 用户 API (https://api.github.com/user),获取详细的用户信息。继承DefaultOAuth2UserService复用这个默认行为,super.loadUser(userRequest)内部完成了:

  1. 用授权码换取 Access Token(由OAuth2AuthorizedClientService自动完成)
  2. 用 Access Token 调用 GitHub API 获取用户信息
  3. 返回包含完整用户属性的OAuth2User对象

nameAttributeKey的作用:

DefaultOAuth2User构造函数需要指定一个属性名作为用户的唯一标识。GitHub 的id字段是数字类型,唯一且不可变,适合作为nameAttributeKey。之后通过oAuth2User.getName()即可获取该值。

3.3 OAuth2 成功处理器

privatevoidoauth2SuccessHandler(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException{OAuth2Useroauth2User=(OAuth2User)authentication.getPrincipal();// 1. 提取 GitHub 用户信息Stringprovider="GITHUB";StringproviderId=oauth2User.getAttribute("id").toString();Stringusername=oauth2User.getAttribute("login");Stringemail=oauth2User.getAttribute("email");Stringavatar=oauth2User.getAttribute("avatar_url");// 2. 查找本地用户(根据 provider + providerId)varuser=userService.findByProviderAndProviderId(provider,providerId);if(user==null){// 3. 首次登录 → 自动创建本地用户user=userService.createOAuth2User(username,email,avatar,provider,providerId);}// 4. 签发 JWT 双 TokenStringaccessToken=jwtTokenProvider.generateAccessToken(user.getUsername(),user.getEmail(),user.getAvatar());StringrefreshToken=jwtTokenProvider.generateRefreshToken(user.getUsername(),true);// 5. 重定向到前端,Token 放在 URL 参数中response.sendRedirect("http://localhost:5173/oauth2/callback?token="+accessToken+"&refreshToken="+refreshToken);}

成功处理器的关键步骤:

步骤说明
提取用户信息OAuth2User.getAttributes()中获取 GitHub 返回的用户属性
查找/创建本地用户通过provider+providerId这一对唯一组合确定用户身份
签发 JWT生成 Access Token + Refresh Token
重定向前端将 Token 以 URL 参数形式传给前端OAuth2Callback页面

3.4 GitHub 用户属性映射

GitHub 属性类型映射到本地字段说明
idIntegerprovider_idGitHub 用户唯一 ID
loginStringusernameGitHub 用户名
emailStringemail用户邮箱(可能为 null)
avatar_urlStringavatar用户头像 URL
provider固定值provider固定为GITHUB

3.5 本地用户创建

publicUsercreateOAuth2User(Stringusername,Stringemail,Stringavatar,Stringprovider,StringproviderId){Useruser=newUser();user.setUsername(username);user.setPassword(passwordEncoder.encode("oauth2_user_"+System.currentTimeMillis()));// 随机密码user.setEmail(email);user.setAvatar(avatar);user.setProvider(provider);user.setProviderId(providerId);user.setEnabled(true);user.setCreatedAt(LocalDateTime.now());userMapper.insert(user);returnuser;}

OAuth2 用户与本地注册用户的区别:

对比项本地注册用户OAuth2 用户
providerLOCALGITHUB
provider_idnullGitHub 用户 ID
passwordBCrypt 加密随机字符串(不可用于登录)
avatar可选GitHub 头像 URL

3.6 数据库表结构

CREATETABLEIFNOTEXISTSusers(idBIGINTNOTNULLAUTO_INCREMENTPRIMARYKEY,usernameVARCHAR(255)NOTNULLUNIQUE,passwordVARCHAR(255)NOTNULL,emailVARCHAR(255)DEFAULTNULL,avatarVARCHAR(255)DEFAULTNULL,providerVARCHAR(50)NOTNULLDEFAULT'LOCAL',-- LOCAL / GITHUBprovider_idVARCHAR(255)DEFAULTNULL,-- GitHub 用户 IDenabledTINYINT(1)NOTNULLDEFAULT1,created_atDATETIMENOTNULLDEFAULTCURRENT_TIMESTAMP)ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;

关键索引:(provider, provider_id)作为 OAuth2 用户的唯一标识,对应查询:

SELECT*FROMusersWHEREprovider='GITHUB'ANDprovider_id='12345678';

4. 前端实现

4.1 登录页面 —— 触发 GitHub 登录

// LoginPage.vuefunctionhandleGithubLogin(){window.location.href='http://localhost:8080/oauth2/authorization/github'}

直接跳转到 Spring Security 的 OAuth2 授权端点,后端自动处理授权流程。

4.2 OAuth2 回调页面 —— 接收 Token

// OAuth2Callback.vueonMounted(async()=>{consttoken=route.query.tokenasstringconstrefreshToken=route.query.refreshTokenasstringif(token&&refreshToken){authStore.setTokens(token,refreshToken)// 保存双 Tokentry{constres=awaitgetUserInfo()// 获取用户信息const{username,avatar}=res.data.data authStore.setUser(username,avatar)router.push('/dashboard')}catch{router.push('/dashboard')// 即使获取用户信息失败也跳转}}else{ElMessage.error('登录失败,未获取到认证信息')router.push('/login')}})

为什么回调页面需要调用/api/auth/user

后端 OAuth2 成功处理器在重定向时没有返回用户信息(用户名、头像),因为 URL 长度有限且安全考虑。前端需要用自己的 Access Token 调用/api/auth/user获取用户信息,然后存入 Pinia Store 和 localStorage。


5. 完整数据流

以下是一个用户首次使用 GitHub 登录的完整请求链路:

Step 1: 前端跳转 GitHub 授权 GET http://localhost:8080/oauth2/authorization/github → 302 重定向到 https://github.com/login/oauth/authorize?client_id=xxx&redirect_uri=http://localhost:8080/login/oauth2/code/github Step 2: 用户同意授权 → GitHub 302 重定向到 http://localhost:8080/login/oauth2/code/github?code=xxx Step 3: 后端用 code 换取 access_token → POST https://github.com/login/oauth/access_token Step 4: 后端用 access_token 获取用户信息 → GET https://api.github.com/user ← { id: 12345678, login: "zhangsan", email: "zhangsan@example.com", avatar_url: "https://..." } Step 5: 查找/创建本地用户 → SELECT * FROM users WHERE provider = 'GITHUB' AND provider_id = '12345678' → 未找到 → INSERT INTO users ... Step 6: 签发 JWT 并重定向前端 → 302 重定向到 http://localhost:5173/oauth2/callback?token=xxx&refreshToken=yyy Step 7: 前端保存 Token 并获取用户信息 → GET /api/auth/user (Authorization: Bearer xxx) ← { username: "zhangsan", email: "...", avatar: "..." } Step 8: 跳转仪表盘 → router.push('/dashboard')

6. 安全性分析

风险点防护措施
授权码泄露授权码一次性使用,有效期短(秒级)
GitHub Access Token 泄露仅后端持有,不经过前端,不会泄露
CSRF 攻击Spring Security OAuth2 Client 默认使用 state 参数防 CSRF
重定向 URL 篡改GitHub 会验证 redirect_uri 与注册时一致
伪造回调后端用 code + client_secret 向 GitHub 换取 token,伪造无效

7. 注意事项

  1. GitHub 邮箱可能为空:如果用户在 GitHub 上设置了隐私模式(隐藏邮箱),email字段可能为null。此时user.getEmail()返回null,JWT claims 中 email 会设为空字符串。

  2. 用户名冲突:如果已存在同名的本地注册用户,GitHub 登录的login字段会因UNIQUE约束而插入失败。实际中需考虑用户名冲突处理策略(如添加后缀或前缀)。

  3. OAuth2 状态管理:当前实现未要求自定义loginPage,使用 Spring Security 默认的 OAuth2 端点路径,避免了自定义路径与内置回调路径冲突导致的循环重定向。

  4. Session 管理:虽然设置了SessionCreationPolicy.STATELESS,但 OAuth2 流程中 Spring Security 需要在 Session 中临时保存OAuth2AuthorizationRequest(含 state 参数),因此 OAuth2 流程会短暂使用 Session,流程完成后后续请求通过 JWT 鉴权。

  5. 注册 GitHub OAuth App 时的注意事项

    • Homepage URL 填http://localhost:5173
    • Authorization callback URL 填http://localhost:8080/login/oauth2/code/github
    • 生产环境需要将localhost替换为实际域名
http://www.gsyq.cn/news/1592587.html

相关文章:

  • iOS国际化测试:MJRefresh多语言自动化测试完整解决方案
  • 拿 DeepSeek 的免费对话搓了个 Everything 的静态 WebUI
  • Metasploit渗透测试实战:从DC-1靶机入门到后渗透技术精讲
  • WFuzz插件开发实战:从链接提取到漏洞检测的深度定制
  • MySQL 死锁排查思路
  • 【紧急修复指南】:VMware 7.0U3升级后性能断崖式下跌?官方未公开的kernel module兼容性补丁已验证生效
  • 数据安全删除实战:从原理到工具,彻底清除数字痕迹
  • 终极Koikatsu Sunshine增强补丁:如何快速安装并解锁100+插件功能
  • VMware不支持硬件虚拟化?别急着重装系统!先做这7项底层诊断——基于Intel ARK/AMD CPUID指令的硬核验证流程
  • 免费解锁Windows多用户远程桌面的终极方案:RDP Wrapper完全指南
  • 告别网盘限速:九大平台高速下载完全攻略
  • 【软工方法论17】行为型设计模式命令模式全解析
  • Cypress Testing Library 八大查询命令详解:从原理到实战,打造健壮的前端自动化测试
  • VMware ESXi虚机蓝屏代码解密档案(仅限认证工程师访问):基于127TB生产环境日志训练的AI归因模型输出TOP10根因及对应KB编号
  • 【稀缺首发】VMware KB官方未公开的3类“伪不支持”场景:Hyper-V共存冲突、TPM 2.0驱动劫持、UEFI Secure Boot签名绕过方案
  • 泛化管理化技术中的泛化计划泛化实施泛化验证
  • 【企业级开发环境标准化实践】:基于VMware的12类开发镜像模板设计规范(含Docker+K8s桥接方案)
  • 康复训练系统:运动捕捉与进度跟踪技术
  • 打通 OpenClaw 本地自动化,先搞定解压、权限、网关各类问题(含安装包)
  • 侧边栏主题切换高级动效实战(Vue2/Element UI 可复用版)
  • 易元智创APP:适配实体商家引流,海南易元现实科技有限公司助力实体店线上拓客增收
  • 家里吃灰的电脑再利用,买个域名就能当服务器用
  • 如何快速配置Realtek 8852AE Wi-Fi 6驱动:完整实用指南
  • uniapp组件uni-datetime-picker常见bug
  • 世界杯主题活动海报转化拆解:信息层级、利益点与生成输入实操
  • 销售离职带不走客户?一部剪流AI员工手机,如何彻底杜绝销售飞单与客户流失
  • 网络安全监控体系
  • FDD大规模MIMO中鲁棒反向注水算法:应对CSI反馈挑战的工程实践
  • 魔兽争霸3辅助工具终极指南:5分钟解决所有兼容性问题
  • 日志管理化技术中的日志收集日志分析日志存储