LoginSignup
2
0

RustのCORS設定でハマったので記録。まさか "/"が問題だったとは。

Posted at

RustでAxumを使ってAPIサーバーを作っていたのですが、適切にCORS設定をしているのにも関わらずCORSエラーが発生する事案が発生しました。その報告とエラー解消を記録します。

状況

Axumを使ってAPIサーバーを建てていました。以下のようなイメージです(Axumのサンプルを改変)。

use axum::{
    routing::get,
    Router,
};
use tower_http::cors::CorsLayer;

#[tokio::main]
async fn main() {
    let origins = [
        "http://aaa.com".parse::<HeaderValue>().unwrap(),
        "http://bbb.com/".parse::<HeaderValue>().unwrap()
    ];

    let app = Router::new()
        .route("/", get(|| async { "Hello, World!" }));
        .layer(
            CorsLayer::new()
                .allow_origin(origins)
        );

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

ポイントとしては以下の点になります。

  1. 複数のオリジンに対しアクセス許可をするため、originsHeaderValueのListとしている
  2. 2つ目のoriginがhttp://bbb.com/のように最後に/が挿入されている

上記のようなシチュエーションで、http://aaa.comからはリソースにアクセスできるのに、http://bbb.comからはCORSエラーが発生し、リソースにアクセスできない、という問題が発生していました。

原因と解決策

解決策ですが、オリジン設定の際にhttp://bbb.com/ではなく、http://bbb.comのように /を削除して設定を行うことが必要です。

つまり、以下のようにorigin変数を束縛します。

// Error
let origins = [
    "http://aaa.com".parse::<HeaderValue>().unwrap(),
    "http://bbb.com/".parse::<HeaderValue>().unwrap()
];

// OK
let origins = [
    "http://aaa.com".parse::<HeaderValue>().unwrap(),
    "http://bbb.com".parse::<HeaderValue>().unwrap()
];

自分的には、http://bbb.comでもhttp://bbb.com/でもオリジンは同じなので問題ないのでは?と考えており、上記解決策になかなか気付けずハマってしまいました。

原因としてはtower-httpライブラリの実装に起因します。

CORSの設定・処理を行うCorsLayertower-httpライブラリからインポートして使用しているのですが、その中でAllow-Contrl-Allow-Originの設定を行うAllowOriginの実装を見ると、以下のようにヘッダーを作っています。

// ~ 略
pub(super) fn to_header(
    &self,
    origin: Option<&HeaderValue>,
    parts: &RequestParts,
) -> Option<(HeaderName, HeaderValue)> {
    let allow_origin = match &self.0 {
        OriginInner::Const(v) => v.clone(),
        OriginInner::List(l) => origin.filter(|o| l.contains(o))?.clone(),
        OriginInner::Predicate(c) => origin.filter(|origin| c(origin, parts))?.clone(),
    };

    Some((header::ACCESS_CONTROL_ALLOW_ORIGIN, allow_origin))
}
// ~ 略

紛らわしいかもしれませんが、to_headerメソッドの第一引数&selfはアクセスを許可するオリジン、第二引数originはリクエストが送られてきたオリジンになります(to_headerメソッドを使っているこちらも参照)。

複数のオリジンを指定した場合、origin.filter(|o| l.contains(o))?.clone()の部分を見れば分かる通り、 リクエストのオリジンと指定したアクセス許可するオリジンのリストを比較し、そのリストの中にリクエストのオリジンが含まれていれば、そのオリジンをAccess-Control-Allow-Originヘッダに設定しています

そのため、エラーが発生した原因としては、

  • リクエストのオリジンはhttp://bbb.comである
  • アクセスを許可はhttp://bbb.com/と設定してる
  • http://bbb.comhttp://bbb.com/と一致していないと判断され、Allow-Control-Allow-Origin設定が反映されていない(ように見える)

となります。

また、上記のコードの通り、単一のオリジンのみにアクセスを許可する場合はリクエストのオリジンとの比較を行っていないため、http://bbb.com/でも問題なさそうです。

スラッシュを削除するだけなのでエラー解消自体は簡単ですが、中々原因に気付けずハマってしまいました、、、

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0