在学习和工作中,不少开发者都曾面对过登录注册的开发难题:无论是前端的表单校验、提交处理与权限控制,还是后端的数据库交互、令牌生成与安全校验,都涉及大量繁琐的逻辑,才能搭建起一套完整的登录注册体系。 此时,若能有一种方案简化这些重复性工作,让开发者得以将精力聚焦在核心业务上,无疑是极大的便利。而 OAuth—— 也就是我们常说的 “开放授权”—— 正是为解决这一问题而生。接下来,我们就一起探究它是如何简化开发流程的。
本文将从概念解析、原理剖析,到以Github OAuth为例的实际项目搭建,为大家系统详解 OAuth 的使用。既能帮你理清核心概念、吃透底层原理,也能让你掌握在项目中落地的具体方法 —— 相信读完本文,你定会有自己的收获。
一、概念
1.1 初步认识
相信大家在使用APP时肯定遇到过“授权登录”的情况,不管是游戏还是办公软件,几乎都离不开它。所以在概念的理解上应该没有太大问题。用一句稍微专业一点的话来解释,OAuth 的思想就是让第三方应用在不获取用户账号密码的前提下,安全地获得用户在某个服务(如微信、GitHub)上的部分资源权限。
这里的第三方应用指的就是APP或者说我们自己开发的应用,它们不直接获取用户的账号密码等信息,而是去向一些比较“权威”的应用(微信、QQ、Github等)去请求用户的授权,从而获得用户的权限和资源。比如一些游戏可能会使用你的好友信息和头像等信息。
1.2 优劣分析
那么从这个流程就可以得知,OAuth到底给我们带来了哪些好处,又引出了哪些问题: 分为三个方面说,分别是用户、开发者、和授权服务提供商,
- 对
用户来说:首先是简便性,省去 “填写手机号、设置密码、验证邮箱” 等注册流程。其次是安全性,在登录的同时可以选择“允许”和“拒绝”一些权限,如访问联系人等,让第三方应用不乱用权限。 - 对
开发者来说:无需从零开发登录功能(看似简单,实则涉及安全加密、合规性等大量细节),直接复用微信、GitHub 等成熟平台的身份验证能力,节省开发和维护成本。其次就是可以提高用户的转化率,“微信登录”肯定比“注册账号”更能让用户“留下”。 - 对
授权服务提供商来说:通过让第三方应用接入自己的账号体系,既能增强用户对自身平台的依赖(用户更难离开),也能通过开放资源(如用户关系、内容)扩大生态影响力(比如微信通过 OAuth 让小程序生态更繁荣)
然而,伴随着如此优势而来的也有一些问题,这里拿 “单点故障” 举例:
试想:当你越依赖某个人或者某件事的时候,或许会发现自己越来越不自由了,事事都要跟随对方的想法😟
这里的OAuth也是一个道理,比如授权方的服务器如果出现问题,就会严重影响第三方应用的使用。再比如,授权方可能调整接口的规则,第三方应用也需要随之调整,增加维护成本。
所以可以看到市面上并不是所有的APP都使用OAuth授权,那么知道了优缺点,现在来想想到底什么样的应用比较适合OAuth授权吧!
1.3 适用场景
- 轻量型工具型应用,即核心价值不在 “用户账号管理”,自建注册登录系统会浪费资源,用 OAuth 可快速复用成熟平台的身份能力。比如在学习开发的时候搭建的一些工具型平台(编辑器等);
- 应用的功能必须结合外部平台的数据才能实现,比如游戏的社交性(排名、邀请好友上线等功能)就依赖于一些社交软件,这种情况也适合用OAuth;
总之如果身份信息比较敏感(如银行、支付类应用)或者对授权方的稳定性要求高(如医疗系统),则不建议用OAuth授权避免导致一些问题。
在了解了OAuth“是什么”之后,还需要知道“为什么”,那么下面就是原理的讲解部分。
二、原理
不同的授权方对于接口定义和授权流程都不尽相同,这里用Github OAuth举例。
先上图,看看大概的流程。
(前端登录后页面可以自行定义,这里只是展示了一个示例)
可以在图中看到三个角色,分别是第三方应用的前端、后端以及github授权方
据图总结出来的以用户为主的流程如下:
- 用户点击 "使用 GitHub 登录" 按钮
- 用户跳转到 GitHub 的授权页面,点击“同意授权账号信息”按钮
- GitHub验证用户身份并准备用户信息
- 用户授权成功后,GitHub 向后端地址发送授权码
- 后端使用授权码向 GitHub 请求访问令牌并使用令牌获取用户信息
- 后端创建 / 更新用户,重定向到对应登陆后的页面
- 前端进行后续身份验证和请求,流程完毕
这个简要的流程很好理解,但是如果上手开发,会发现还是有不少疑惑,那么对于开发者来说,我们还有哪些需要注意的地方呢?下面以问题为导向,给大家解释一下详细的流程。
-
Q1:用户点击登录按钮之后,跳转的URL是什么?我们如何确定?
-
A1:这其实是前置的一个准备操作,我们想要使用github的OAuth服务,那自然也要去github网站做对应的申请。下面给出步骤:
github个人主页——Settings——Developer Settings——OAuth Apps
根据应用实际情况填写正确的前后端地址,得到两个关键参数,
Client ID和Client Secrets,都是用于验证第三方应用的身份信息。界面如下图所示:在得到
ClientId之后,前端就可以由此拼接出github约定的URL完成用户授权,具体拼接规则后面“实现”章节会详细给出。
-
Q2:后端和github服务器的交互为什么需要两次(授权码换取令牌,令牌换取用户信息)?
-
A2:首先,明确授权码是临时凭证而不是核心凭证,有效期通常只有几分钟,需要后端通过clientSecret+授权码才能得到核心凭证令牌,这样是为了防止前端或者其他方窃取授权码从而窃取用户信息,而第二次的交互就是常见的通过令牌请求用户信息;
由此我们不难发现,
client_secret千万千万不能泄漏,才能杜绝 “伪造的第三方应用” 通过窃取授权码获取令牌的可能(因为伪造应用没有client_secret)。另一方面,这也是GitHub服务器单一职责的体现,token令牌由授权服务器(GitHub 的
/login/oauth端点)发放,而用户信息由“资源服务器”(GitHub 的API端点)发放,符合设计模式。
通过上面两个问题的解释相信大家在原理方面已经有了一定的认识,接下来就一起来在项目中通过GithubOAuth来简化登录操作吧!
三、实现
项目背景:本次示例项目主体是仿飞书协同编辑器(目前在起步阶段),属于工具型轻量型项目,所以对于登录功能我们选择GithubOAuth授权。
关于技术栈:前端nextjs后端nestjs
GitHub地址:DocVault(求求star嘿嘿😊)
3.1前端实现
通过前面的步骤可以看到,前端需要准备登录页(跳转前),逻辑也比较简单,按钮绑定跳转事件即可。话不多说直接上代码,前面提到的授权页url拼接规则也一并在代码中给出。
//核心逻辑:跳转函数
const handleSignIn = () => {
const clientId =
process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID;
//后端地址
const redirectUrl =
process.env.NEXT_PUBLIC_AUTH_CALLBACK_URL;
//拼接规则:基本url+client_id+redirect_url+scope
//scope即需要的权限范围,这里选择最基础的user权限也就是用户信息
const githubAuthUrl =
`https://github.com/login/oauth/authorize?` +
`client_id=${clientId}&` +
`redirect_uri=${redirectUrl}&&` +
`scope=user&`;
//跳转到授权页
window.location.href = githubAuthUrl;
};
3.2后端实现
后端需要在对应的回调接口实现对于授权码的处理逻辑(请求token进而请求用户信息),最后将用户信息存入数据库。以下是代码实现,注释详细标注了每个步骤。
//核心函数1,两次请求得到用户信息
async handleGitHubCallback(code: string) {
//通过code请求accesstoken,nest官方封装了axios为HttpService,使用请求很方便
const { access_token: accessToken, error } = await this.getGitHubAccessToken(code);
if (error || !accessToken) {
throw new Error(`GitHub 令牌获取失败: ${error}`);
}
//通过accessToken换取用户信息
const githubUser = await this.getGitHubUserInfo(accessToken);
//通过数据库更新/新建用户信息
const localUser = await this.userService.findOrCreate({
name: githubUser?.name || '',
githubUserId: githubUser.id.toString(),
id: githubUser.id,
avatar: githubUser?.avatar_url || '',
});
return { user: localUser };
}
//核心函数2:与数据库的交互
async findOrCreate(dto: FindOrCreateUserDto): Promise<FindOrCreateUserInterface|null> {
//prisma的upsert函数逻辑,先根据唯一性参数查找对应用户
//如果找到了就根据传入信息更新,没找到则根据传入信息新建
const user = await this.prisma.user.upsert({
// 1. 查找条件:根据 githubUserId 匹配
where: {
githubUserId: dto.githubUserId,
},
// 2. 若存在:更新字段
update: {
name: dto?.name || '',
avatar: dto?.avatar || '',
},
// 3. 若不存在:创建新用户
create: {
githubUserId: dto.githubUserId,
name: dto?.name || '',
avatar: dto?.avatar || '',
},
});
return user;
}
至此授权工作就已经圆满完成了,后续前端对应请求后端的用户信息就可以进行使用和操作。
3.3优化和完善
当然,作为一个成熟的项目开发,最基本的功能远远不能满足业务的需要,所以提出一些优化方案或者说后续思考(和GitHubOAuth也就是本文的主题不太相关所以不展开叙述),并从中挑选出有代表性的进行讲解,提升项目的健壮性。
- 登录成功后后端
jwt的生成与发放(推荐采用httpOnly Cookie防止安全问题) - 前端的访问控制,不登陆无法访问的路由需要进行拦截
- 登出/注销操作的实现
由此可见,GitHubOAuth作为登录只能省去注册和校验的部分步骤,后续的维护还是和传统登录体系一样比较复杂。不过总而言之,它对我们的团队开发来说,还是能节约相当大一部分时间的!
四、总结
一个好的learner不仅要在短时间内学会知识并加以应用,更要学会总结(具体的知识变为抽象的思想)方可每次有进步。这也是我每篇文章都在给大家传递的想法。以下是我这两周调研并开发项目登录体系的一些小总结:
-
原理和实践的关系:有的人认为在实践之前必须要将原理吃透,在做事时才能胸有成竹。在接触开发前我也是这个想法,但是我们作为开发者,一方面有时候没有充足的时间;另一方面技术的更迭导致我们从低到高的学习成本不断加重。
所以个人最佳实践应该是:从应用入手,在
应用的同时保持对原理的好奇和探究(通俗点说就是方便提问)。这样在完成目标的同时收获了知识。 比如我在为项目调研github登录时,就是先知道步骤开始实施,过程中提出一些问题并解决,比如辩证的发现其局限性,以及我们的项目为什么合适用这种方式。 -
关于知识本身,OAuth 的本质是一套开放授权的标准化协议(Protocol) ,更准确地说,是一套定义了 “第三方应用如何安全获取用户在某服务上的资源权限” 的规则体系。核心目标是解决 “跨平台授权” 的安全与标准化问题。为了让文章更易懂,我弱化了这部分知识的讲解,有兴趣的同学可以自行学习。
最后相信大家通过文章「概念→原理→实现→总结」的递进逻辑,从 “是什么(OAuth 定义 / 优劣 / 适用场景)” 到 “为什么(GitHub OAuth 原理)”,再到 “怎么做(前后端具体代码)”,最后升华到 “学习方法”,都会有自己的感受和收获。