1. 前言
在前期研究 yudao-cloud 代码时,对其中微信小程序的登录流程理解不够深入。然而,在后续项目中涉及 OAuth 2.0 对接需求时,通过实际操作与实践,相关流程逐渐变得清晰。因此,特在此进行详细记录,以便后续查阅与参考。
2. OAuth 2网页端登录流程
此处,借用飞书的网页应用登录流程图进行展示,网页应用登录流程流程可分为四部分,具体如下:
获取登录授权码code;
根据code获取access_token;
根据access_token获取用户身份信息;
刷新access_token(可选步骤,本文不做详细解释)。
2.1 获取登录授权码code
当用户访问网页应用并需要进行飞书授权时,系统会将网页重定向至飞书的授权页面。用户在授权页面完成授权操作后,浏览器将携带授权码code
)跳转至网页应用在飞书平台配置的回调地址redirect_uri
)。
2.1.1 授权码请求
请求URL示例:https://accounts.feishu.cn/open-apis/authen/v1/authorize?client_id=cli_a5d611352af9d00b&redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Foauth%2Fcallback&scope=bitable:app:readonly%20contact:contact&state=RANDOMSTRING
URL中关键参数讲解:
redirect_uri
:授权应用重定向地址,注意拼接至URL时需要进行URL编码;
此地址一般实现为前端空白页,由前端空白页将code参数传递至网页应用后端。
client_id
:网页应用在飞书登记的App ID;state
:用来维护请求和回调之间状态的附加字符串,在授权完成回调时会原样回传此参数,应用可以根据此字符串来判断上下文关系;
该参数也可以用以防止 CSRF 攻击,请务必校验 state 参数前后是否一致;
实现上可以为随机字符串;
一般上为非必填字段,若网页应用未生成,则授权应用回调时会忽略此字段。
2.2.1 授权码响应
授权成功响应示例:https://example.com/api/oauth/callback?code=2Wd5g337vo5BZXUz-3W5KECsWUmIzJ_FJ1eFD59fD1AJIibIZljTu3OLK-HP_UI1&state=RANDOMSTRING
当用户同意授权后,浏览器将携带授权码code
重定向到发起授权时给定的redirect_uri
(若网页应用发起授权请求时携带state
,则重定向地址也将携带state
)。
2.2 获取access_token
OAuth 令牌接口,可用于获取 user_access_token
以及 refresh_token
(部分授权应用可能未实现refresh_token
)。
user_access_token
为用户访问凭证,使用该凭证可以以用户身份调用 OpenAPI。refresh_token
为刷新凭证,可以用来获取新的user_access_token
。
2.2.1 access_token请求
请求参数示例:
{
"grant_type": "authorization_code",
"client_id": "cli_a5ca35a685b0x26e",
"client_secret": "baBqE5um9LbFGDy3X7LcfxQX1sqpXlwy",
"code": "a61hb967bd094dge949h79bbexd16dfe",
"redirect_uri": "https://example.com/api/oauth/callback",
"code_verifier": "TxYmzM4PHLBlqm5NtnCmwxMH8mFlRWl_ipie3O0aVzo"
}
参数解释:
grant_type
:授权类型,当前场景固定为authorization_code
;client_id
:同2.1.1章节的解释;client_secret
:网页应用分配的secret
,授权应用使用client_id
以及client_secret
综合验证网页应用身份;redirect_uri
:同2.1.1章节的解释。
2.2.2 access_token响应
成功响应示例:
{
"code": 0,
"access_token": "eyJhbGciOiJFUzI1NiIs**********X6wrZHYKDxJkWwhdkrYg",
"expires_in": 7200, // 非固定值,请务必根据响应体中返回的实际值来确定 access_token 的有效期
"refresh_token": "eyJhbGciOiJFUzI1NiIs**********XXOYOZz1mfgIYHwM8ZJA",
"refresh_token_expires_in": 604800, // 非固定值,请务必根据响应体中返回的实际值来确定 refresh_token 的有效期
"scope": "auth:user.id:read offline_access task:task:read user_profile",
"token_type": "Bearer"
}
参数解释:
expires_in
:access_token
失效时间,默认单位为秒;refresh_token
:用于刷新access_token
;
2.3 根据access_token获取用户信息
网页应用后端服务,需要根据2.2章节获取的access_token
(一般存储至header
中),访问授权应用获取用户的信息。
以下是飞书返回的个人信息示例(一般使用open_id作为用户的身份唯一标识):
{
"code": 0,
"msg": "success",
"data": {
"name": "zhangsan",
"en_name": "zhangsan",
"avatar_url": "www.feishu.cn/avatar/icon",
"avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
"avatar_middle": "www.feishu.cn/avatar/icon_middle",
"avatar_big": "www.feishu.cn/avatar/icon_big",
"open_id": "ou-caecc734c2e3328a62489fe0648c4b98779515d3",
"union_id": "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
"email": "zhangsan@feishu.cn",
"enterprise_email": "demo@mail.com",
"user_id": "5d9bdxxx",
"mobile": "+86130002883xx",
"tenant_key": "736588c92lxf175d",
"employee_no": "111222333"
}
}
3. JuatAuth完成飞书网页应用登录
JustAuth
是一款开箱即用的开源组件,能够高效整合第三方登录功能。在yudao-cloud
项目中,它被广泛应用于众多第三方登录的集成工作。鉴于我们之前已经详细介绍了飞书网页应用的登录流程,接下来我们将深入探讨如何借助JustAuth
实现飞书登录流程的具体操作。
3.1 创建AuthRequest
创建AuthRequest,根据在飞书平台的注册信息,完成相应参数的填写。
AuthRequest authRequest = new AuthFeishuRequest(AuthConfig.builder()
.clientId("App ID")
.clientSecret("App Secret")
.redirectUri("重定向 URL")
.build());
3.2 生成飞书的授权地址
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
这个链接我们可以直接后台重定向跳转,也可以返回到前端后,前端控制跳转。
前端控制的好处就是,可以将第三方的授权页嵌入到iframe中,适配网站设计。
生成飞书授权地址的逻辑比较简单,具体如下所示:
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize())
.queryParam("app_id", config.getClientId())
.queryParam("redirect_uri", GlobalAuthUtils.urlEncode(config.getRedirectUri()))
.queryParam("state", getRealState(state))
.build();
}
3.3 完整流程示意
@RestController
@RequestMapping("/oauth")
public class RestAuthController {
@RequestMapping("/render")
public void renderAuth(HttpServletResponse response) throws IOException {
AuthRequest authRequest = getAuthRequest();
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState()));
}
@RequestMapping("/callback")
public Object login(AuthCallback callback) {
AuthRequest authRequest = getAuthRequest();
return authRequest.login(callback);
}
private AuthRequest getAuthRequest() {
return new AuthFeishuRequest(AuthConfig.builder()
.clientId("App ID")
.clientSecret("App Secret")
.redirectUri("重定向 URL")
.build());
}
}
完整版代码如上所示,通过以上代码即可完成飞书网页应用的登录流程。
JustAuth可以帮助我们省去较多的样板代码,此外还可以帮我们托管access_token
等,减少开发工作量。
4. JustAuth完成小程序登录
小程序的登录流程与OAuth 2.0流程在原理上具有相似性,但为了适应小程序的特定环境和开发规范,需要进行一些针对性的适配工作。主要区别点在于:
微信小程序的授权码
code
是通过小程序中的wx.login
函数获取的,因此无需配置redirect_uri
;
微信小程序授权码是小程序中获取的,因此不需要校验
state
。
4.1 创建AuthRequest
AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
.clientId("xx")
.clientSecret("xx")
.ignoreCheckRedirectUri(true)
.ignoreCheckState(true)
.build());
根据上文的描述,微信小程序的AuthRequest
创建如上图所示(可与3.1章节进行比较)。
4.2 生成微信小程序的授权地址
小程序平台的授权登录不需要回调地址,不需要调用authRequest.authorize(AuthStateUtils.createState())
方法,此步略去。
4.3 完整流程示意
@RestController
@RequestMapping("/oauth")
public class RestAuthController {
@RequestMapping("/callback")
public Object login(AuthCallback callback) {
AuthRequest authRequest = getAuthRequest();
return authRequest.login(callback);
}
private AuthRequest getAuthRequest() {
return new AuthWechatMiniProgramRequest(AuthConfig.builder()
.clientId("xx")
.clientSecret("xx")
.ignoreCheckRedirectUri(true)
.ignoreCheckState(true)
.build());
}
}
5 总结
不同公司,乃至同一公司内部的各个部门,在实现OAuth 2.0流程时往往存在差异,并未完全严格按照标准规范执行。
因此,我们唯有深入熟悉并理解这些流程的细节与特点,才能更精准、高效地编写相应的对接代码,确保系统的顺利集成与运行。