Skip to main content

crab_vault_auth/
lib.rs

1pub mod error;
2
3use clap::ValueEnum;
4use jsonwebtoken::{Algorithm, EncodingKey, Header};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::vec;
8use uuid::Uuid;
9use validator::{Validate, ValidationError};
10
11#[cfg(feature = "server-side")]
12use base64::Engine;
13#[cfg(feature = "server-side")]
14use glob::Pattern;
15#[cfg(feature = "server-side")]
16use jsonwebtoken::{DecodingKey, Validation};
17
18use crate::error::AuthError;
19
20#[derive(Clone)]
21pub struct JwtEncoder {
22    /// 用于签发 JWT 的密钥。从 kid 到 ([`EncodingKey`], [`Algorithm`]) 的映射
23    pub encoding_key: HashMap<String, (EncodingKey, Algorithm)>,
24
25    kids: Vec<String>
26}
27
28#[cfg(feature = "server-side")]
29#[derive(Clone)]
30pub struct JwtDecoder {
31    /// 用于验证 JWT 的密钥映射。
32    ///
33    /// [`HashMap`] 的键是签发者 (iss, kid),值是对应的轮换密钥 ([`DecodingKey`])。
34    #[cfg(feature = "server-side")]
35    decoding_keys: HashMap<(String, String), DecodingKey>,
36
37    /// JWT 的验证规则。
38    ///
39    /// 用于配置如何验证 `exp`, `nbf`, `iss`, `aud` 等标准声明。
40    #[cfg(feature = "server-side")]
41    validation: Validation,
42}
43
44/// ## 表示一个完整的 JWT,包含标准声明和自定义载荷。
45///
46/// 泛型参数 `P` 代表自定义的载荷 (Payload) 结构体。
47#[derive(Serialize, Deserialize, Clone, Debug)]
48#[serde(rename_all = "camelCase")]
49pub struct Jwt<P> {
50    /// (Issuer) 签发者
51    pub iss: String,
52
53    /// (Audience) 受众。可以是一个或多个。
54    pub aud: Vec<String>,
55
56    /// (Expiration Time) 过期时间。Unix 时间戳。
57    pub exp: i64,
58
59    /// (Not Before) 生效时间。Unix 时间戳。
60    pub nbf: i64,
61
62    /// (Issued At) 签发时间。Unix 时间戳。
63    pub iat: i64,
64
65    /// (JWT ID) 令牌唯一标识。
66    pub jti: Uuid,
67
68    /// 自定义的载荷数据。
69    pub load: P,
70}
71
72/// ## JWT 令牌的载荷 (Payload) 中用于权限控制的部分。
73#[derive(Serialize, Deserialize, Validate, Clone, Debug, PartialEq)]
74#[serde(rename_all = "camelCase")]
75pub struct Permission {
76    /// ## 允许的操作列表。
77    ///
78    /// 定义此令牌授权执行的具体 [`HTTP`](HttpMethod) 方法。
79    pub methods: Vec<HttpMethod>,
80
81    /// ## 资源路径模式。
82    ///
83    /// 定义此令牌可以访问的资源路径,支持通配符 `*` 和 `?` (Glob 模式)。
84    ///
85    /// 如果是 None,那么表示这个令牌没有任何对象的操作权限
86    #[validate(length(max = 128))]
87    pub resource_pattern: Option<String>,
88
89    /// ## 允许上传的最大对象大小 (字节)。
90    ///
91    /// `None` 表示没有限制。
92    pub max_size: Option<usize>,
93
94    /// ## 允许的内容类型 (MIME types)。
95    ///
96    /// 支持通配符,例如 `image/*` 或 `*` (Glob 模式)。
97    ///
98    /// **大小有限制,每一个通配模式不超过 128 字节、最多 8 个模式**
99    #[validate(custom(function = "Self::validate_content_type_pattern"))]
100    pub allowed_content_types: Vec<String>,
101}
102
103#[cfg(feature = "server-side")]
104#[derive(Clone)]
105pub struct CompiledPermission {
106    pub methods: Vec<HttpMethod>,
107    pub resource_pattern: Option<String>,
108    pub max_size: Option<usize>,
109    pub allowed_content_types: Vec<String>,
110    resource_pattern_cache: Option<Pattern>,
111    allowed_content_types_cache: Vec<Pattern>,
112}
113
114/// HTTP 操作方法枚举。
115///
116/// [`ValueEnum`] 用于 [`clap`] 集成,使其可以在命令行参数中使用。
117#[derive(Serialize, Deserialize, PartialEq, Eq, Hash, Clone, Copy, Debug, ValueEnum)]
118#[serde(rename_all = "UPPERCASE")]
119pub enum HttpMethod {
120    Get,
121    Post,
122    Put,
123    Patch,
124    Delete,
125    Head,
126    Options,
127    Trace,
128    Connect,
129    /// 代表非标准的 HTTP 方法。
130    Other,
131    /// 代表所有 HTTP 方法,通常用于管理员权限。
132    All,
133    /// 代表所有安全的 HTTP 方法,你可以参看 [`HttpMethod::safe`] 获取 **安全** 一词的含义
134    Safe,
135    /// 代表所有不安全的 HTTP 方法,你可以参看 [`HttpMethod::safe`] 获取 **安全** 一词的含义
136    Unsafe,
137}
138
139impl JwtEncoder {
140    #[inline]
141    pub fn new(encoding_key: HashMap<String, (EncodingKey, Algorithm)>) -> Self {
142        let kids = encoding_key.keys().cloned().collect();
143        Self { encoding_key, kids }
144    }
145
146    /// ## 将 JWT 声明编码为字符串形式的 Token
147    ///
148    /// **注意**:header 中的 alg 字段和 kid 对应的加密算法需要保持一致
149    #[inline]
150    pub fn encode<P: Serialize>(
151        &self,
152        claims: &Jwt<P>,
153        kid: &str,
154    ) -> Result<String, AuthError> {
155        use AuthError::InternalError;
156
157        let (key, alg) = self
158            .encoding_key
159            .get(kid)
160            .ok_or(InternalError("No such kid found in your encoder".into()))?;
161
162        let mut header = Header::new(*alg);
163        header.kid = Some(kid.to_string());
164
165        Ok(jsonwebtoken::encode(&header, claims, key)?)
166    }
167
168    pub fn encode_randomly<P: Serialize>(&self, claims: &Jwt<P>) -> Result<String, AuthError> {
169        let random_kid = &self.kids[rand::random_range(..self.kids.len())];
170        self.encode(claims, random_kid)
171    }
172}
173
174#[cfg(feature = "server-side")]
175impl JwtDecoder {
176    /// ## 新建一个 [`JwtDecoder`]
177    ///
178    /// ### 参数说明
179    ///
180    /// - `mapping` `iss`、`kid` 到 [`DecodingKey`] 的映射,注意  [`mapping`](HashMap) 的联合主键的顺序是 (iss, kid),别搞反了!
181    /// - `algorithms`    接受的算法
182    /// - `iss`     接受的令牌的签发人
183    /// - `aud`     接受的令牌中的 aud 值
184    ///
185    /// ### panic
186    ///
187    /// - 如果 `algorithms` 中一个算法都没有,即 `algorithms` 是一个空的切片
188    ///
189    /// ### 新建完成后可以通过以下函数修改相应的配置
190    ///
191    /// - [`iss_kid_dec`](JwtDecoder::iss_kid_dec)
192    /// - [`algorithms`](JwtDecoder::algorithms)
193    /// - [`authorized_issuer`](JwtDecoder::authorized_issuer)
194    /// - [`possible_audience`](JwtDecoder::possible_audience)
195    /// - [`leeway`](JwtDecoder::leeway)
196    /// - [`reject_tokens_expiring_in_less_than`](JwtDecoder::reject_tokens_expiring_in_less_than)
197    ///
198    /// ### 然后可以使用方法 [`decode`](JwtDecoder::decode) 来解码、校验一个 jwt
199    ///
200    pub fn new<T: ToString, U: ToString>(
201        mapping: HashMap<(String, String), DecodingKey>,
202        algorithms: &[Algorithm],
203        iss: &[T],
204        aud: &[U],
205    ) -> Self {
206        let mut validation =
207            Validation::new(*algorithms.first().expect(
208                "You should provide at least one algorithm in your accepted algorithm slice!",
209            ));
210        validation.validate_aud = true;
211        validation.validate_exp = true;
212        validation.validate_nbf = true;
213        validation.algorithms = algorithms.to_vec();
214        validation.reject_tokens_expiring_in_less_than = 0;
215        validation.leeway = 60;
216        validation.set_issuer(iss);
217        validation.set_audience(aud);
218
219        // 必须有下面的四个字段,否则视为非法 token,
220        // jsonwebtoken 只接受下面的这些和 sub 字段,所以 iat 限制无法设置
221        // 当然,如果没有,serde 也会自己产生反序列化错误,所以应该没问题……吧
222
223        validation.set_required_spec_claims(&["aud", "exp", "nbf", "iss"]);
224        Self {
225            decoding_keys: mapping,
226            validation,
227        }
228    }
229
230    /// ## 设置 (iss, kid) 到 [`DecodingKey`] 的映射
231    ///
232    /// 注意  [`mapping`](HashMap) 的联合主键的顺序是 (iss, kid),别搞反了!
233    #[inline]
234    pub fn iss_kid_dec(mut self, mapping: HashMap<(String, String), DecodingKey>) -> Self {
235        self.decoding_keys = mapping;
236        self
237    }
238
239    /// ## 设置接受的算法
240    #[inline]
241    pub fn algorithms(mut self, algorithms: &[Algorithm]) -> Self {
242        self.validation.algorithms = algorithms.to_vec();
243        self
244    }
245
246    /// ## 设置接受的 issuer
247    #[inline]
248    pub fn authorized_issuer<T: ToString>(mut self, iss: &[T]) -> Self {
249        self.validation.set_issuer(iss);
250        self
251    }
252
253    /// ## 设置接受的 audience
254    #[inline]
255    pub fn possible_audience<T: ToString>(mut self, aud: &[T]) -> Self {
256        self.validation.set_audience(aud);
257        self
258    }
259
260    /// ## 设置接受的 leeway
261    #[inline]
262    pub const fn leeway(mut self, leeway: u64) -> Self {
263        self.validation.leeway = leeway;
264        self
265    }
266
267    /// ## 临期的 token 不予通过
268    #[inline]
269    pub const fn reject_tokens_expiring_in_less_than(mut self, tolerance: u64) -> Self {
270        self.validation.reject_tokens_expiring_in_less_than = tolerance;
271        self
272    }
273
274    /// ## 使用给定的配置解码并验证一个字符串形式的 Token。
275    ///
276    /// 此函数会执行完整的验证流程,包括:
277    /// 1. 检查签名是否有效。
278    /// 2. 验证 `exp` 和 `nbf` 时间戳。
279    /// 3. 根据 `config.validation` 中的设置验证 `iss` 和 `aud`。
280    ///
281    /// ### 泛型参数说明
282    ///
283    /// 注意这个函数的泛型参数 `P` 代表的是 **载荷 (Payload)** 的类型,而不是 `Jwt` 本身。
284    ///
285    /// ### 代码示例
286    ///
287    /// #### 推荐写法 (Best Practice)
288    ///
289    /// 利用 Rust 的类型推断,显式标注变量类型,代码最为清晰:
290    ///
291    /// ```rust,no_run
292    /// # use crab_vault_auth::{JwtDecoder, Jwt, Permission, error::AuthError};
293    /// # fn example(decoder: &JwtDecoder, token: &str) -> Result<(), AuthError> {
294    /// // 编译器会自动推断出 P 是 Permission
295    /// let jwt: Jwt<Permission> = decoder.decode(token)?;
296    /// # Ok(())
297    /// # }
298    /// ```
299    ///
300    /// #### 显式泛型写法
301    ///
302    /// 也可以使用 Turbofish 语法显式指定载荷类型:
303    ///
304    /// ```rust,no_run
305    /// # use crab_vault_auth::{JwtDecoder, Jwt, Permission, error::AuthError};
306    /// # fn example(decoder: &JwtDecoder, token: &str) -> Result<(), AuthError> {
307    /// // 注意:尖括号内只需填 Permission
308    /// let jwt = decoder.decode::<Permission>(token)?;
309    /// // 此时 jwt 的类型为 Jwt<Permission>
310    /// # Ok(())
311    /// # }
312    /// ```
313    ///
314    /// #### 错误写法 (编译失败)
315    ///
316    /// 不要将 `Jwt<Permission>` 作为泛型参数传入,否则会导致类型嵌套 (`Jwt<Jwt<P>>`),
317    /// 这会导致类型不匹配从而**编译失败**:
318    ///
319    /// ```rust,compile_fail
320    /// # use crab_vault_auth::{JwtDecoder, Jwt, Permission, AuthError};
321    /// # fn example(decoder: &JwtDecoder, token: &str) -> Result<(), AuthError> {
322    /// // 错误:decode 返回的是 Jwt<T>。
323    /// // 如果传入 T = Jwt<Permission>,返回值就是 Jwt<Jwt<Permission>>。
324    /// // 这与左侧的变量类型 Jwt<Permission> 不匹配。
325    /// let jwt: Jwt<Permission> = decoder.decode::<Jwt<Permission>>(token)?;
326    /// # Ok(())
327    /// # }
328    /// ```
329    #[cfg(feature = "server-side")]
330    pub fn decode<P>(&self, token: &str) -> Result<Jwt<P>, AuthError>
331    where
332        for<'de> P: Deserialize<'de>,
333    {
334        let kid = jsonwebtoken::decode_header(token)?
335            .kid
336            .ok_or(AuthError::MissingClaim("kid".to_string()))?;
337
338        let body_unchecked: Jwt<P> = serde_json::from_value(Self::decode_unchecked(token)?)?;
339
340        let key = self
341            .decoding_keys
342            .get(&(body_unchecked.iss, kid))
343            .ok_or(AuthError::InvalidIssuer)?;
344
345        Ok(jsonwebtoken::decode::<Jwt<P>>(token, key, &self.validation)?.claims)
346    }
347
348    /// ## **\[不安全\]** 在不验证签名的情况下解码 JWT 的载荷。
349    ///
350    /// # 警告
351    ///
352    /// **绝对不要**相信此函数返回的数据!因为它**没有验证** JWT 的签名。
353    /// 这意味着任何人都可以伪造这个 JWT 的内容。
354    ///
355    /// 此函数仅应用于需要查看 Token 内容的调试或日志记录场景。
356    /// 在任何与安全相关的逻辑中,都**必须**使用 [`JwtDecoder::decode`]。
357    #[cfg(feature = "server-side")]
358    pub fn decode_unchecked(token: &str) -> Result<serde_json::Value, AuthError> {
359        let mut parts = token.split('.');
360        let _header = parts.next();
361        let payload = parts.next().ok_or(AuthError::InvalidToken)?;
362
363        let decoded_payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(payload)?;
364        let json_value = serde_json::from_slice(&decoded_payload)?;
365
366        Ok(json_value)
367    }
368}
369
370impl<P: Serialize + for<'de> Deserialize<'de>> Jwt<P> {
371    /// 创建一个新的 `Jwt` 实例,并填入默认值。
372    ///
373    /// 默认值:
374    /// - `iss`: `None`
375    /// - `aud`: 空 `Vec`
376    /// - `exp`: `一小时后` 的时间戳
377    /// - `nbf`: `0` (立即生效)
378    /// - `iat`: 当前时间的 Unix 时间戳
379    /// - `jti`: 一个使用 [`Uuid::new_v4`] 新生成的 [`Uuid`]
380    #[inline]
381    pub fn new<T: ToString, U: ToString>(iss: T, aud: &[U], payload: P) -> Self {
382        let now = chrono::Utc::now().timestamp();
383        Self {
384            iss: iss.to_string(),
385            aud: aud.iter().map(|s| s.to_string()).collect(),
386            exp: now + 3600,
387            nbf: now,
388            iat: now,
389            jti: Uuid::new_v4(),
390            load: payload,
391        }
392    }
393
394    /// 设置 JWT 的相对过期时间,从现在开始计算。
395    #[inline]
396    pub fn expires_in(mut self, duration: chrono::Duration) -> Self {
397        self.exp = (chrono::Utc::now() + duration).timestamp();
398        self
399    }
400
401    /// 设置 JWT 的过期时间为一个绝对的时间点。
402    #[inline]
403    pub fn expires_at<T>(mut self, when: chrono::DateTime<T>) -> Self
404    where
405        T: chrono::TimeZone,
406    {
407        self.exp = when.timestamp();
408        self
409    }
410
411    /// !!! 永不过期 !!!
412    #[inline]
413    pub const fn never_expires(mut self) -> Self {
414        self.exp = i32::MAX as i64;
415        self
416    }
417
418    /// 设置 JWT 的生效时间,从现在开始计算。
419    #[inline]
420    pub fn not_valid_in(mut self, duration: chrono::Duration) -> Self {
421        self.nbf = (chrono::Utc::now() + duration).timestamp();
422        self
423    }
424
425    /// 设置 JWT 的生效时间为一个绝对的时间点。
426    #[inline]
427    pub fn not_valid_till<T>(mut self, when: chrono::DateTime<T>) -> Self
428    where
429        T: chrono::TimeZone,
430    {
431        self.nbf = when.timestamp();
432        self
433    }
434
435    /// 在构建 token 的时候更换 uuid
436    #[inline]
437    pub const fn uuid(mut self, id: Uuid) -> Self {
438        self.jti = id;
439        self
440    }
441}
442
443impl Default for Permission {
444    #[inline]
445    fn default() -> Self {
446        Self::new_minimum()
447    }
448}
449
450impl Permission {
451    fn validate_content_type_pattern(patterns: &[String]) -> Result<(), ValidationError> {
452        if patterns.len() <= 8 && patterns.iter().all(|s| s.len() <= 128) {
453            Ok(())
454        } else {
455            Err(ValidationError::new("pattern too long/much for parsing"))
456        }
457    }
458
459    #[inline]
460    pub const fn new() -> Self {
461        Self::new_minimum()
462    }
463
464    /// 创建一个 <u>**拥有所有权限**</u> 的 `root` `Permission`。
465    ///
466    /// ### 这个操作应当尽量少用,因为这个获取这个权限就意味着该用户能够读写所有的资源 (所有!)
467    ///
468    /// 默认值
469    ///
470    /// - 允许操作: [`HttpMethod::All`]
471    /// - 允许资源: [`Some("*".to_string())`](Some) (所有路径)
472    /// - 大小限制:[`None`]
473    /// - MIME: **所有**
474    pub fn new_root() -> Self {
475        Self {
476            methods: vec![HttpMethod::All],
477            resource_pattern: Some("*".to_string()),
478            max_size: None,
479            allowed_content_types: vec!["*".to_string()],
480        }
481    }
482
483    /// 创建一个 <u>**没有任何权限**</u> 的 "minimum" `Permission`。
484    ///
485    /// 直接签发这个 [`Permission`] 将导致完全无法访问任何内容
486    ///
487    /// 默认值
488    ///
489    /// - 允许操作: 无(一个空的 vec)
490    /// - 允许资源: [`None`] (所有路径都不允许)
491    /// - 大小限制:[`Some(0)`](Some) (上传的最大包大小为 0 字节)
492    /// - MIME: **所有都不行**
493    pub const fn new_minimum() -> Self {
494        Self {
495            methods: vec![],
496            resource_pattern: None,
497            max_size: Some(0),
498            allowed_content_types: vec![],
499        }
500    }
501
502    /// 更换这个 [`Permission`] 允许的 operations
503    ///
504    /// 注意这会**更换**,而不是添加
505    #[inline]
506    pub fn permit_method(mut self, methods: Vec<HttpMethod>) -> Self {
507        self.methods = methods;
508        self
509    }
510
511    /// 修改这个令牌能够访问的资源路径
512    #[inline]
513    pub fn permit_resource_pattern<T>(mut self, pattern: T) -> Self
514    where
515        T: Into<String>,
516    {
517        self.resource_pattern = Some(pattern.into());
518        self
519    }
520
521    /// 修改这个令牌能够访问的资源路径
522    #[inline]
523    pub fn permit_resource_pattern_option<T>(mut self, pattern: Option<T>) -> Self
524    where
525        T: Into<String>,
526    {
527        self.resource_pattern = pattern.map(T::into);
528        self
529    }
530
531    /// 设置最大的内容长度
532    #[inline]
533    pub const fn restrict_maximum_size(mut self, max: usize) -> Self {
534        self.max_size = Some(max);
535        self
536    }
537
538    #[inline]
539    pub const fn restrict_maximum_size_option(mut self, max: Option<usize>) -> Self {
540        self.max_size = max;
541        self
542    }
543
544    /// 此令牌允许的最大内容类型
545    #[inline]
546    pub fn permit_content_type(mut self, content_type: Vec<String>) -> Self {
547        self.allowed_content_types = content_type;
548        self
549    }
550
551    #[cfg(feature = "server-side")]
552    pub fn compile(self) -> CompiledPermission {
553        let Permission {
554            methods,
555            resource_pattern,
556            max_size,
557            allowed_content_types,
558        } = self;
559
560        let resource_pattern_cache = match &resource_pattern {
561            Some(pat) => Pattern::new(pat).ok(),
562            None => None,
563        };
564
565        let mut allowed_content_types_cache = vec![];
566
567        for pat in &allowed_content_types {
568            if let Ok(pat) = Pattern::new(pat) {
569                allowed_content_types_cache.push(pat)
570            }
571        }
572
573        CompiledPermission {
574            methods,
575            resource_pattern,
576            max_size,
577            allowed_content_types,
578            resource_pattern_cache,
579            allowed_content_types_cache,
580        }
581    }
582}
583
584#[cfg(feature = "server-side")]
585impl CompiledPermission {
586    /// ## 检查此权限是否允许执行给定的 HTTP 方法。
587    ///
588    /// 此方法会依次检查:
589    ///
590    /// 1. [`Permission`] 中含有 [`All`](HttpMethod::All),返回 `true`
591    /// 2. [`Permission`] 中含有提供的 [`method`](HttpMethod),返回 `true`
592    /// 3. [`Permission`] 中是否含有 [`Safe`](HttpMethod::Safe),若有,且提供的 [`method`](HttpMethod) 的确是安全的,返回 `true`
593    /// 4. [`Permission`] 中是否含有 [`Unsafe`](HttpMethod::Unsafe),若有,且提供的 [`method`](HttpMethod) 的确是不安全的,返回 `true`
594    /// 5. 其他,返回 false
595    pub fn can_perform_method(&self, method: HttpMethod) -> bool {
596        self.methods.contains(&HttpMethod::All)
597            || self.methods.contains(&method)
598            || (self.methods.contains(&HttpMethod::Safe) && method.safe())
599            || (self.methods.contains(&HttpMethod::Unsafe) && !method.safe())
600    }
601
602    /// ## 检查此权限是否能访问给定的资源路径。
603    ///
604    /// 使用 `resource_pattern` 对 `path` 进行 Glob 匹配。
605    ///
606    /// - 如果 `resource_pattern` 不是一个有效的 Glob 模式,会安全地返回 `false`。
607    /// - 如果是一个 [`None`] 也会返回 false,因为规定了 [`None`] 表示所有都不能访问
608    pub fn can_access(&self, path: &str) -> bool {
609        match &self.resource_pattern_cache {
610            Some(pat) => pat.matches(path),
611            None => false,
612        }
613    }
614
615    /// ## 检查给定的大小是否在 `max_size` 的限制内。
616    ///
617    /// - 如果 `max_size` 是 `None` (无限制)
618    /// - 或者 `size` 小于等于限制,则返回 `true`。
619    pub fn check_size(&self, size: usize) -> bool {
620        self.max_size.is_none_or(|limit| size <= limit)
621    }
622
623    /// ## 检查给定的内容类型是否被允许。
624    ///
625    /// 遍历 `allowed_content_types`,对每个模式进行 Glob 匹配。
626    pub fn check_content_type(&self, content_type: &str) -> bool {
627        self.allowed_content_types_cache
628            .iter()
629            .any(|allow_pat| allow_pat.matches(content_type))
630    }
631}
632
633impl From<&axum::http::Method> for HttpMethod {
634    fn from(value: &axum::http::Method) -> Self {
635        use axum::http::Method;
636
637        match *value {
638            Method::GET => Self::Get,
639            Method::POST => Self::Post,
640            Method::PUT => Self::Put,
641            Method::PATCH => Self::Patch,
642            Method::DELETE => Self::Delete,
643            Method::HEAD => Self::Head,
644            Method::OPTIONS => Self::Options,
645            Method::TRACE => Self::Trace,
646            Method::CONNECT => Self::Connect,
647            _ => Self::Other,
648        }
649    }
650}
651
652impl From<axum::http::Method> for HttpMethod {
653    fn from(value: axum::http::Method) -> Self {
654        Self::from(&value)
655    }
656}
657
658impl HttpMethod {
659    /// ## 判断一个方法是否安全
660    ///
661    /// 根据 [MDN](https://developer.mozilla.org/zh-CN/docs/Glossary/Safe/HTTP)
662    /// 以及 [rfc7231](https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1)
663    /// 对于安全的定义
664    ///
665    /// 一个方法是否安全取决于该方法的请求在被服务器响应后,<u>**服务器的状态是否改变**</u>
666    ///
667    /// 或者说一个方法安不安全取决于是否蕴含着**写入请求**
668    ///
669    /// 所以,对于 [`OPTIONS`](HttpMethod::Options) 这类在通常认知中
670    /// 会造成服务器信息暴露等问题的方法,仍然认为是安全的
671    ///
672    ///
673    /// - 如果一个方法是只读的,如 [`HEAD`](HttpMethod::Head),[`GET`](HttpMethod::Get) 等,那他就是安全的
674    /// - 如果一个方法有写入的含义,如 [`PUT`](HttpMethod::Put),[`DELETE`](HttpMethod::Delete) 等,那么就不安全
675    ///
676    /// 同时,在这里,由于有两个例外:[`HttpMethod::Other`] 和 [`HttpMethod::All`] 这两个标记
677    ///
678    /// 它们两个一个代表其他请求(rfc规范之外的),一个代表所有的请求,包括 rfc 规范之外的,所以都视为不安全
679    pub fn safe(self) -> bool {
680        match self {
681            // safe 不必说,必然是安全的
682            HttpMethod::Safe
683            | HttpMethod::Get
684            | HttpMethod::Head
685            | HttpMethod::Options
686            | HttpMethod::Trace => true,
687            // unsafe operations,这些操作会导致内容改变
688            HttpMethod::Unsafe
689            | HttpMethod::Connect
690            | HttpMethod::Post
691            | HttpMethod::Put
692            | HttpMethod::Patch
693            | HttpMethod::Delete
694            | HttpMethod::Other
695            | HttpMethod::All => false,
696        }
697    }
698
699    pub fn as_str(self) -> &'static str {
700        match self {
701            HttpMethod::Get => "GET",
702            HttpMethod::Post => "POST",
703            HttpMethod::Put => "PUT",
704            HttpMethod::Patch => "PATCH",
705            HttpMethod::Delete => "DELETE",
706            HttpMethod::Head => "HEAD",
707            HttpMethod::Options => "OPTIONS",
708            HttpMethod::Trace => "TRACE",
709            HttpMethod::Connect => "CONNECT",
710            HttpMethod::Other => "OTHER",
711            HttpMethod::All => "ALL",
712            HttpMethod::Safe => "SAFE",
713            HttpMethod::Unsafe => "UNSAFE",
714        }
715    }
716}