iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
Software Development

Rust Web API 從零開始系列 第 25

Day25 - 權限驗證(3) - 自己寫個middleware吧

  • 分享至 

  • xImage
  •  

透過昨天建立的Login API,使用者登入後,我們需要在特定的API驗證cookie中JWT token的有效性,這一點需要靠中介軟體來達到。通常情況下我們不用真的從頭實做一個中介層,歸功於與tower生態系的整合,可以找找看有沒有符合需求的元件來使用,我們需要開啟tower-httpvalidate-request

cargo add tower-http --features validate-request

驗證器

根據文件中的說明,我們可以使用ValidateRequestHeaderLayer,並帶入自定義的驗證,帶入的驗證需要實做ValidateRequest trait:

pub trait ValidateRequest<B> {
    type ResponseBody;

    // Required method
    fn validate(
        &mut self,
        request: &mut Request<B>
    ) -> Result<(), Response<Self::ResponseBody>>;
}

這邊在嘗試的時候遇到一點小問題,ResponseBody的關聯型別需要使用axum::body::BoxBody。接下來我們先建立驗證器的struct:

#[derive(Clone)]
pub struct Authorization {
    pub jwt_handler: JwtHandler,
}

因為驗證的時候會需要用到前面建立的JwtHandler,所以驗證器需要包含它。

impl<B> ValidateRequest<B> for Authorization {
    type ResponseBody = BoxBody;

    fn validate(&mut self, request: &mut Request<B>) -> Result<(), Response<Self::ResponseBody>> {
        request
            .headers()
            .get(header::COOKIE)
            .and_then(|cookie| cookie.to_str().ok())
            .and_then(|cookie| cookie.split(';').find(|cookie| cookie.contains("token")))
            .and_then(|cookie| cookie.split('=').nth(1))
            //// 驗證token
            .map(|token| self.jwt_handler.clone().decode_token(token.to_string()))
            .map(|result| match result {
                Ok(_) => Ok(()),
                Err(e) => {
                    if e.kind().eq(&ErrorKind::ExpiredSignature) {
                        Err((
                            StatusCode::UNAUTHORIZED,
                            "Token expired, please login again",
                        )
                            .into_response())
                    } else {
                        Err(StatusCode::UNAUTHORIZED.into_response())
                    }
                }
            })
            .unwrap_or_else(|| {
                warn!("Missing authorization header");
                Err(StatusCode::UNAUTHORIZED.into_response())
            })
    }
}

每次的HTTP請求會以request傳遞進入方法中,這個參數是可變的借用,但在驗證過程中我們不會去修改它。接下來我們要從request中取出header,並連續使用幾個map來取得token。使用jwt_handler驗證token後再針對不同的情境顯示處裡,這裡可以看到jwtbeartoken回傳的錯誤型別其實是一個enum,可以表示不同驗證失敗的原因。

路由設定

最後就是把middleware加到路由裡面了:

//// application.rs
pub fn build() -> Router {
    let database = get_database(&config.database).await.unwrap();
    let email_client = get_email_client(&config.email_client);
    let jwt_handler = get_jwt_handler(&config.jwt_handler);
        
    let app = Router::new()
        .route("/", get(health_check))
        //// 加入權限驗證的中介層
        .layer(ValidateRequestHeaderLayer::custom(auth))
        .route("/subscriptions", post(subscribe))
        .route("/subscriptions/confirm/:token", get(confirm))
        .route("/login", post(login))
        .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi()))
        .with_state(AppState {
            database,
            email_client,
            jwt_handler,
        });;
    app
}

前面有提到middleware的運作模式會由下而上,所以需要權限驗證的API要放在ValidateRequestHeaderLayer之上,這邊可以簡單的用health_check做登入的測試。

小結

專案到這邊就告一段落了,原本預計要再做一隻API用來發布新文章通知,因為這是給部落格作者使用的強大API,需要權限控管,也才有了這三篇關於權限驗證的內容,不過發布通知的內容其實跟前面訂閱API需要知道的東西大同小異,舊不多做介紹了。寫到這邊我覺得我的介紹近乎流水帳的把自己side project的內容貼過來,明天我想要聊聊對於rust進行Web API開發的想法,剩下的四篇文章在想想要寫些什麼額外的東西吧!


上一篇
Day24 - 權限驗證(2) - Login API
下一篇
Day26 - 關於Rust開發Web API這件事
系列文
Rust Web API 從零開始30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言