创建授权应用
这里的 Authorization callback URL
就是后边接口的 redirect_uri
参数
授权后返回的地址建议使用 history
, hash
路由重定向后会有问题比如 http://localhost:5173/?code=079f6270a764f73036ed#/login
获取 client_id、client_secret
找到对应的应用点击进入详情
点击 Generate a new client secret
创建 client_secret
将获取到的 client_id、client_secret
保存一下后边要用
授权流程
整体流程大概如下图, 前端调用一个接口、后端调用两个
我们来看下 github 官网文档猛击访问
文档有点不太好看懂,让我们来看下 web 页面如何完成授权登录
- 使用
https://github.com/login/oauth/authorize
填入相关参数重定向到 github 授权页面 - 授权成功会携带
code
参数重定向到上边设置的redirect_uri
地址 - 前端获取到路由中
code
参数向后端发起登录请求 - 后端接收到登录请求取出 code 向
https://github.com/login/oauth/access_token
发起 post 请求获取access_token
信息 - 使用获取到的
access_token
发起https://api.github.com/user
请求获取用户信息 - 后端返回登录成功信息
kkdl 体验地址猛击访问 因为后端服务部署在阿里云所以能不能访问 github 接口全凭天意!
前端实现
html
<script lang="ts" setup>
import { Icon } from '@iconify/vue'
import { useRoute, useRouter } from 'vue-router'
import { ElLoading } from 'element-plus'
import { ref } from 'vue'
import { useUserStore } from '@/stores'
import { githubLogin } from '@/api/login'
const loadingDom = document.getElementById('login-container') as HTMLElement
const loadingInstance = ref()
function setLoginLoading(message: string) {
loadingInstance.value = ElLoading.service({
target: loadingDom,
text: message,
background: 'rgba(0, 0, 0, 0.5)',
})
}
const { VITE_APP_GITHUB_CLIENT_ID } = import.meta.env
function login() {
/**
* 接口参数查看
* https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps
*/
const client_id = VITE_APP_GITHUB_CLIENT_ID
const redirect_uri = `${window.location.origin}/login`
const path = `https://github.com/login/oauth/authorize?client_id=${client_id}&redirect_uri=${redirect_uri}`
window.open(path, '_self')
setLoginLoading('正在获取 github 授权...')
}
const { setLoginResData } = useUserStore()
const router = useRouter()
const route = useRoute()
const code = route.query.code as string
function loginGithub() {
setLoginLoading('正在登录...')
githubLogin({ code })
.then((res) => {
setLoginResData(res.data)
ElMessage.success('登录成功!')
})
.catch(() => {
ElMessage.warning('登录失败!')
router.replace('/login')
})
.finally(() => {
loadingInstance.value?.close()
})
}
// code 存在执行 github 登录逻辑
if (code)
loginGithub()
</script>
<template>
<el-button type="primary" class="w-[340px]" color="black" @click="login">
<Icon icon="uil:github" class="cursor-pointer mr-4" width="24px" height="42px" />
使用 GitHub 登录
</el-button>
</template>
<style lang="scss" scoped>
</style>
client_id、redirect_uri
参数上边有说明
授权页面长这样
拒绝授权会携带错误信息重定向到上边设置的 redirect_uri
页面
确认授权会携带 code
参数重定向到上边设置的 redirect_uri
页面
https://kkdl.netlify.app/#/github?code=xxxxxxxx
授权范围
scope 参数授权范围不传表示:授予对公共信息的只读访问权限(包括用户个人资料信息、存储��信息和要点) 参数文档猛击访问
后端实现
后端使用 go、GoFrame 实现其他语言也几乎一致 OAuth2.0 是一个通用协议
使用 OAuth2.0 可以简化 github 接口的调用
安装所需依赖
bash
go get golang.org/x/oauth2
go get golang.org/x/oauth2/github
接口实现
go
package login
import (
"context"
"encoding/json"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
"net/http"
"time"
)
// github 返回的用户信息
type GithubUserInfo struct {
AvatarURL string `json:"avatar_url"`
Bio string `json:"bio"`
Company string `json:"company"`
CreatedAt time.Time `json:"created_at"`
Email *string `json:"email"`
EventsURL string `json:"events_url"`
Followers int `json:"followers"`
FollowersURL string `json:"followers_url"`
Following int `json:"following"`
FollowingURL string `json:"following_url"`
GistsURL string `json:"gists_url"`
GravatarID string `json:"gravatar_id"`
Hireable *bool `json:"hireable"`
HTMLURL string `json:"html_url"`
ID int `json:"id"`
Location *string `json:"location"`
Login string `json:"login"`
Name string `json:"name"`
NodeID string `json:"node_id"`
OrganizationsURL string `json:"organizations_url"`
PublicGists int `json:"public_gists"`
PublicRepos int `json:"public_repos"`
ReceivedEventsURL string `json:"received_events_url"`
ReposURL string `json:"repos_url"`
SiteAdmin bool `json:"site_admin"`
StarredURL string `json:"starred_url"`
SubscriptionsURL string `json:"subscriptions_url"`
TwitterUsername *string `json:"twitter_username"`
Type string `json:"type"`
UpdatedAt time.Time `json:"updated_at"`
URL string `json:"url"`
}
func getGithubUserInfo(ctx context.Context, code string) (*GithubUserInfo, error) {
// 读取配置
cfg := g.Cfg()
githubOAuthConfig := &oauth2.Config{
ClientID: cfg.MustGet(ctx, "githubConfig.client_id").String(),
ClientSecret: cfg.MustGet(ctx, "githubConfig.client_secret").String(),
RedirectURL: cfg.MustGet(ctx, "githubConfig.redirect_uri").String(),
Scopes: []string{"user"},
Endpoint: github.Endpoint, // 认证的端点这里是 github
}
token, err := githubOAuthConfig.Exchange(ctx, code)
if err != nil {
return nil, gerror.New("获取 github token失败!")
}
client := githubOAuthConfig.Client(ctx, token)
// 获取 github 用户信息
resp, err := client.Get("https://api.github.com/user")
if err != nil {
return nil, gerror.New("获取 github 用户信息失败!")
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, gerror.New("无法获取 github 用户信息!")
}
var userInfo GithubUserInfo
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
g.Log().Error(ctx, "Failed to parse user info:", err)
return nil, gerror.New("格式化 github 用户信息失败!")
}
return &userInfo, nil
}
获取到 github 用户信息后做相应的业务处理
比如判断用户是否已经创建
- 未创建则创建用户然后将用户信息返回前端登录成功
- 已创建则将查询到的用户信息返回前端登录成功
总结
本文实践了 web 网站如何集成 github 登录,前后端代码均已开源