用户访问 /main/files
↓
路由守卫检查(未登录)
↓
重定向到 /login?redirectUrl=/main/files
↓
用户点击GitHub登录
↓
callbackUrl = "/main/files" 传给后端
↓
后端保存到session[state] = "/main/files"
↓
GitHub授权完成
↓
后端取出 session[state] = "/main/files"
↓
返回给前端 callbackUrl = "/main/files"
↓
前端跳转到 /main/files(用户原本想访问的页面)
GitHub OAuth应用配置
创建一个 OAuth App
OAuth 在 GitHub 中被叫作 OAuth Apps,所以需要先创建一个 OAuth App。通过点击右上角头像的 Settings → Developer settings → OAuth Apps 进入到 OAuth Apps 创建和管理页面:https://github.com/settings/developers
如下图:
点击右上角的“New OAuth App”进入新的 OAuth App 创建页面,需要填写的信息非常少。
注意最后一个回调地址(前/后端均可,取决你的实现),当用户点击授权后,GitHub 会携带授权码(code)回调/重定向到这个地址。 这个地址可以是任意合法的地址,比如http://localhost:8080/,在本地开发调试的使用。
回调时,GitHub 会追加一个名为code的查询参数到刚刚指定的回调地址中。(这个名为code的参数是hardcode的,没有找到地方可以修改)比如:https://blog.twofei.com/login?type=github&code=xxxxxx,注意后面的 code 部分。
创建好后,就可以拿到 Client ID 和对应的 Client Secret,同时还可以设置一个头像。Client ID 可以公开,Client Secret 请不要泄露。
下面是我网盘项目前后端实现GitHub第三方登录的具体代码。
根据OAuth App更新后端配置文件
# GitHub OAuth App 配置(已脱敏)
github.app.id=
github.app.secret=
# 授权登录页面 URL 模板
github.url.authorization=https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&state=%s&scope=read:user,user:email
# 获取 access token 的 URL
github.url.access.token=https://github.com/login/oauth/access_token
# 获取 GitHub 用户基本信息的 API
github.url.user.info=https://api.github.com/user
# 获取 GitHub 用户邮箱列表的 API
github.url.user.emails=https://api.github.com/user/emails
# 授权回调页面 URL 模板
github.url.redirect=http:///githublogincallback
Appconfig类配置映射
@Value("${github.app.id:}")
private String githubAppId;
@alue("${github.app.secret:}")
private String githubAppSecret;
@Value("${github.url.authorization:}")
private String githubUrlAuthorization;
@Value("${github.url.access.token:}")
private String githubUrlAccessToken;
@Value("${github.url.user.info:}")
private String githubUrlUserInfo;
@Value("${github.url.user.emails:}")
private String githubUrlUserEmails;
@Value("${github.url.redirect:}")
private String githubUrlRedirect;
用户点击 “GitHub 登录” 按钮后,前端会向后端接口(如 /api/github)发起请求,并携带当前页面的路径作为 callbackUrl 参数。
后端根据配置的 GitHub OAuth 应用信息(如 client_id、redirect_uri、state 等)生成 GitHub 授权登录地址,并将该地址返回给前端。
前端收到后,通过 document.location.href = 授权地址 实现页面跳转,引导用户进入 GitHub 授权页面。
// GitHub 登录方法
const githubLogin = async () => {
// 请求后端接口,生成 GitHub 登录 URL
let result = await proxy.Request({
url: api.githublogin, // 后端提供的 GitHub 登录 API
params: {
// 从路由中获取跳转回来的页面路径,或默认空字符串
callbackUrl: route.query.redirectUrl || "", }, });
// 如果请求失败则退出
if (!result) return;
// 清除登录态(可选操作)
proxy.VueCookies.remove("userInfo");
// 跳转到 GitHub 登录地址
document.location.href = result.data;
};
这段代码通过 router.beforeEach 全局前置守卫,在路由跳转前判断用户是否登录。如果访问的路由 meta.needLogin = true 且未登录(即 Cookie 中没有 userInfo),则重定向到登录页,并将当前访问路径通过 redirectUrl 参数传递,登录成功后可以跳转回原路径。
/**
* 全局前置守卫:用于在页面跳转前判断用户是否已登录
*/
router.beforeEach((to, from, next) => {
// 从 Cookie 中获取用户信息(如未登录则为 null)
const userInfo = VueCookies.get("userInfo");
// 判断是否需要登录且当前用户未登录
if (to.meta.needLogin !== null && to.meta.needLogin && userInfo == null) {
// 跳转到登录页,并携带当前页面路径作为登录成功后的回调地址
router.push("/login?redirectUrl=" + to.path);
return;
}
// 正常放行
next();
});
OAuth 登录第一步:获取授权链接。
当前端请求 /api/githublogin?callbackUrl=/user/123 时,后端会先生成一个随机 state 值,并将 callbackUrl 存入 Session 中作为临时回调地址;然后拼接 GitHub OAuth 登录链接并返回给前端,前端跳转后用户就会进入 GitHub 授权页。
/**
* GitHub 登录接口:用于生成 GitHub 授权跳转地址
*/
@RequestMapping("githublogin")
@GlobalInterceptor(checkLogin = false, checkParams = true)
public ResponseVO githublogin(HttpSession session, String callbackUrl) throws UnsupportedEncodingException {
// 生成随机 state,用于防止 CSRF 攻击
String state = StringTools.getRandomString(Constants.LENGTH_30);
// 如果携带了前端回调地址,则临时存储在 session 中
if (!StringTools.isEmpty(callbackUrl)) {
session.setAttribute(state, callbackUrl);
}
// 拼接 GitHub 授权链接
String url = String.format(appConfig.getGithubUrlAuthorization(), // 授权链接模板
appConfig.getGithubAppId(), // client_id
URLEncoder.encode(appConfig.getGithubUrlRedirect(), "utf-8"), // redirect_uri
state // state 参数
);
// 返回 GitHub 授权跳转地址
return getSuccessResponseVO(url);
}
在 OAuth 登录流程中,用户授权后会携带 code 和 state 参数回调至前端页面。我们通过 proxy.Request 向后端 /oauth/callback 接口发送这些参数,服务端处理并返回用户信息与回调地址。成功后将用户信息写入 Cookie,并重定向到原始页面或首页,失败则跳转回登录页。前端代码如下:
(async () => {
// 使用当前路由的 query 参数(如包含 code 和 state)
const res = await proxy.Request({
url: "/oauth/callback",
params: route.query
});
if (res && res.data) {
// 存储用户信息(示例)
proxy.VueCookies.set("userInfo", res.data.userInfo, 0);
router.replace(res.data.callbackUrl || "/");
} else {
// 跳转登录页
router.replace("/login");
}
})();
后端处理 OAuth 回调时,通过 code 获取用户信息,并将其保存在 Session 中。前端在跳转第三方平台前,先将原始访问地址临时存入 Session,并通过 state 标识。回调成功后,后端从 Session 中提取原地址以及GitHub用户信息返回给前端,从而完成一次安全的登录跳转流程。
登录流程基于标准 OAuth 授权协议,使用授权 code 向平台换取 access_token,进而获取用户信息。系统通过用户在平台的唯一标识(如 GitHub ID)判断用户是否已注册,如未注册则创建新用户。系统还根据配置文件中的管理员邮箱列表判断当前用户是否具有管理权限,最后将登录状态和用户空间信息缓存进 Redis。
使用 OkHttp 发起 POST 请求,将 code 兑换为第三方平台颁发的 access_token。所有敏感配置如 client_id、client_secret 与 redirect_uri 都集中于配置文件中统一管理,避免硬编码泄露风险。同时为增强兼容性,使用 application/json 作为请求和响应格式。
通过 access_token 获取github用户详细资料