用户授权

本章我们将实现用户授权。

创建应用

然后是用户授权界面(/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分支。

要查看完整内容,请先登录