[SpringStack] 快速登录-9分钟给你站点接入Github登录
本文章案例为Java在web端接入GitHub登录,大致流程相同。
一: 设置Github的ClientId和
- Settings --> DeveloperSettings -->OAuth Apps
1.1 创建一个newOAuth app
1.2 创建成功之后会生成一个Clinet ID和 Client secrets 用于在服务端交互时候使用
二:客户端按钮跳转代码
按钮的跳转地址为:
https://github.com/login/oauth/authorize?client_id=KaTeX parse error: Expected 'EOF', got '&' at position 16: {你生成的client_id}&̲redirect_uri={你服务端的跳转地址}.
假如我的client_id为 Ov22342345234,服务端的回调地址:http://127.0.0.1:8080/admin/github/callback
则我客户端的地址为:https://github.com/login/oauth/authorize?client_id=Ov22342345234&redirect_uri=http://127.0.0.1:8080/admin/github/callback
然后嵌入到web页面中。
<div class="row"><div class="col-xs-12"><a href="https://github.com/login/oauth/authorize?client_id=${你生成的client_id}&redirect_uri=http://127.0.0.1:8080/admin/github/callback" class="btn btn-default btn-block btn-flat">使用 GitHub 登录</a></div>
</div>
三:服务端回调地址:
用户在客户端点击GitHub登录点击授权登录,GitHub会回调到redirect_uri的地址,在后面会带一个code码,我们服务端回拿着这个code码换取access_token,因为要校验client_id下服务的唯一性
3.1 表设计:
如果B2C业务模式的话:C端用户player,B端用户 user
如果B2B2C业务模式的话: C端用户player,B端用户 user,多一个M端用户manager(用来控制平台的运转比如数据修复等等相关业务)
more字段是存储三方回调成功之后的用户信息,比如:{“login”:“xxx”,“id”:xxx,“avatar_url”:“xxx”,“name”:“xxx”,“email”:“xxxx@email.com”}
CREATE TABLE `third_user_account` (`third_user_account_id` bigint(20) unsigned NOT NULL COMMENT '主键ID',`create_id` bigint(20) NOT NULL COMMENT '创建人 @link user.id (-1为:系统)',`create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',`modifier_id` bigint(20) unsigned DEFAULT NULL COMMENT '修改人id @link user.id (-1为:系统)',`modify_time` datetime(6) DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '修改时间',`version` int(11) NOT NULL DEFAULT '1' COMMENT '版本号',`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除: true 删除 false 未删除',`enabled` tinyint(1) DEFAULT '1' COMMENT '启用状态: true 启用 (默认) false: 禁用',`is_bound` tinyint(1) DEFAULT '0' COMMENT '是否已绑定: 0 未绑定(默认) 1 已绑定 ',`third_plat_type_code` enum('github','qq','wechat') NOT NULL COMMENT '三方平台类型',`third_unique` varchar(100) NOT NULL COMMENT '三方平台唯一标识(如三方ID标识)',`third_username` varchar(155) DEFAULT NULL COMMENT '账号',`third_email` varchar(155) DEFAULT NULL COMMENT '邮箱',`more` text DEFAULT NULL COMMENT '其他',`remark` varchar(255) DEFAULT NULL COMMENT '备注',PRIMARY KEY (`third_user_account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='三方用户账号表';
四: 编写服务端代码 (本文以Java为例)
这里的代码是GitHub
@RequestMapping("/github/callback")
public String handleGitHubCallback(@RequestParam("code") String code, HttpServletResponse response) {String accessToken = getAccessTokenFromGitHub(code);// 使用 access_token 获取用户信息GitHubUser user = getGitHubUserInfo(accessToken);// 绑定用户XxlJobUser jobUser = bindGitHubUserToPlatform(user);if (!validUserLogin(jobUser)) {log.error("用户:{}, 账号不存在或已经禁用", jobUser);return "账号不存在或已经禁用";}log.info("callback code:{}, state:{}", jobUser);CookieUtil.set(response, LOGIN_IDENTITY_KEY, makeToken(jobUser), Boolean.TRUE);// 重定向到用户登录后的页面return "redirect:/";
}
- 使用 OkHttp 获取 GitHub access_token
private String getAccessTokenFromGitHub(String code) {String clientId = "你生成的client_id";String clientSecret = "你的密钥";String url = "https://github.com/login/oauth/access_token";String redirectUri = "http://127.0.0.1:8080/admin/github/callback";OkHttpClient client = new OkHttpClient();// 构建请求体RequestBody formBody = new FormBody.Builder().add("client_id", clientId).add("client_secret", clientSecret).add("code", code).add("redirect_uri", redirectUri).build();// 创建 POST 请求Request request = new Request.Builder().url(url).post(formBody).header("Accept", "application/json").build();try (Response response = client.newCall(request).execute()) {if (!response.isSuccessful()) {throw new IOException("Unexpected code " + response);}// 获取响应体并返回 access_tokenString responseBody = response.body().string();return parseAccessTokenWithGson(responseBody);} catch (IOException e) {e.printStackTrace();}return null;
}
- 使用 Gson 解析 access_token
private String parseAccessTokenWithGson(String responseBody) {// 使用 Gson 解析返回的 JSON 响应JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject();return jsonObject.get("access_token").getAsString();
}
- 根据 GitHub 用户信息绑定或创建用户账户.检查用户 email 是否已经注册,或者根据 GitHub id 进行绑定.
/*** 根据 GitHub 用户信息绑定或创建用户账户.* 检查用户 email 是否已经注册,或者根据 GitHub id 进行绑定.* @param gitHubUser* @return
*/
public XxlJobUser bindGitHubUserToPlatform(GitHubUser gitHubUser) {List<ThirdUserAccountPO> list = ChainWrappers.lambdaQueryChain(thirdUserAccountDAO).eq(ThirdUserAccountPO::getThirdUnique, gitHubUser.getId()).eq(ThirdUserAccountPO::getThirdPlatTypeCode, ThirdPlatformTypeEnum.GITHUB.getCode()).list();ThirdUserAccountPO thirdUserAccountPO;if (CollectionUtils.isEmpty(list)) {thirdUserAccountPO = new ThirdUserAccountPO();thirdUserAccountPO.setThirdUnique(String.valueOf(gitHubUser.getId()));thirdUserAccountPO.setCreateId(-1L);thirdUserAccountPO.setThirdPlatTypeCode(ThirdPlatformTypeEnum.GITHUB.getCode());Optional.ofNullable(gitHubUser.getEmail()).ifPresent(v -> thirdUserAccountPO.setThirdEmail(v));Optional.ofNullable(gitHubUser.getLogin()).ifPresent(v -> thirdUserAccountPO.setThirdUsername(v));thirdUserAccountPO.setMore(new Gson().toJson(gitHubUser));thirdUserAccountPO.setIsBound(Boolean.TRUE);thirdUserAccountDAO.insert(thirdUserAccountPO);} else {thirdUserAccountPO = list.get(0);}//生成绑定到后台账户XxlJobUser xxlJobUser = ChainWrappers.lambdaQueryChain(xxlJobUserDao).eq(XxlJobUser::getThirdUserAccountId, thirdUserAccountPO.getThirdUserAccountId()).one();String thirdUnique = thirdUserAccountPO.getThirdUnique();if (xxlJobUser == null) {xxlJobUser = new XxlJobUser();xxlJobUser.setUsername("gh_".concat(thirdUnique.substring(0,3)).concat(thirdUserAccountPO.getThirdUsername()));HashCode hashPwd = Hashing.hmacMd5(thirdUnique.getBytes(StandardCharsets.UTF_8)).hashString(thirdUnique, StandardCharsets.UTF_8);xxlJobUser.setPassword(hashPwd.toString());xxlJobUser.setRole(0);xxlJobUser.setThirdUserAccountId(thirdUserAccountPO.getThirdUserAccountId());xxlJobUserDao.save(xxlJobUser);}return xxlJobUser;
}
- 校验用户是否是可用状态
private Boolean validUserLogin(XxlJobUser xxlJobUser) {if (xxlJobUser.getDeleted() || xxlJobUser.getStatus() != AccountStatusEnum.ENABLED.getCode()) {return Boolean.FALSE;}return Boolean.TRUE;
}
总结
本文只是使用web演示了GitHub的登录接入,如果使用App跳转,只需要把接口请求成功后,返回跳转地址的标识改为跳转回APP就可以了。
参考资料 & 致谢
[1] GitHub Apps
[2] About creating GitHub Apps