Skip to content

Commit

Permalink
Improve deserialization performance around validation and its tests (#…
Browse files Browse the repository at this point in the history
…202)

* Improve deserialization performance around validation and its tests

The claims validation was done via deserializing into a Map, which
implies allocations/deallocations. This was done even if the map was not
used afterwards.

This commit improves performance of the validation by never
deserializing in a `Map`, and deserializing only when necessary, to
a struct that typically only borrows from the original b64-decoded
json string.

The validation function interface change required update to the tests,
which are also made easier to read by using the `serde_json::json!`
macro.

* unrelated: fix bench compilation
  • Loading branch information
Ten0 authored and Keats committed Feb 2, 2022
1 parent 1dcfda9 commit f9771f7
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 142 deletions.
8 changes: 6 additions & 2 deletions benches/jwt.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};

#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
Expand All @@ -23,7 +23,11 @@ fn bench_decode(c: &mut Criterion) {

c.bench_function("bench_decode", |b| {
b.iter(|| {
decode::<Claims>(black_box(token), black_box(&key), black_box(&Validation::default()))
decode::<Claims>(
black_box(token),
black_box(&key),
black_box(&Validation::new(Algorithm::HS256)),
)
})
});
}
Expand Down
9 changes: 5 additions & 4 deletions src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::crypto::verify;
use crate::errors::{new_error, ErrorKind, Result};
use crate::header::Header;
use crate::pem::decoder::PemEncodedKey;
use crate::serialization::{b64_decode, from_jwt_part_claims};
use crate::serialization::{b64_decode, DecodedJwtPartClaims};
use crate::validation::{validate, Validation};

/// The return type of a successful call to [decode](fn.decode.html).
Expand Down Expand Up @@ -198,10 +198,11 @@ pub fn decode<T: DeserializeOwned>(
match verify_signature(token, key, validation) {
Err(e) => Err(e),
Ok((header, claims)) => {
let (decoded_claims, claims_map): (T, _) = from_jwt_part_claims(claims)?;
validate(&claims_map, validation)?;
let decoded_claims = DecodedJwtPartClaims::from_jwt_part_claims(claims)?;
let claims = decoded_claims.deserialize()?;
validate(decoded_claims.deserialize()?, validation)?;

Ok(TokenData { header, claims: decoded_claims })
Ok(TokenData { header, claims })
}
}
}
Expand Down
30 changes: 16 additions & 14 deletions src/serialization.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use serde_json::map::Map;
use serde_json::{from_slice, to_vec, Value};
use serde::{Deserialize, Serialize};

use crate::errors::Result;

Expand All @@ -15,18 +12,23 @@ pub(crate) fn b64_decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>> {

/// Serializes a struct to JSON and encodes it in base64
pub(crate) fn b64_encode_part<T: Serialize>(input: &T) -> Result<String> {
let json = to_vec(input)?;
let json = serde_json::to_vec(input)?;
Ok(b64_encode(json))
}

/// Decodes from base64 and deserializes from JSON to a struct AND a hashmap of Value so we can
/// run validation on it
pub(crate) fn from_jwt_part_claims<B: AsRef<[u8]>, T: DeserializeOwned>(
encoded: B,
) -> Result<(T, Map<String, Value>)> {
let s = b64_decode(encoded)?;
/// This is used to decode from base64 then deserialize from JSON to several structs:
/// - The user-provided struct
/// - The ClaimsForValidation struct from this crate to run validation on
pub(crate) struct DecodedJwtPartClaims {
b64_decoded: Vec<u8>,
}

impl DecodedJwtPartClaims {
pub fn from_jwt_part_claims(encoded_jwt_part_claims: impl AsRef<[u8]>) -> Result<Self> {
Ok(Self { b64_decoded: b64_decode(encoded_jwt_part_claims)? })
}

let claims: T = from_slice(&s)?;
let validation_map: Map<_, _> = from_slice(&s)?;
Ok((claims, validation_map))
pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T> {
Ok(serde_json::from_slice(&self.b64_decoded)?)
}
}
Loading

0 comments on commit f9771f7

Please sign in to comment.