添加微信登录相关接口
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
@ -46,3 +46,6 @@ jsonwebtoken = "9.3.0"
|
|||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
mimalloc = "0.1.42"
|
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"
|
4
app.toml
4
app.toml
@ -17,3 +17,7 @@ token_secret = "chuanyue"
|
|||||||
refresh_token_secret = "chuanyue"
|
refresh_token_secret = "chuanyue"
|
||||||
expires = 1800
|
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 }
|
validator = { workspace = true }
|
||||||
moka = { workspace = true, features = ["future", "logging"] }
|
moka = { workspace = true, features = ["future", "logging"] }
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
|
hmac = { workspace = true }
|
||||||
|
sha2 = { workspace = true }
|
||||||
|
hex-literal = { workspace = true }
|
||||||
|
|
||||||
domain = { path = "../domain" }
|
domain = { path = "../domain" }
|
||||||
i18n = { path = "../i18n" }
|
i18n = { path = "../i18n" }
|
@ -8,7 +8,8 @@ pub struct Config {
|
|||||||
pub server: Server,
|
pub server: Server,
|
||||||
pub logger: Logger,
|
pub logger: Logger,
|
||||||
pub database: Database,
|
pub database: Database,
|
||||||
pub jwt: Jwt
|
pub jwt: Jwt,
|
||||||
|
pub social: Social,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
@ -48,6 +49,20 @@ pub struct Jwt {
|
|||||||
pub refresh_expires: i64,
|
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_export]
|
||||||
macro_rules! config {
|
macro_rules! config {
|
||||||
() => {
|
() => {
|
||||||
|
@ -3,3 +3,4 @@ type SocialResult<T> = Result<T, Box<dyn std::error::Error>>;
|
|||||||
pub mod google;
|
pub mod google;
|
||||||
pub mod facebook;
|
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}
|
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(
|
pub async fn authenticate_with_password(
|
||||||
context: WhiteContext,
|
context: WhiteContext,
|
||||||
req: AuthenticateWithPassword,
|
req: AuthenticateWithPassword,
|
||||||
|
Loading…
Reference in New Issue
Block a user