到目前為止基礎的功能已經完成了,接下來幾天就要來做一些附加的內容,比如說使用後台API來做管理,所以就來做個登入用的API吧。
關於權限驗證的方案,我打算使用JWT做權限驗證,首先加入套件jsonwebtoken
:
cargo add jsonwebtoken
這是rust中比較熱門用來解析jwt的套件,接下來我們需要定義好jwt的格式,所以就先建立一個產生token用的Claims
結構:
//// jwt_handler.rs
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
aud: String, // Optional. Audience (who or what the token is intended for)
exp: usize, // Required (validate_exp defaults to true in validation). Expiration time (as UTC timestamp)
iat: usize, // Optional. Issued at (as UTC timestamp) (default = now)
iss: String, // Optional. Issuer (who issued the token)
nbf: usize, // Optional. Not Before (as UTC timestamp)
sub: String, // Optional. Subject (whom token refers to)
}
Claims
中的欄位不是必須的,但如果有設定的話jsonwebtoken
默認會支援,套件本身也可以容許我們自行定義欄位。接下來要建立用於產生token的JwtHandler
:
//// jwt_handler.rs
#[derive(Clone, Debug)]
pub struct JwtHandler {
pub private_key: Secret<String>,
pub header: Header,
pub public_key: String,
pub expiration_minutes: i64,
}
如果對於JWT熟悉的話就知道,我們需要自行保管用來簽名的私鑰,這部份就跟前面的資料庫連線一樣我們要從環境變數中取得並用Secret保護,另外我們也可以在設定檔中決定token的過期時間。接下來就是實做簽名跟解析的方法:
impl JwtHandler {
pub fn create_token(self, user_name: &str) -> String {
let claims = Claims {
aud: user_name.to_owned(),
exp: (Utc::now() + chrono::Duration::minutes(self.expiration_minutes)).timestamp()
as usize,
iat: Utc::now().timestamp() as usize,
iss: "zero_to_production".to_owned(),
nbf: Utc::now().timestamp() as usize,
sub: "zero_to_production".to_owned(),
};
encode(
&self.header,
&claims,
&EncodingKey::from_secret(self.private_key.expose_secret().as_ref()),
)
.unwrap_or_default()
}
pub fn decode_token(self, token: String) -> Result<Claims> {
let mut validation = jsonwebtoken::Validation::new(Algorithm::HS512);
validation.sub = Some("zero_to_production".to_string());
decode::<Claims>(
&token,
&DecodingKey::from_secret(self.private_key.expose_secret().as_ref()),
&validation,
)
.map(|data| data.claims)
}
}
簽名的實現非常簡單,只要使用jsonwebtoken
提供的encode
方法即可,注意一下EncodingKey
這個結構其實提供了很多種不同的簽名方式,通常情況下用默認的就可以了。
接下來是解析token的方法,首先建立驗證器後,驗證器會將token解析的結果以Result
的方式回傳,除了解析失敗以外,因為我們的claims有設定過期時間,所以解析完畢後發現token過期也會回傳Error