actix-webでjwtの発行と検証を行ったので記録しておきます。
Cargo.toml
こちら GitHub - Keats/jsonwebtoken: JWT lib in rust を使用しました。
jsonwebtoken = "7"
jwtの生成
例として8時間まで有効のjwtを生成。尚、expireを指定しないとjwt decode時にtimeout tokenとみなされるので、 GitHub - Keats/jsonwebtoken: JWT lib in rust こちらにある通り、expフィールドは必須になります。
use jsonwebtoken::{encode, Header, Algorithm, EncodingKey}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Claims { account_id: String, iat: i64, exp: i64, } pub fn make_jwt( secret: &String, account_id: &String ) -> String { let mut header = Header::default(); header.typ = Some("JWT".to_string()); header.alg = Algorithm::HS256; let now = Utc::now(); let iat = now.timestamp(); let exp = (now + Duration::hours(8)).timestamp(); let my_claims = Claims { account_id: account_id.clone(), iat: iat, exp: exp, }; encode(&header, &my_claims, &EncodingKey::from_secret( secret.as_ref())).unwrap() }
ここで生成したjwtをクライアントへレスポンスしクライアント側で保存を行います。
jwtの送信例
例としてaxiosでAuthorization
ヘッダーにjwtを送信する例を示します。Bearer schema typeで送信するのが一般的のようです。
axios.get("http://localhost:8080/auth", { headers: { Authorization: `Bearer ${jwt_token}` } });
jwtの検証
サーバ側でのjwtの検証
actix-webでのHTTPリクエストヘッダー参照
HttpRequestでbindすることができます。req.headers()
でHTTPリクエストヘッダーが格納されたHashMapを取得できます。
#[get("/auth")] async fn auth(pool: web::Data<DbPool>, req: HttpRequest) -> Result<HttpResponse> { let authorization = req.headers().get("authorization"); //authorizationヘッダーの解析 let result = decode_authorization_header(authorization); let decode = match result { Err(error) => // error時の処理 , // jwtのデコード処理 Ok(jwt) => decode_jwt( jwt, your_secret ), } // match decode ...認証成功、失敗の処理 }
authorizationヘッダーの解析
上記で取得したAuthorization
ヘッダーがサーバが意図したヘッダーであるか解析を行います。
- Authorizationが存在しない:NG
- schema Bearer が指定されていない:NG
- Bearerに続くjwtが送信されていない:NG
みたいな感じです。
//Authorizationヘッダーのdecode fn decode_authorization_header(authorization:Option<&HeaderValue>) -> Result<&str, &str> { let token = match authorization { None => return Err("notfound authorization header"), Some(token) => token.to_str().unwrap(), }; let mut split_token = token.split_whitespace(); match split_token.next() { None => return Err("not found schema type"), Some(schema_type) => { if schema_type != "Bearer" { return Err("invalid schema type"); } } }; let jwt = match split_token.next() { None => return Err("not found jwt token"), Some(jwt) => jwt }; Ok(jwt) }
jwtのデコード
bindするClaims
structはjwt生成時にした使用したClaimsと同じstructを指します。secretは勿論、生成時と同じものを指定します。
use jsonwebtoken::{TokenData, DecodingKey, Validation, decode}; fn decode_jwt(jwt: &str, secret: &str) -> Result<TokenData<Claims>, jsonwebtoken::errors::Error> { let secret = std::env::var(secret).expect("secret is not set"); decode::<Claims>( jwt, &DecodingKey::from_secret(secret.as_ref()), &Validation::default()) }
上記でデコードが成功するとこのようなTokenDataを参照することができます。
Ok(TokenData { header: Header { typ: Some("JWT"), alg: HS256, cty: None, jku: None, kid: None, x5u: None, x5t: None }, claims: Claims { account_id: "****-****-****", iat: 1607908963, exp: 1607937763 } })