添加微信登录相关接口
This commit is contained in:
parent
e84d9c188c
commit
96a9bf72ac
741
Cargo.lock
generated
741
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -45,4 +45,7 @@ error-stack = "0.4"
|
||||
jsonwebtoken = "9.3.0"
|
||||
lazy_static = "1.4.0"
|
||||
mimalloc = "0.1.42"
|
||||
tokio-cron-scheduler = "0.10.2"
|
||||
tokio-cron-scheduler = "0.10.2"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.8"
|
||||
hex-literal = "0.4.1"
|
6
app.toml
6
app.toml
@ -16,4 +16,8 @@ options = { min_conns = 10, max_conns = 20, conn_timeout = 30, idle_timeout = 30
|
||||
token_secret = "chuanyue"
|
||||
refresh_token_secret = "chuanyue"
|
||||
expires = 1800
|
||||
refresh_expires = 3600
|
||||
refresh_expires = 3600
|
||||
|
||||
[social.wechat]
|
||||
app_id = "wx0d0b0b0b0b0b0b0b"
|
||||
app_secret = "0d0b0b0b0d0b0b0b0d0b0b0b0b0b0b0b"
|
||||
|
@ -28,6 +28,9 @@ reqwest = { workspace = true, features = ["blocking", "json"] }
|
||||
validator = { workspace = true }
|
||||
moka = { workspace = true, features = ["future", "logging"] }
|
||||
lazy_static = { workspace = true }
|
||||
hmac = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
hex-literal = { workspace = true }
|
||||
|
||||
domain = { path = "../domain" }
|
||||
i18n = { path = "../i18n" }
|
@ -8,7 +8,8 @@ pub struct Config {
|
||||
pub server: Server,
|
||||
pub logger: Logger,
|
||||
pub database: Database,
|
||||
pub jwt: Jwt
|
||||
pub jwt: Jwt,
|
||||
pub social: Social,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
@ -48,6 +49,20 @@ pub struct Jwt {
|
||||
pub refresh_expires: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Social {
|
||||
pub wechat: Wechat,
|
||||
// pub google: google::GoogleSocial,
|
||||
// pub facebook: facebook::FacebookSocial,
|
||||
// pub apple: apple::AppleSocial,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Wechat {
|
||||
pub app_id: String,
|
||||
pub app_secret: String,
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! config {
|
||||
() => {
|
||||
|
@ -2,4 +2,5 @@ type SocialResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
pub mod google;
|
||||
pub mod facebook;
|
||||
pub mod apple;
|
||||
pub mod apple;
|
||||
pub mod wechat;
|
166
library/src/social/wechat.rs
Normal file
166
library/src/social/wechat.rs
Normal file
@ -0,0 +1,166 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use futures_util::lock::Mutex;
|
||||
use hmac::{Hmac, Mac};
|
||||
use lazy_static::lazy_static;
|
||||
use serde::Deserialize;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::{
|
||||
config,
|
||||
res::response::ResErr,
|
||||
};
|
||||
|
||||
use super::SocialResult;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref WECHAT_SOCIAL: WechatSocial = WechatSocial::default();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WechatSocial {
|
||||
app_id: String,
|
||||
app_secret: String,
|
||||
access_token: Arc<Mutex<WeChatAccessToken>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone, Default)]
|
||||
pub struct WeChatAccessToken {
|
||||
pub access_token: String,
|
||||
pub expires_in: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct WeChatBaseResult {
|
||||
// 错误码
|
||||
pub errcode: i64,
|
||||
// 错误信息
|
||||
pub errmsg: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct MiniAppLoginResult {
|
||||
// 用户唯一标识
|
||||
pub openid: String,
|
||||
// 会话密钥
|
||||
pub session_key: String,
|
||||
// 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台账号下会返回
|
||||
// 如果开发者拥有多个移动应用、网站应用、和公众账号(包括小程序),可通过 UnionID 来区分用户的唯一性
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html
|
||||
pub unionid: String,
|
||||
// 错误码
|
||||
pub errcode: i64,
|
||||
// 错误信息
|
||||
pub errmsg: String,
|
||||
}
|
||||
|
||||
impl Default for WechatSocial {
|
||||
fn default() -> Self {
|
||||
let wechat_option = &config!().social.wechat;
|
||||
Self {
|
||||
app_id: wechat_option.app_id.to_owned(),
|
||||
app_secret: wechat_option.app_secret.to_owned(),
|
||||
access_token: Arc::new(Mutex::new(WeChatAccessToken {
|
||||
access_token: "".to_string(),
|
||||
expires_in: 0,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WechatSocial {
|
||||
/// 异步获取微信公钥,适用于小程序和小游戏
|
||||
/// 本接口和getStableAccessToken类似
|
||||
/// 区别为:
|
||||
/// getAccessToken,每次调用都会生成新的access_token;
|
||||
/// getStableAccessToken,在force_refresh=false模式下,会尽量返回相同的access_token。
|
||||
/// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
|
||||
///
|
||||
/// https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/access-token/auth.getAccessToken.html
|
||||
/// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
|
||||
async fn fetch_and_parse_wechat_access_token(&self) -> SocialResult<WeChatAccessToken> {
|
||||
let mut wechat_access_token = self.access_token.lock().await;
|
||||
if wechat_access_token.access_token.is_empty()
|
||||
&& wechat_access_token.expires_in < Utc::now().timestamp()
|
||||
{
|
||||
let response = reqwest::get(format!(
|
||||
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}",
|
||||
self.app_id,
|
||||
self.app_secret
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
let mut new_wechat_access_token: WeChatAccessToken = response.json().await.unwrap();
|
||||
tracing::info!("wechat access token: {:?}", new_wechat_access_token);
|
||||
new_wechat_access_token.expires_in =
|
||||
Utc::now().timestamp() + wechat_access_token.expires_in;
|
||||
*wechat_access_token = new_wechat_access_token;
|
||||
}
|
||||
tracing::info!("wechat access token: {:?}", wechat_access_token);
|
||||
Ok(wechat_access_token.clone())
|
||||
}
|
||||
|
||||
/// 微信登录登录
|
||||
/// 本接口适用于小程序和小游戏
|
||||
/// https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/login/auth.code2Session.html
|
||||
/// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
|
||||
pub async fn code_2_session(&self, code: &str) -> SocialResult<MiniAppLoginResult> {
|
||||
let response = reqwest::get(format!(
|
||||
"https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code",
|
||||
self.app_id,
|
||||
self.app_secret,
|
||||
code
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
let result: MiniAppLoginResult = response.json().await.unwrap();
|
||||
if result.errcode != 0 {
|
||||
tracing::error!(
|
||||
"微信登录失败,errcode:{},errmsg:{}",
|
||||
result.errcode,
|
||||
result.errmsg
|
||||
);
|
||||
return Err(Box::new(ResErr::social(format!(
|
||||
"微信登录失败,errcode:{},errmsg:{}",
|
||||
result.errcode, result.errmsg
|
||||
))));
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 验证登录信息
|
||||
/// 本接口适用于小程序和小游戏
|
||||
///
|
||||
/// https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/login/auth.checkSessionKey.html
|
||||
/// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html
|
||||
/// todo 使用的hmac签名是否正确,签名结果转换成字符串是否正确
|
||||
pub async fn check_session(&self, session_key: &str, open_id: &str) -> SocialResult<()> {
|
||||
// let signature = Hmac::<Sha256>::new_from_slice(session.as_bytes()).expect("");
|
||||
let mut mac =
|
||||
Hmac::<Sha256>::new_from_slice(session_key.as_bytes()).expect("初始化hmac失败");
|
||||
mac.update(b"");
|
||||
let out = mac.finalize().into_bytes();
|
||||
let signature = std::str::from_utf8(out.trim_ascii())
|
||||
.unwrap_or_default()
|
||||
.trim_ascii()
|
||||
.to_string();
|
||||
let response = reqwest::get(format!(
|
||||
"https://api.weixin.qq.com/wxa/checksession?access_token={}&signature={}&openid={}&sig_method=hmac_sha256",
|
||||
self.access_token.lock().await.access_token,
|
||||
signature,
|
||||
open_id
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
let result: WeChatBaseResult = response.json().await.unwrap();
|
||||
if result.errcode != 0 {
|
||||
tracing::error!("微信登录校验失败,errcode:{},errmsg:{}", result.errcode, result.errmsg);
|
||||
return Err(Box::new(ResErr::social(format!(
|
||||
"微信登录校验失败,errcode:{},errmsg:{}",
|
||||
result.errcode, result.errmsg
|
||||
))));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
2
service/src/social.rs
Normal file
2
service/src/social.rs
Normal file
@ -0,0 +1,2 @@
|
||||
/// 用于接收各个服务平台的回调、消息推送
|
||||
|
@ -14,6 +14,7 @@ use library::{
|
||||
cache::account_cache::{CacheAccount, LOGIN_CACHE}, context::WhiteContext, db, res::response::{ResData, ResErr, ResResult}, token::{generate_refresh_token, generate_token}
|
||||
};
|
||||
|
||||
/// 登录, 使用账号和密码
|
||||
pub async fn authenticate_with_password(
|
||||
context: WhiteContext,
|
||||
req: AuthenticateWithPassword,
|
||||
|
Loading…
Reference in New Issue
Block a user