Skip to main content

wechat_minapp/user/
credential.rs

1use super::User;
2use super::user_info::{UserBuilder, UserInfo};
3#[allow(deprecated)]
4use aes::{
5    Aes128,
6    cipher::{BlockDecryptMut, KeyIvInit, block_padding::Pkcs7, generic_array::GenericArray},
7};
8use base64::{Engine, engine::general_purpose::STANDARD};
9use cbc::Decryptor;
10use hex::encode;
11use hmac::{Hmac, Mac};
12use http::Method;
13use serde::{Deserialize, Serialize};
14use serde_json::from_slice;
15use sha2::Sha256;
16use tracing::{debug, instrument};
17
18use crate::utils::{RequestBuilder, ResponseExt};
19use crate::{Result, constants};
20
21type Aes128CbcDec = Decryptor<Aes128>;
22
23#[derive(Serialize, Deserialize, Clone)]
24pub struct Credential {
25    #[serde(rename = "openid")]
26    open_id: String,
27    session_key: String,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    union_id: Option<String>,
30}
31
32impl Credential {
33    pub fn open_id(&self) -> &str {
34        &self.open_id
35    }
36
37    pub fn session_key(&self) -> &str {
38        &self.session_key
39    }
40
41    pub fn union_id(&self) -> Option<&str> {
42        self.union_id.as_deref()
43    }
44
45    /// 解密用户数据,使用的是 AES-128-CBC 算法,数据采用PKCS#7填充。
46    /// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
47    /// ```no_run
48    /// use wechat_minapp::client::WechatMinappSDK;
49    /// use wechat_minapp::user::{User, Contact};
50    ///
51    ///  #[tokio::main]
52    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
53    ///     let client = WechatMinappSDK::new("app_id", "secret");
54    ///     let user = User::new(client);
55    ///     let code = "0816abc123def456";
56    ///     let credential = user.login(code).await?;
57    ///     let info = credential.decrypt(&encrypted_data, &iv)?;
58    ///     println!("昵称: {}", info.nickname());
59    ///     println!("性别: {}", info.gender());
60    ///     println!("地区: {}-{}-{}", info.country(), info.province(), info.city());
61    ///     println!("头像: {}", info.avatar());
62    ///     println!("AppID: {}", info.app_id());
63    ///     println!("时间戳: {}", info.timestamp());
64    ///     
65    ///     Ok(())
66    /// }
67    /// ```
68    #[instrument(skip(self, encrypted_data, iv))]
69    pub fn decrypt(&self, encrypted_data: &str, iv: &str) -> Result<UserInfo> {
70        debug!("encrypted_data: {}", encrypted_data);
71        debug!("iv: {}", iv);
72
73        let key = STANDARD.decode(self.session_key.as_bytes())?;
74        let iv = STANDARD.decode(iv.as_bytes())?;
75        #[allow(deprecated)]
76        let decryptor = Aes128CbcDec::new(
77            &GenericArray::clone_from_slice(&key),
78            &GenericArray::clone_from_slice(&iv),
79        );
80
81        let encrypted_data = STANDARD.decode(encrypted_data.as_bytes())?;
82
83        let buffer = decryptor.decrypt_padded_vec_mut::<Pkcs7>(&encrypted_data)?;
84
85        let builder = from_slice::<UserBuilder>(&buffer)?;
86
87        debug!("user builder: {:#?}", builder);
88
89        Ok(builder.build())
90    }
91}
92
93impl std::fmt::Debug for Credential {
94    // 为了安全,不打印 session_key
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        f.debug_struct("Credential")
97            .field("open_id", &self.open_id)
98            .field("session_key", &"********")
99            .field("union_id", &self.union_id)
100            .finish()
101    }
102}
103
104type HmacSha256 = Hmac<Sha256>;
105
106impl User {
107    /// 检查登录态是否过期
108    /// [官方文档](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html)
109    #[instrument(skip(self, session_key, open_id))]
110    pub async fn check_session_key(&self, session_key: &str, open_id: &str) -> Result<()> {
111        let mut mac = HmacSha256::new_from_slice(session_key.as_bytes())?;
112        mac.update(b"");
113        let hasher = mac.finalize();
114        let signature = encode(hasher.into_bytes());
115
116        let query = serde_json::json!({
117            "openid": open_id.to_string(),
118            "signature":signature,
119            "sig_method": "hmac_sha256".to_string()
120        });
121
122        let request = RequestBuilder::new(constants::CHECK_SESSION_KEY_END_POINT)
123            .query(query)
124            .method(Method::GET)
125            .build()?;
126
127        let client = &self.client.client;
128
129        let response = client.execute(request).await?;
130
131        debug!("response: {:#?}", response);
132        response.to_json::<()>()
133    }
134
135    /// 重置用户的 session_key
136    /// [官方文档](https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/ResetUserSessionKey.html)
137    #[instrument(skip(self, open_id))]
138    pub async fn reset_session_key(&self, session_key: &str, open_id: &str) -> Result<Credential> {
139        let mut mac = HmacSha256::new_from_slice(session_key.as_bytes())?;
140        mac.update(b"");
141        let hasher = mac.finalize();
142        let signature = encode(hasher.into_bytes());
143
144        let query = serde_json::json!({
145            "access_token":self.client.token().await?,
146            "openid": open_id.to_string(),
147            "signature":signature,
148            "sig_method": "hmac_sha256".to_string()
149        });
150
151        let request = RequestBuilder::new(constants::RESET_SESSION_KEY_END_POINT)
152            .query(query)
153            .method(Method::GET)
154            .build()?;
155
156        let client = &self.client.client;
157        let response = client.execute(request).await?;
158        debug!("response: {:#?}", &response);
159
160        response.to_json::<Credential>()
161    }
162}