vortesnail/blog

部署阿里云云函数舒畅无阻的请求 Github OpenApi 实现 OAuth2.0 登录

Opened this issue · 0 comments

在自己建站的过程中,想要实现登录功能其实并不是一个简单的活儿,它的主要难点不在于技术实现,而是难在独立开发者往往没有太多资金投入。

举个例子,如果我现在要实现一个手机号码短信登录,先不考虑用户意愿问题,我们需要购买的第一个第三方服务就是短信发送的服务,每发送一条短信验证码,都是真金白银的钱,为了防止被人恶意刷量,还要购买行为验证码(或风控相关的)服务,这背后要多少钱大家可以自行了解下。

换个思路,我们支持微信登录好了,结果一查要注册公司盖章提交审核,才能使用微信的第三方登录功能,还要每年交个 300 元。所以可选择的不多,Github OAuth 登录是我的首选项,因为大多数开发者都会有自己的 Github 账号,而 Github OAuth 登录又没有类似微信的各种限制。

不过问题是,Github 登录过程中要调用它的 OpenApi,如果你和我一样买的是大陆的云服务器,会经常超时,这非常令人头疼,修改 host 什么的根本卵用没有,买国外的服务器又死贵。不过好在阿里云的 FC(云函数)产品还是挺便宜的,接下来就介绍下如何利用云函数调用 OpenApi,而国内服务器又能调该云函数获取返回结果。

前提

因为我购买的是阿里云的 FC 产品,所以你最好也是和我一样,有一个阿里云账号,如果你还没购买,新用户可以试用三个月。

打开控制台

找到产品函数计算 FC,点击【管理控制台】。

进入之后,你会看到账号下的基础信息。

创建服务

找到左边菜单的【服务及函数】按钮,点击跳转后,将注意力移到最上方,地理位置选择美区(只要不是大陆地区就行),非常重要!

接着我们点击【创建服务】,名称随便填写,最好和我们要实现的功能相近。打开【高级选项】,我们要选择一个角色,没有的话,点下面创建一个就行。

创建函数

服务创建完成后,我们就可以开始创建云函数,点击【创建函数】。

选择【使用自定义运行时创建】,函数名称写一个比较贴近函数功能的名字,请求处理程序类型选择【处理 HTTP 请求】,运行环境推荐选比较稳定的 Node.js 16,代码上传方式我选择的是【使用示例代码】,这样我们可以知道如何写一个 Hello World 级别的云函数,并正确调用后慢慢实现我们的功能函数。

其它的选项我们都可以先使用默认的,点击底部【创建】按钮完成创建。

调用云函数

创建完成后,来到这一个选项卡【触发器管理(URL)】,就可以看到我们的访问地址了。

在选项卡【函数代码】中可以直接编写我们的接口,【测试函数】能够进行接口调用测试,都是很实用的,这里我们先打开【函数代码】看看写了啥。

可以看到我们的接口代码,使用 Apifox 调用下这个接口测试下。

返回正确,证明我们的云函数部署成功了。接下来就可以大施拳脚改造我们的请求接口,实现 Github Openapi 的调用了。

比如我主要是实现 OAuth2.0 登录,代码如下:

const express = require('express');
const bodyParser = require('body-parser');
const axios = require('axios')

const app = express();

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(bodyParser.raw());

const port = 9000

app.get('/oauth/access_token', async (req, res) => {
  let errorMsg
  let tokenResponse = {}

  try {
    const { client_id, client_secret, code } = req.query
    tokenResponse = await axios({
      method: 'post',
      url:
        'https://github.com/login/oauth/access_token?' +
        `client_id=${client_id}&` +
        `client_secret=${client_secret}&` +
        `code=${code}`,
      timeout: 6000,
      headers: {
        accept: 'application/json',
      },
    })
  } catch (error) {
    errorMsg = error
  }

  res.send(errorMsg ? {
    errorMsg
  } : {
    ...tokenResponse.data
  })
})

app.get('/oauth/user_info', async (req, res) => {
  let errorMsg
  let githubUserInfo = {}

  try {
    const { access_token } = req.query
    githubUserInfo = await axios({
      method: 'get',
      url: 'https://api.github.com/user',
      timeout: 6000,
      headers: {
        accept: 'application/json',
        Authorization: `token ${access_token}`,
      },
    })
  } catch (error) {
    errorMsg = error
  }

  res.send(errorMsg ? {
    errorMsg
  } : {
    ...githubUserInfo.data
  })
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

上面我写了两个接口,没在同一个接口实现两个接口的请求是因为会 401,但是分开就可以,这里非常奇怪,我也不知道是哪里问题,非常奇怪。

最后

现在你就可以在自己服务端去调这些你写好的接口了,比如我的调用如下:

async oauthGithubLogin(oauthGithubLoginDto: OAuthGithubLoginDto) {
  const { code } = oauthGithubLoginDto

  let tokenResponse: any = {}
  let githubUserInfo: any = {}

  try {
    tokenResponse = await axios({
      method: 'get',
      url:
        'https://github-auth-api-github-openapi-phktxmgeeb.us-east-1.fcapp.run/oauth/access_token?' +
        `client_id=${OAUTH_GITHUB_CLIENT_ID}&` +
        `client_secret=${OAUTH_GITHUB_CLIENT_SECRET}&` +
        `code=${code}`,
      timeout: 6000,
    })
  } catch (error) {
    throw new RequestTimeoutException('Request github openapi timed out.')
  }

  try {
    const { access_token: accessToken } = tokenResponse.data || {}
    githubUserInfo = await axios({
      method: 'get',
      url: 'https://github-auth-api-github-openapi-phktxmgeeb.us-east-1.fcapp.run/oauth/user_info?' + `access_token=${accessToken}`,
      timeout: 6000,
    })
  } catch (error) {
    throw new RequestTimeoutException('Request github openapi timed out.')
  }

  console.log(githubUserInfo)
  // 后续逻辑
}

好了,终于可以愉快调用 Github 的 OpenApi 了,整就一个舒畅。