Open4

JWT

katayama8000katayama8000

JWT

  • 語源
    • jsonwebtoken

構成

[ヘッダ].
[ペイロード].
[署名]

実際のjwt

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MDg4NDQyNzQsImV4cCI6MTcwOTQ0OTA3NCwic3ViIjoiYXV0aCIsInVzZXJfbmFtZSI6ImV4YW1wbGVfdXNlciJ9.NqM23O2483r-q6eJAMazwKngfWHinMY5_jod-BFQveI

作り方

ヘッダ

{
  "typ": "JWT",
  "alg": "HS256"
}

Base64エンコード

echo -n '{"alg":"HS256","typ":"JWT"}' | base64

結果

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

ペイロード

{
  "iat": 1708844274,
  "exp": 1709449074,
  "sub": "auth",
  "user_name": "example_user"
}

Base64エンコード
結果

eyJpYXQiOjE3MDg4NDQyNzQsImV4cCI6MTcwOTQ0OTA3NCwic3ViIjoiYXV0aCIsInVzZXJfbmFtZSI6ImV4YW1wbGVfdXNlciJ9

署名

ヘッダ``ペイロードを繋げて、署名なしトークンを作成
署名なしTokenに対し、秘密鍵とHMAC-SHA256を用いて署名を生成

echo -n 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MDg4NDQyNzQsImV4cCI6MTcwOTQ0OTA3NCwic3ViIjoiYXV0aCIsInVzZXJfbmFtZSI6ImV4YW1wbGVfdXNlciJ9' | \
openssl dgst -binary -sha256 -hmac 'app-secret' | \
base64

生成された、トークンを全て [.] で繋げる

katayama8000katayama8000

エンコード

前提として、name passwordをキーとする

let claims = ApiClaims::generate_claims(&User {
         name: user.name.clone(),
         password: user.password.clone(),
    });
let token = ApiJwt::encode(claims); 

ペイロードの部分を構築

#[derive(Debug, Serialize, Deserialize)]
pub struct ApiClaims {
    iat: i64,          // issued at
    exp: i64,          // expiration
    sub: String,       // subject
    user_name: String, // user_name
}

impl ClaimsGenerator<User> for ApiClaims {
    fn generate_claims(user: &User) -> Self {
        let now = chrono::Utc::now().timestamp();
        let exp = now + 60 * 60 * 24 * 7; // 7 days
        ApiClaims {
            iat: now,
            exp,
            sub: "auth".to_string(),
            user_name: user.name.clone(),
        }
    }
}

ヘッダーと署名の部分を含めてエンコードする

pub struct ApiJwt;

impl ApiJwt {
    pub fn encode<T: Serialize>(claims: T) -> Result<String, JwtError> {
        let header = Header {
            typ: Some("JWT".into()),
            alg: Algorithm::HS256,
            ..Default::default()
        };

        encode(
            &header,
            &claims,
            &EncodingKey::from_secret(JWT_SECRET_KEY.as_ref()),
        )
    }
}
katayama8000katayama8000

デコード

    // decode token
    let token = match ApiJwt::parse_header(&headers) {
        Ok(token) => token,
        Err(err) => {
            eprintln!("Failed to parse token: {:?}", err);
            return (StatusCode::UNAUTHORIZED, "Invalid token").into_response();
        }
    };

    let token_data = match ApiJwt.decode(&token) {
        Ok(token_data) => token_data,
        Err(err) => {
            eprintln!("Failed to decode token: {:?}", err);
            return (StatusCode::UNAUTHORIZED, "Invalid token").into_response();
        }
    };

ヘッダーから取り出すtokenを取り出す

    fn parse_header(header: &HeaderMap) -> Result<String, CustomErr> {
        match header.get(AUTHORIZATION) {
            Some(token) => {
                let mut split_token = token
                    .to_str()
                    .map_err(|_| CustomErr::InvalidHeader)?
                    .split_whitespace();
                match split_token.next() {
                    Some(schema_type) if schema_type == "Bearer" => match split_token.next() {
                        Some(jwt_token) => Ok(jwt_token.to_string()),
                        None => Err(CustomErr::NoJwtTokenFound),
                    },
                    Some(_) | None => Err(CustomErr::InvalidSchemaType),
                }
            }
            None => Err(CustomErr::NoAuthorizationHeader),
        }
    }
    fn decode(&self, token: &str) -> Result<TokenData<ApiClaims>, CustomErr> {
        decode::<ApiClaims>(
            token,
            &DecodingKey::from_secret(JWT_SECRET_KEY.as_ref()),
            &Validation::default(),
        )
        .map_err(CustomErr::JwtError)
    }

キーを使って、デコード

katayama8000katayama8000

JWTにおけるログインの流れ

ログイン

  1. name=hoge, password=hogeとし、 /loginにPOSTリクエストする。
  2. APサーバは、DBに対し、ユーザの確認をリクエストする。
  3. DBは、APサーバに対し、対応するユーザIDを返す。
  4. APサーバは、ユーザIDと秘密鍵より、Tokenを生成する。
  5. APサーバは、ユーザにTokenを返す。

ユーザー情報取得

  1. AuthorizationにJWTを設定し、 /userにGETリクエストする。
  2. APサーバは、秘密鍵を用いてTokenを検証する。
  3. APサーバは、TokenからユーザIDを取得する。
  4. APサーバは、DBに対し、IDに対するユーザ情報をリクエストする。
  5. DBは、IDに対するユーザ情報を返す。
  6. APサーバは、ユーザにユーザ情報を返す。

この流れでは、ログイン時にjwtを取得して、Cookieなどに保存しておき、ユーザー取得時には、jwtを検証して、idを取り出し、そのidをキーにして、ユーザーを取得する