2024/08/31
目次
- 概要と使用例
- プロジェクト設定
- base64関連
- 署名アルゴリズム関連
- ヘッダ定義
- JWT本体の実装
- JWT本体のTest
はじめに
本Partでは、base64関連の実装を行う。
JWTでは、base64を多用するので、base64エンコード・デコードを気軽に利用できるように、クレート内でラップして利用する。
コード全文
src/jwt/base64.rs
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
use crate::error::Result;
pub(crate) struct Base64;
impl Base64 {
pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
BASE64_URL_SAFE_NO_PAD.encode(input)
}
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>> {
Ok(BASE64_URL_SAFE_NO_PAD.decode(input)?)
}
/// Serialize a value to a base64 string
pub fn serialize<T: serde::ser::Serialize>(input: &T) -> Result<String> {
let serialized = serde_json::to_string(input)?;
Ok(Base64::encode(serialized))
}
/// Deserialize a value from a base64 string
pub fn deserialize<T: serde::de::DeserializeOwned>(input: &str) -> Result<T> {
let decoded = Base64::decode(input)?;
Ok(serde_json::from_slice(decoded.as_slice())?)
}
}
#[cfg(test)]
mod tests {
use crate::{jwt::header::Header, Alg};
use super::*;
#[test]
fn decode_encode() -> Result<()> {
let cases = vec![
(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
r#"{"alg":"HS256","typ":"JWT"}"#,
),
(
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ",
r#"{"sub":"1234567890","name":"John Doe","iat":1516239022}"#,
),
];
for (input, expected) in cases {
let decoded = String::from_utf8(Base64::decode(input)?)?;
println!("decoded: {:?}", decoded);
assert_eq!(decoded, expected);
let reencoded = Base64::encode(decoded);
assert_eq!(input, reencoded);
}
Ok(())
}
#[test]
fn deserialize_serialize() -> Result<()> {
let input = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; // {"alg":"HS256","typ":"JWT"}
let v: Header = Base64::deserialize(input)?;
matches!(v.alg, Alg::HS256);
let reencoded = Base64::serialize(&v)?;
assert_eq!(input, reencoded);
Ok(())
}
}
解説
定数・型の定義
base64
クレートを使う。
実装しているクレート内での利用は、Base64
構造体と名前をつけ、ラップする。
あえてラップしているのは、
- paddingなどの設定を変えたくなったときの変更を一元化する
- クレート内での利便性のため、
crate::error::Result
を返すようにする serde
との連携の簡素な記述のためのserialize
/deserialize
メソッドを追加する
という目的がある。
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
use crate::error::Result;
pub(crate) struct Base64;
encode/decode
まずはエンコード・デコードを書いておく。ジェネリクスは、多様な型を受け入れられるようにしておく。
前述の通り、Result
を返すようにしておく。
impl Base64 {
pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
BASE64_URL_SAFE_NO_PAD.encode(input)
}
pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>> {
Ok(BASE64_URL_SAFE_NO_PAD.decode(input)?)
}
serialize/deserialize
Header, Payloadとbase64の連携を簡素に記述するために、
serialize()
:serde
でシリアライズしたものをbase64エンコードするdeserialize()
: base64デコードしたものをserde
でデシリアライズする
という関連関数を実装しておく。
Base64::serialize()
のように使うので、間違えることはないと思うが、念の為doccommentを書き、base64 encodeが行われることを明示しておく。(deserialize()
も同様)
/// Serialize a value to a base64 string
pub fn serialize<T: serde::ser::Serialize>(input: &T) -> Result<String> {
let serialized = serde_json::to_string(input)?;
Ok(Base64::encode(serialized))
}
/// Deserialize a value from a base64 string
pub fn deserialize<T: serde::de::DeserializeOwned>(input: &str) -> Result<T> {
let decoded = Base64::decode(input)?;
Ok(serde_json::from_slice(decoded.as_slice())?)
}
}
test
Base64
の利用方法の例示を兼ね、テストを書いておく。
encode/decode
とserialize/deserialize
の組に分けてテストを書いている。
これは、変換・逆変換の対でテストすることで、(値を元に戻せるという観点での)実装の正しさを確認する目的を兼ねている。
Header
という構造体は現時点では未定義だが、ここには serde
のSerialize
/Deserialize
を実装しておくことを想定している。
#[cfg(test)]
mod tests {
use crate::{jwt::header::Header, Alg};
use super::*;
#[test]
fn decode_encode() -> Result<()> {
let cases = vec![
(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
r#"{"alg":"HS256","typ":"JWT"}"#,
),
(
"eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ",
r#"{"sub":"1234567890","name":"John Doe","iat":1516239022}"#,
),
];
for (input, expected) in cases {
let decoded = String::from_utf8(Base64::decode(input)?)?;
println!("decoded: {:?}", decoded);
assert_eq!(decoded, expected);
let reencoded = Base64::encode(decoded);
assert_eq!(input, reencoded);
}
Ok(())
}
#[test]
fn deserialize_serialize() -> Result<()> {
let input = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"; // {"alg":"HS256","typ":"JWT"}
let v: Header = Base64::deserialize(input)?;
matches!(v.alg, Alg::HS256);
let reencoded = Base64::serialize(&v)?;
assert_eq!(input, reencoded);
Ok(())
}
}