- 支持试读
认识 OAuth 2.0
OAuth 2.0 是授权协议的行业标准,允许应用程序在无需共享密码的情况下获取对用户数据的有限、安全访问权限。 接入 Github OAuth 2.0
开发自己的 OAuth 服务之前,通过集成第三方的 OAuth 服务能更加了解其流程,本章我们将通过接入 Github 的 OAuth 服务来体验 OAuth 的完整流程。申请接入
本章我们开始实现自己的 OAuth 2.0 服务,首先从申请接入开始。应用管理
本章我们实现应用及密钥的管理。- 支持试读
用户授权
本章我们将实现用户授权。 - 支持试读
获取访问令牌及调用API
本章我们将实现访问令牌的获取以及用户信息API。
用户授权
- 11
- 2026-04-19 11:44:39
本章我们将实现用户授权。

然后是用户授权界面(/login/oauth/authorize):
图中“应用名称”应该改成
{appData?.name},请读者自行修改。详细代码请参考OauthAuthorizeConfirm.tsx
作用域
本专题定义了一下作用域:
- User(
user):读写用户信息,包含read:user - ReadUser(
read:user):读取用户信息,包含user:email - UserEmail(
user:email):读取用户邮箱
虽然定义了若干作用域,但实际并未做区分,使用的都是 read:user 作用域。读者可以自行实现这些作用域。
数据库
模型定义
// oauth2/oauth2/src/authorize/model.rs
#[derive(Debug, Serialize, Deserialize, Default, sqlx::Type, Clone)]
#[sqlx(type_name = "authorize_scope")]
#[serde(rename_all = "snake_case")]
pub enum AuthorizeScope {
User,
#[serde(rename = "read:user")]
ReadUser,
#[default]
#[serde(rename = "user:email")]
UserEmail,
}
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
pub struct Authorize {
pub id: String,
pub application_id: String,
pub user_id: String,
pub scope: AuthorizeScope,
pub created_at: chrono::DateTime<chrono::Utc>,
}
impl Authorize {
pub fn new<S: Into<String>>(application_id: S, user_id: S, scope: AuthorizeScope) -> Self {
Self {
id: utils::new_id(),
application_id: application_id.into(),
user_id: user_id.into(),
scope,
created_at: chrono::Utc::now(),
}
}
}
数据操作
// oauth2/oauth2/src/authorize/db.rs
use sqlx::PgExecutor;
use super::model;
pub async fn create_or_update( // 1️⃣
e: impl PgExecutor<'_>,
authorize: model::Authorize,
) -> sqlx::Result<model::Authorize> {
sqlx::query_as(
r#"
INSERT INTO "authorizes" ("id", "application_id", "user_id", "scope", "created_at")
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (application_id, user_id)
DO UPDATE SET scope = $4
RETURNING "id", "application_id", "user_id", "scope", "created_at"
"#,
)
.bind(&authorize.id)
.bind(&authorize.application_id)
.bind(&authorize.user_id)
.bind(&authorize.scope)
.bind(&authorize.created_at)
.fetch_one(e)
.await
}
pub async fn find(
e: impl PgExecutor<'_>,
application_id: &str,
user_id: &str,
) -> sqlx::Result<Option<model::Authorize>> {
sqlx::query_as(
r#"
SELECT "id", "application_id", "user_id", "scope", "created_at"
FROM "authorizes"
WHERE "application_id" = $1 AND "user_id" = $2
"#,
)
.bind(application_id)
.bind(user_id)
.fetch_optional(e)
.await
}
- 1️⃣ 我们实现了当未用户授权时插入新记录,如果已授权则更新记录。
handler
- 1️⃣ 接受用户授权,并返回临时令牌
- 2️⃣ 使用临时令牌交换访问令牌。此处不应使用中间件,我们将在下一章进行修正
- 3️⃣ 获取获取信息
路由
// oauth2/oauth2/src/init/router.rs
// 初始化授权的路由
fn authorize_init(state: ArcAppState) -> Router {
Router::new()
.route("/authorize", post(authorize::handler::authorize))
.route("/access_token", post(authorize::handler::access_token)) // 1️⃣
.route(
"/authorize/{application_id}",
get(authorize::handler::get_authorize),
)
.layer(middleware::from_extractor_with_state::<
mw::UserAuth,
ArcAppState,
>(state.clone()))
.with_state(state)
}
fn api_init(state: ArcAppState) -> Router {
Router::new()
.nest("/auth", auth_init(state.clone()))
.nest("/application", application_init(state.clone()))
.nest("/login/oauth", authorize_init(state)) // 将授权的路由嵌套到 API 路由
}
本章代码位于05.authorize分支。
