- 支持试读
认识 OAuth 2.0
OAuth 2.0 是授权协议的行业标准,允许应用程序在无需共享密码的情况下获取对用户数据的有限、安全访问权限。 - 支持试读
接入 Github OAuth 2.0
开发自己的 OAuth 服务之前,通过集成第三方的 OAuth 服务能更加了解其流程,本章我们将通过接入 Github 的 OAuth 服务来体验 OAuth 的完整流程。
接入 Github OAuth 2.0
- 20
- 2026-02-27 08:55:44
开发自己的 OAuth 服务之前,通过集成第三方的 OAuth 服务能更加了解其流程,本章我们将通过接入 Github 的 OAuth 服务来体验 OAuth 的完整流程。
流程总览
接入 Github OAuth 2.0 的流程可以概括为:
- 申请接入
- 接受用户授权
- 业务处理
申请接入
首先,通过申请页面,提交表单:

- Application name:应用名称,比如
AXUM中文网 - Homepage URL:主页地址,比如
https://axum.eu.org - Application description:应用描述,比如
AXUM中文网为你提供了企业级axum Web开发中所需要的大部分知识。从基础知识到企业级项目的开发,都有完整的系列教程。更难得的是,除了文字教程,我们还录制了配套的视频教程,方便你以多种形式进行学习。 - Authorization callback URL:用户授权后的回调页面,比如
https://axum.eu.org/oauth/github
Github OAuth 允许填写本地 URL,这在开发时非常有用。比如,开发时可以将:
- Homepage URL 设置为
http://127.0.0.1- Authorization callback URL 设置为
http://127.0.0.1/oauth/github
注册成功后,将会进入管理页面,如下图:

首次进入该页面,你需要点击 【Generate a new client secret】按钮,生成一个新的密钥。该页面有2个数据至关重要:
- Client ID:你的应用的客户ID
- Client secret:你的应用的密钥。该密钥只在生成时显示一次,所以注意保存。
async fn index() -> Html<String> {
let client_id = std::env::var("GITHUB_CLIENT_ID").unwrap();
let html_content = format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OAuth 2.0</title>
</head>
<body>
<div style="margin: 5rem">
<a href="https://github.com/login/oauth/authorize?scope=user:email&client_id={client_id}" style="background: blueviolet; border-radius: 0.5rem; color: #fff; padding: 0.5rem;text-decoration: none;">使用 Github 登录</a>
</div>
</body>
</html>
"#,
);
Html(html_content.to_string())
}
- 只要将链接地址指向
https://github.com/login/oauth/authorize?scope=【作用域】&client_id=【客户ID】即可
效果如图:

