wechat_minapp/
credential.rs

1#[allow(deprecated)]
2use aes::{
3    Aes128,
4    cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7, generic_array::GenericArray},
5};
6use base64::{Engine, engine::general_purpose::STANDARD};
7use cbc::Decryptor;
8use hex::encode;
9use hmac::{Hmac, Mac};
10use serde::{Deserialize, Serialize};
11use serde_json::from_slice;
12use sha2::Sha256;
13use std::collections::HashMap;
14use tracing::{debug, instrument};
15
16use crate::{
17    Result,
18    client::Client,
19    constants,
20    error::Error::InternalServer,
21    response::Response,
22    user::{User, UserBuilder},
23};
24
25type Aes128CbcDec = Decryptor<Aes128>;
26
27#[derive(Serialize, Deserialize, Clone)]
28pub struct Credential {
29    open_id: String,
30    session_key: String,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    union_id: Option<String>,
33}
34
35impl Credential {
36    pub fn open_id(&self) -> &str {
37        &self.open_id
38    }
39
40    pub fn session_key(&self) -> &str {
41        &self.session_key
42    }
43
44    pub fn union_id(&self) -> Option<&str> {
45        self.union_id.as_deref()
46    }
47
48    /// 解密用户数据,使用的是 AES-128-CBC 算法,数据采用PKCS#7填充。
49    /// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
50    /// ```rust
51    /// use wechat_minapp::{Client, Result};
52    /// use serde::Deserialize;
53    /// use crate::{Error, state::AppState};
54    /// use actix_web::{Responder, web};
55    ///
56    /// #[derive(Deserialize, Default)]
57    /// pub(crate) struct EncryptedPayload {
58    ///     code: String,
59    ///     encrypted_data: String,
60    ///     iv: String,
61    /// }
62    ///
63    /// pub(crate) async fn decrypt(
64    ///     state: web::Data<AppState>,
65    ///     payload: web::Json<EncryptedPayload>,
66    /// ) -> Result<impl Responder, Error>  {
67    ///     let credential = state.client.login(&payload.code).await?;
68    ///
69    ///     let user = credential.decrypt(&payload.encrypted_data, &payload.iv)?;
70    ///
71    ///     Ok(())
72    /// }
73    /// ```
74    #[instrument(skip(self, encrypted_data, iv))]
75    pub fn decrypt(&self, encrypted_data: &str, iv: &str) -> Result<User> {
76        debug!("encrypted_data: {}", encrypted_data);
77        debug!("iv: {}", iv);
78
79        let key = STANDARD.decode(self.session_key.as_bytes())?;
80        let iv = STANDARD.decode(iv.as_bytes())?;
81        #[allow(deprecated)]
82        let decryptor = Aes128CbcDec::new(
83            &GenericArray::clone_from_slice(&key),
84            &GenericArray::clone_from_slice(&iv),
85        );
86
87        let encrypted_data = STANDARD.decode(encrypted_data.as_bytes())?;
88
89        let buffer = decryptor.decrypt_padded_vec_mut::<Pkcs7>(&encrypted_data)?;
90
91        let builder = from_slice::<UserBuilder>(&buffer)?;
92
93        debug!("user builder: {:#?}", builder);
94
95        Ok(builder.build())
96    }
97}
98
99impl std::fmt::Debug for Credential {
100    // 为了安全,不打印 session_key
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.debug_struct("Credential")
103            .field("open_id", &self.open_id)
104            .field("session_key", &"********")
105            .field("union_id", &self.union_id)
106            .finish()
107    }
108}
109
110#[derive(Deserialize)]
111pub(crate) struct CredentialBuilder {
112    #[serde(rename = "openid")]
113    open_id: String,
114    session_key: String,
115    #[serde(rename = "unionid")]
116    union_id: Option<String>,
117}
118
119impl CredentialBuilder {
120    pub(crate) fn build(self) -> Credential {
121        Credential {
122            open_id: self.open_id,
123            session_key: self.session_key,
124            union_id: self.union_id,
125        }
126    }
127}
128
129impl std::fmt::Debug for CredentialBuilder {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("CredentialBuilder")
132            .field("open_id", &self.open_id)
133            .field("session_key", &"********")
134            .field("union_id", &self.union_id)
135            .finish()
136    }
137}
138
139type HmacSha256 = Hmac<Sha256>;
140
141impl Client {
142    /// 检查登录态是否过期
143    /// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html
144    #[instrument(skip(self, session_key, open_id))]
145    pub async fn check_session_key(&self, session_key: &str, open_id: &str) -> Result<()> {
146        let mut mac = HmacSha256::new_from_slice(session_key.as_bytes())?;
147        mac.update(b"");
148        let hasher = mac.finalize();
149        let signature = encode(hasher.into_bytes());
150
151        let mut map = HashMap::new();
152
153        map.insert("openid", open_id.to_string());
154        map.insert("signature", signature);
155        map.insert("sig_method", "hmac_sha256".into());
156
157        let response = self
158            .request()
159            .get(constants::CHECK_SESSION_KEY_END_POINT)
160            .query(&map)
161            .send()
162            .await?;
163
164        debug!("response: {:#?}", response);
165
166        if response.status().is_success() {
167            let response = response.json::<Response<()>>().await?;
168
169            response.extract()
170        } else {
171            Err(crate::error::Error::InternalServer(response.text().await?))
172        }
173    }
174
175    /// 重置用户的 session_key
176    /// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/ResetUserSessionKey.html
177    #[instrument(skip(self, open_id))]
178    pub async fn reset_session_key(&self, session_key: &str, open_id: &str) -> Result<Credential> {
179        let mut mac = HmacSha256::new_from_slice(session_key.as_bytes())?;
180        mac.update(b"");
181        let hasher = mac.finalize();
182        let signature = encode(hasher.into_bytes());
183
184        let mut map = HashMap::new();
185
186        map.insert("access_token", self.access_token().await?);
187        map.insert("openid", open_id.to_string());
188        map.insert("signature", signature);
189        map.insert("sig_method", "hmac_sha256".into());
190
191        let response = self
192            .request()
193            .get(constants::RESET_SESSION_KEY_END_POINT)
194            .query(&map)
195            .send()
196            .await?;
197
198        debug!("response: {:#?}", response);
199
200        if response.status().is_success() {
201            let response = response.json::<Response<CredentialBuilder>>().await?;
202
203            let credential = response.extract()?.build();
204
205            debug!("credential: {:#?}", credential);
206
207            Ok(credential)
208        } else {
209            Err(InternalServer(response.text().await?))
210        }
211    }
212}