本文是[链接]的一个章节,该系列文章将介绍 [链接] 这款 Java 博客系统是如何从无到有的,希望大家能通过它对 Solo 从设计到实现有个直观地了解、能为想参与贡献的人介绍清楚项目,也希望能为给重复发明重新定义博客系统的人做个参考 申请授权 Solo 是通过 GitHub OAuth 实现登录验证的。我们在 Git ..

Solo 登录验证 GitHub OAuth

本文是《Solo 从设计到实现》的一个章节,该系列文章将介绍 Solo 这款 Java 博客系统是如何从无到有的,希望大家能通过它对 Solo 从设计到实现有个直观地了解、能为想参与贡献的人介绍清楚项目,也希望能为给重复发明重新定义博客系统的人做个参考 ❤️

申请授权

Solo 是通过 GitHub OAuth 实现登录验证的。我们在 GitHub 的 B3log 组织下创建了一个 Solo OAuth 应用,并通过社区接口 https://hacpai.com/oauth/solo/client2 返回该应用的 id 以及验证地址(每次登录时都会通过 Solo 服务端请求社区接口来获取)。

方法OAuthProcessor#redirectAuth

/**
 * Redirects to auth page.
 *
 * @param context the specified context
 */
@RequestProcessing(value = "/oauth/github/redirect", method = HttpMethod.GET)
public void redirectAuth(final RequestContext context) {
    final HttpResponse res = HttpRequest.get("https://hacpai.com/oauth/solo/client2").trustAllCerts(true).
            connectionTimeout(3000).timeout(7000).header("User-Agent", Solos.USER_AGENT).send();
    if (HttpServletResponse.SC_OK != res.statusCode()) {
        LOGGER.log(Level.ERROR, "Gets oauth client id failed: " + res.toString());

        context.sendError(HttpServletResponse.SC_NOT_FOUND);

        return;
    }
    res.charset("UTF-8");
    final JSONObject result = new JSONObject(res.bodyText());
    if (0 != result.optInt(Keys.CODE)) {
        LOGGER.log(Level.ERROR, "Gets oauth client id failed: " + result.optString(Keys.MSG));

        return;
    }
    final JSONObject data = result.optJSONObject(Keys.DATA);
    final String clientId = data.optString("clientId");
    final String loginAuthURL = data.optString("loginAuthURL");

    String referer = context.param("referer");
    if (StringUtils.isBlank(referer)) {
        referer = Latkes.getServePath();
    }
    final String cb = Latkes.getServePath() + "/oauth/github";
    final String state = referer + ":::" + RandomStringUtils.randomAlphanumeric(16) + ":::cb=" + cb + ":::";
    STATES.put(state, URLs.encode(state));

    final String path = loginAuthURL + "?client_id=" + clientId + "&state=" + state
            + "&scope=public_repo,read:user,user:follow";

    context.sendRedirect(path);
}

动态获取 OAuth 应用信息是考虑到未来一些不可控因素,比如应用下架、网络不通等。一旦发生这样无法解决的问题时,我们会在社区开发自己的 OAuth 应用来平滑切换验证体系,不影响用户使用。

另外,关于 GitHub 权限申请以及凭证(Access Token)等安全相关设计考量请参考这里

授权后回调

从 GitHub 授权后会回调社区验证服务 https://oauth.b3log.org,该服务主要做的事情是映射凭证:生成一个随机凭证和 GitHub 凭证做关联,并缓存获取到的 GitHub 用户信息。

然后继续将请求重定向回到用户 Solo 端,此时凭证信息会使用刚刚生成的随机凭证,避免真实凭证泄露。

回调到 Solo

处理来自 oauth.b3log.org 的回调请求:

  1. 验证 state 防止重放攻击
  2. 处理 Referer 获取参数以及后续跳转
  3. 使用随机凭证到社区获取之前缓存的用户信息
  4. 根据 Open Id(GitHub User Id)查库,并根据系统状态处理(初始化、添加访客用户等)

方法OAuthProcessor#authCallback

/**
 * OAuth callback.
 *
 * @param context the specified context
 */
@RequestProcessing(value = "/oauth/github", method = HttpMethod.GET)
public synchronized void authCallback(final RequestContext context) {
    final String state = context.param("state");
    String referer = STATES.get(state);
    if (StringUtils.isBlank(referer)) {
        context.sendError(HttpServletResponse.SC_BAD_REQUEST);

        return;
    }
    STATES.remove(state);
    referer = URLs.decode(referer);
    final String accessToken = context.param("ak");
    final JSONObject userInfo = GitHubs.getGitHubUserInfo(accessToken);
    if (null == userInfo) {
        LOGGER.log(Level.WARN, "Can't get user info with token [" + accessToken + "]");
        context.sendError(HttpServletResponse.SC_UNAUTHORIZED);

        return;
    }

    final String openId = userInfo.optString("openId");
    JSONObject user = userQueryService.getUserByGitHubId(openId);
    if (null == user) {
        if (!initService.isInited()) {
            // 初始化系统
	    ....
        } else {
            user = userQueryService.getUserByName(userName);
            if (null == user) {
                // 添加访客用户
		....
            } else {
                // 绑定已有用户 Open Id
		....
            }
        }
    }

    // 一些验证
    ....

    final String redirect = StringUtils.substringBeforeLast(referer, "__");
    Solos.login(user, response);
    context.sendRedirect(redirect);
    LOGGER.log(Level.INFO, "Logged in [name={0}, remoteAddr={1}] with oauth", userName, Requests.getRemoteAddr(request));
}

一点优化

这里的优化主要是指获取 GitHub 用户信息时的速度优化,因为国内访问 GitHub API 实在太慢,所以我们将 B3log OAuth 服务部署在国外,这样稳定性更好并且速度也快很多 😅


回到全文目录:《Solo 从设计到实现》

  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。

    Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!
    具体细节请浏览 B3log 构思

    712 引用 • 5414 回帖 • 698 关注
  • 设计
    87 引用 • 550 回帖 • 1 关注
  • 文档
    51 引用 • 949 回帖 • 1 关注
12 回帖   
请输入回帖内容...
  • WOLFCHAN  

    不明觉厉, 大佬加油😄

  • EvilCodes  

    涛哥对技术是完全的开放啊,没有一点私心

    1 回复
  • 88250      

    谁是涛哥 😂

    1 回复
  • EvilCodes        

    打错字了

    1 回复
  • 88250      

    社区的回帖内容可以编辑更新的。

  • rainningLovexiang  

    目标是成为像大佬您一样的人物

  • ellenbboe  

    image.png
    不知道写的对不对 -.-

    1 回复
  • 88250      

    有心了,差不多就是这个意思吧 😊

  • xiaoyao2102  

    请问现在只能用 github 登录了么?

    1 回复
  • 88250      

    是的呢。

    1 回复
  • xiaoyao2102        

    不是特别喜欢自己 github 的账号名哈哈。。能换一个最好呢

    1 回复
  • 88250      

    GitHub 可以改名的,改好以后社区这边私信我你的新用户名。博客端需要你自己手动修改数据库。

请输入回帖内容 ...