当用户点击绿色的【Authorize】按钮后,完成用户授权,Github 会跳转到申请接入时填写的【Authorization callback URL】页面。
处理授权
下面我们来编写【Authorization callback URL】页面(一下简称“回调页面”),以便处理授权。
Github 会将临时的授权码通过 URL 的 code 参数返回给回调页面,所以,我们要在 AXUM 中获取这个参数:
async fn callback(Query(q): Query<HashMap<String, String>>) {
let code = q.get("code").unwrap();
println!("callback: {code}");
}
这只是一个临时码,我们要通过这个临时码向 Github 服务器换取正式的访问令牌(Access Token):
向 https://github.com/login/oauth/access_token 发起 POST 请求,参数为:
| 参数 | 说明 |
|---|---|
client_id | 客户ID |
client_secret | 密钥 |
code | 上一步获取的临时码 |
如果要让 Github 返回 JSON 格式的数据,需要在请求头插入 Accept: application/json。请求成功后,Github 会返回如下字段:
| 字段 | 说明 |
|---|---|
access_token | 访问令牌 |
scope | 作用域 |
token_type | 令牌类型 |
针对此,我们可以定义一个结构体:
#[derive(Debug, Serialize, Deserialize)]
pub struct AccessTokenRespose {
pub access_token: String,
pub scope: String,
pub token_type: String,
}
示例数据:
AccessTokenRespose { access_token: "gho_BHsPm9*****1CrG0Opv1u", scope: "user:email", token_type: "bearer" }
我们来看一下该函数的完整实现:
async fn callback(Query(q): Query<HashMap<String, String>>) {
let code = q.get("code").unwrap();
let client_id = std::env::var("GITHUB_CLIENT_ID").unwrap();
let secert_key = std::env::var("GITHUB_SECERT_KEY").unwrap();
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
let cli = reqwest::ClientBuilder::new()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap();
let mut data = HashMap::new();
data.insert("client_id", client_id);
data.insert("client_secret", secert_key);
data.insert("code", code.into());
let resp: AccessTokenRespose = cli
.post("https://github.com/login/oauth/access_token")
.json(&data)
.send()
.await
.unwrap()
.json()
.await
.unwrap();
println!("{resp:?}");
}
获取更多信息和作用域
拿到了访问令牌(Access Token)后,我们可以向 Github 获取更多信息,比如获取用户邮箱、头像等。
不同的信息由作用域决定,官方文档列举了可用的作用域,而这里列出了非常丰富的 API。
下面以获取已授权用户信息为例进行演示:
async fn user_info(Path((access_token, token_type)): Path<(String, String)>) -> String {
let auth = format!("{token_type} {access_token}");
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&auth).unwrap(), // 1️⃣
);
headers.insert(
reqwest::header::USER_AGENT,
reqwest::header::HeaderValue::from_static("AXUM.EU.ORG"), // 2️⃣
);
let cli = reqwest::ClientBuilder::new()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap();
let resp = cli
.get("https://api.github.com/user") // 3️⃣
.send()
.await
.unwrap()
.text()
.await
.unwrap();
resp
}
注意:本示例可能需要将 scope 改为
user或read:user。
至此,我们已经成功接入 Github OAuth 了,回顾一下完整代码:
use std::collections::HashMap;
use axum::{
Router,
extract::{Path, Query},
response::Html,
routing::get,
serve,
};
use serde::{Deserialize, Serialize};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
dotenv::dotenv().ok();
let listener = TcpListener::bind("127.0.0.1:9527").await?;
let app = Router::new()
.route("/", get(index))
.route("/callback", get(callback))
.route("/user-info/{access_token}/{token_type}", get(user_info));
serve(listener, app).await?;
Ok(())
}
async fn index() -> Html<String> {
let client_id = std::env::var("GITHUB_CLIENT_ID").unwrap();
let html_content = format!(
r#"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OAuth 2.0</title>
</head>
<body>
<div style="margin: 5rem">
<a href="https://github.com/login/oauth/authorize?scope=read:user&client_id={client_id}" style="background: blueviolet; border-radius: 0.5rem; color: #fff; padding: 0.5rem;text-decoration: none;">使用 Github 登录</a>
</div>
</body>
</html>
"#,
);
Html(html_content.to_string())
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AccessTokenRespose {
pub access_token: String,
pub scope: String,
pub token_type: String,
}
async fn callback(Query(q): Query<HashMap<String, String>>) -> Html<String> {
let code = q.get("code").unwrap();
let client_id = std::env::var("GITHUB_CLIENT_ID").unwrap();
let secert_key = std::env::var("GITHUB_SECERT_KEY").unwrap();
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
let cli = reqwest::ClientBuilder::new()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap();
let mut data = HashMap::new();
data.insert("client_id", client_id);
data.insert("client_secret", secert_key);
data.insert("code", code.into());
let resp: AccessTokenRespose = cli
.post("https://github.com/login/oauth/access_token")
.json(&data)
.send()
.await
.unwrap()
.json()
.await
.unwrap();
println!("{resp:?}");
Html(format!(
r#"<a href="/user-info/{}/{}">用户信息</a>"#,
&resp.access_token, &resp.token_type
))
}
async fn user_info(Path((access_token, token_type)): Path<(String, String)>) -> String {
let auth = format!("{token_type} {access_token}");
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json"),
);
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&auth).unwrap(),
);
headers.insert(
reqwest::header::USER_AGENT,
reqwest::header::HeaderValue::from_static("AXUM.EU.ORG"),
);
let cli = reqwest::ClientBuilder::new()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(10))
.build()
.unwrap();
let resp = cli
.get("https://api.github.com/user")
.send()
.await
.unwrap()
.text()
.await
.unwrap();
resp
}
本章目的是让读者了解 Github OAuth 接入流程,实际开发中,读者可以使用现成的 Crate 来加速开发。
本章代码位于01.github-oauth分支。
