使用github进行第三方登录

156 阅读3分钟

  在网上找了很多相关资料,发现说的都不是很清楚,就自己尝试写了。

注册 OAuth

  首先去 github 注册一个新的 OAuth 应用,请点此去往。以下为填写示例: 注册 OAuth   注册完之后,点进该实例。其中的Application logo可以上传应用图片,这里需要生成一个Client secrets,生成后请立即复制(后面需要使用)。如下图: 生成Client secrets

前端

注册路由

  分别注册首页、登录页及重定向页面。

import { createRouter, createWebHistory } from "vue-router";

const routes = [
  { path: "/", component: () => import("@/pages/home/index.vue") }, // 首页
  { path: "/login", component: () => import("@/pages/login/index.vue") }, // 登录页
  { path: "/redirect", component: () => import("@/pages/redirect/index.vue") }, // 重定向页
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

const whiteList = ["/login", "/redirect"];

router.beforeEach(async (to, _from, next) => {
  if (whiteList.indexOf(to.path) !== -1) return next();
  // 存放的用户信息
  const github = localStorage.getItem("github");
  if (github) {
    next();
  } else {
    next({ path: "/login" });
  }
});

export default router;

登录页

  这里需要打开跳转至 github 认证页,一般来说这个按钮是放一个 github 的图标,对应接口链接

<script setup lang="ts">
const login = async () => {
  const state = Math.floor(Math.random() * Math.pow(10, 8));
  window.location.href =
    "https://github.com/login/oauth/authorize?client_id=CLIENT_ID&redirect_uri=https://githubauth.vercel.app/redirect&state=" +
    state;
};
</script>

<template>
  <div class="page">
    <button @click="login">登录</button>
  </div>
</template>

重定向页

  向后端发送获取access_token的请求,请注意这个接口https://github.com/login/oauth/access_token不要在前端调用。这个错误编写的时候找了很久,然后发现不能在前端调用。获取到 access_token后就可以去github换取用户信息。

<script setup lang="ts">
import { onMounted } from "vue";
import { useRoute, useRouter } from "vue-router";
import axios from "axios";

const router = useRouter();

const loginAction = async (code: string) => {
  const res = await axios.get("https://github-admin-sooty.vercel.app/auth", {
    params: {
      code,
    },
  });
  const { access_token, token_type } = res.data;
  const token = `${token_type} ${access_token}`;
  const result = await axios.get("https://api.github.com/user", {
    headers: {
      Authorization: token,
    },
  });
  const { avatar_url, login } = result.data;
  localStorage.setItem(
    "github",
    JSON.stringify({
      avatar_url,
      login,
      token,
    })
  );
  router.replace("/");
};

onMounted(() => {
  const route = useRoute();
  const code = route.query.code as string;
  if (code) {
    loginAction(code);
  } else {
    router.replace("/login");
  }
});
</script>

<template>
  <div class="page">登录中...</div>
</template>

首页

  这里比较常规,就是展示一些数据。

<template>
  <div class="home">
    <div class="avatar">
      <img :src="user?.avatar_url" alt="" />
    </div>
    <span class="username">{{ username }}</span>
  </div>
</template>

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
type userType = {
  avatar_url?: string;
  login?: string;
};
const user = ref<userType>({});
const username = ref("");
const timers = ref<(number | NodeJS.Timeout)[]>([]); // 用于存储定时器ID

const printText = () => {
  if (user.value.login) {
    for (let i = 0; i < user.value.login.length; i++) {
      const timer = setTimeout(() => {
        username.value += user.value.login![i];
      }, i * 400);
      timers.value.push(timer); // 将定时器ID添加到数组中
    }
  }
};
onMounted(() => {
  const github = localStorage.getItem("github");
  if (github) {
    const { avatar_url, login } = JSON.parse(github);
    user.value = { avatar_url, login };
    printText();
  }
});

onBeforeUnmount(() => {
  // 清除所有的定时器
  timers.value.forEach((timer) => {
    clearTimeout(timer);
  });
});
</script>

<style scoped>
.home {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.avatar {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  border: 1px solid #ccc;
  overflow: hidden;
}
.avatar img {
  width: 100%;
  height: 100%;
}
.username {
  margin-top: 10px;
}
</style>

部署到 vercel

  文件名为:vercel.json

{
  "routes": [
    {
      "src": "/(.*).css$",
      "headers": { "content-type": "text/css" },
      "dest": "/$1.css"
    },
    {
      "src": "/(.*).js$",
      "headers": { "content-type": "text/javascript" },
      "dest": "/$1.js"
    },
    { "src": "/(.*)", "dest": "/index.html" }
  ]
}

后端

  这里使用了一个全局错误中间件,用于捕获可能产生的错误。

const Koa = require("koa");
const cors = require("@koa/cors");
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const axios = require("axios");

const app = new Koa();
const router = new Router();

// 跨域
app.use(cors());
// 解析参数
app.use(bodyParser());

// 全局错误处理中间件
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = {
      code: "-1",
      msg: err.message,
    };
  }
});

router.get("/", (ctx, next) => {
  ctx.body = {
    code: 0,
    msg: "Hello Koa",
  };
});

router.get("/error", async (ctx, next) => {
  // 抛出一个错误
  throw new Error("Something went wrong");
});

router.get("/auth", async (ctx, next) => {
  const { code } = ctx.request.query;
  try {
    const result = await axios({
      method: "post",
      url: "https://github.com/login/oauth/access_token",
      headers: {
        accept: "application/json",
      },
      data: {
        client_id: "CLIENT_ID",
        client_secret: "CLIENT_SECRET",
        code,
      },
    });
    ctx.body = result.data;
  } catch (error) {
    throw new Error(error);
  }
});

app.use(router.routes());
app.use(router.allowedMethods());

module.exports = app.callback();

// 本地运行
// app.listen(3000, () => {
//   console.log("服务正在运行:http://localhost:3000");
// });

部署到 vercel

  文件名为:vercel.json

{
  "version": 2,
  "builds": [
    {
      "src": "./index.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "/",
      "headers": {
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET,OPTIONS,PATCH,DELETE,POST,PUT",
        "Access-Control-Allow-Headers": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"
      }
    }
  ]
}

对应源代码链接

在线链接:githubauth.vercel.app/

参考链接:docs.github.com/zh/apps/oau…