From b413898465846bcf971117090b90efc0cd85e7c3 Mon Sep 17 00:00:00 2001 From: liyunjia Date: Mon, 7 Oct 2024 14:02:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=BE=AE=E4=BF=A1=E7=99=BB?= =?UTF-8?q?=E9=99=86=E4=BF=A1=E6=81=AF=E6=A0=A1=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 3 +- domain/src/dto/social_wx.rs | 14 ++++++++- i18n.csv | 4 ++- i18n/src/message_ids.rs | 6 ++++ library/Cargo.toml | 1 + library/src/middleware/req_ctx.rs | 1 + library/src/social/wechat.rs | 31 ++++++++++++------- server/src/controller/mod.rs | 1 + server/src/controller/social_wx_controller.rs | 14 ++++++++- server/src/service/social_wx_service.rs | 24 ++++++++++++++ 11 files changed, 84 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01d313c..370a093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1116,6 +1116,7 @@ dependencies = [ "chrono", "domain", "futures-util", + "hex", "hex-literal", "hmac", "http", diff --git a/Cargo.toml b/Cargo.toml index 90a2169..62a92d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,4 +56,5 @@ hyper = "1.4.1" tower = "0.4.13" csv = "1.3.0" strum = "0.26.3" -strum_macros = "0.26.3" \ No newline at end of file +strum_macros = "0.26.3" +hex = "0.4.3" \ No newline at end of file diff --git a/domain/src/dto/social_wx.rs b/domain/src/dto/social_wx.rs index 1cc5145..54c718d 100644 --- a/domain/src/dto/social_wx.rs +++ b/domain/src/dto/social_wx.rs @@ -2,10 +2,22 @@ use serde::{Deserialize, Serialize}; use validator::Validate; - +/// 微信用户登录,code通过wx.login获取 #[derive(Debug, Serialize, Deserialize, Validate)] pub struct WxMinAppLogin { /// 微信code #[validate(required(message = "ValidateWxMinAppLoginCodeRequired"), length(min = 1, message = "ValidateWxMinAppLoginCodeRequired"))] pub code: Option, +} + +/// 微信用户登陆后的信息 +/// +/// 通过code登录后获取openid、session_key、unionid +#[derive(Debug, Serialize, Deserialize, Validate)] +pub struct WxLoginInfo { + #[validate(required(message = "ValidateWxMinAppLoginInfoOpenidRequired"), length(min = 1, message = "ValidateWxMinAppLoginInfoOpenidRequired"))] + pub openid: Option, + #[validate(required(message = "ValidateWxMinAppLoginSessionRequired"), length(min = 1, message = "ValidateWxMinAppLoginSessionRequired"))] + pub session_key: Option, + pub unionid: Option, } \ No newline at end of file diff --git a/i18n.csv b/i18n.csv index ac8204f..507e5d6 100644 --- a/i18n.csv +++ b/i18n.csv @@ -16,4 +16,6 @@ BadRequest,bad request,无效请求 InvalidParams,invalid params,无效参数 FailedGetWxAaccessToken,Failed to get WeChat access_token,获取微信access_token失败 FailedWeChatLogin,Failed to check WeChat login code,微信登录失败 -ValidateWxMinAppLoginCodeRequired,login code is required,微信登陆code不能为空 \ No newline at end of file +ValidateWxMinAppLoginCodeRequired,login code is required,微信登陆code不能为空 +ValidateWxMinAppLoginInfoOpenidRequired,openid is required,open id不能为空 +ValidateWxMinAppLoginSessionRequired,session key is required,session key不能为空 \ No newline at end of file diff --git a/i18n/src/message_ids.rs b/i18n/src/message_ids.rs index 23405ba..b36d299 100644 --- a/i18n/src/message_ids.rs +++ b/i18n/src/message_ids.rs @@ -42,4 +42,10 @@ pub enum MessageId { FailedGetWxAaccessToken, /// 微信登录失败,校验code失败 FailedWeChatLogin, + /// 微信用户的openid不能为空 + ValidateWxMinAppLoginInfoOpenidRequired, + /// 微信用户的session key不能为空 + ValidateWxMinAppLoginSessionRequired, + + } diff --git a/library/Cargo.toml b/library/Cargo.toml index 270ad37..356db0e 100644 --- a/library/Cargo.toml +++ b/library/Cargo.toml @@ -35,6 +35,7 @@ tokio-cron-scheduler = { workspace = true } tower-http = { workspace = true, features = ["trace"] } tower = { workspace = true } hyper = { workspace = true } +hex = { workspace = true } domain = { path = "../domain" } i18n = { path = "../i18n" } diff --git a/library/src/middleware/req_ctx.rs b/library/src/middleware/req_ctx.rs index a1f6ddb..cf2949f 100644 --- a/library/src/middleware/req_ctx.rs +++ b/library/src/middleware/req_ctx.rs @@ -12,6 +12,7 @@ const WHITE_LIST: &[(&str, &str)] = &[ ("POST", "/account/google"), ("GET", "/wechat/access_token"), ("POST", "/wechat/code_2_session"), + ("POST", "/wechat/check_session"), ]; /// 认证中间件,包括网络请求白名单、token验证、登录缓存 diff --git a/library/src/social/wechat.rs b/library/src/social/wechat.rs index 71cc039..5d606ef 100644 --- a/library/src/social/wechat.rs +++ b/library/src/social/wechat.rs @@ -164,28 +164,35 @@ impl WechatSocial { /// 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::::new_from_slice(session.as_bytes()).expect(""); + let token = self.get_access_token().await?; let mut mac = Hmac::::new_from_slice(session_key.as_bytes()).expect("初始化hmac失败"); - mac.update(b""); + mac.update("".as_bytes()); 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!( + let signature = hex::encode(out); + let url = format!( "https://api.weixin.qq.com/wxa/checksession?access_token={}&signature={}&openid={}&sig_method=hmac_sha256", - self.access_token.lock().await.access_token, + token.access_token, signature, open_id - )) + ); + let response = reqwest::get(url) .await .unwrap(); - let result: WeChatBaseResult = response.json().await.unwrap(); + let result: WeChatBaseResult = match response.json().await { + Ok(result) => result, + Err(err) => { + tracing::error!("微信登录信息校验失败,err:{:?}", err); + return Err(Box::new(ResErr::social(format!( + "微信登录信息校验失败,err:{}", + err + )))); + } + }; if result.errcode != 0 { - tracing::error!("微信登录校验失败,errcode:{},errmsg:{}", result.errcode, result.errmsg); + tracing::error!("微信登录信息校验失败,errcode:{},errmsg:{}", result.errcode, result.errmsg); return Err(Box::new(ResErr::social(format!( - "微信登录校验失败,errcode:{},errmsg:{}", + "微信登录信息校验失败,errcode:{},errmsg:{}", result.errcode, result.errmsg )))); } diff --git a/server/src/controller/mod.rs b/server/src/controller/mod.rs index cdfb518..d05a18f 100644 --- a/server/src/controller/mod.rs +++ b/server/src/controller/mod.rs @@ -18,4 +18,5 @@ pub fn init() -> Router { // 微信相关路由 .typed_route(social_wx_controller::get_wechat_access_token) .typed_route(social_wx_controller::code_2_session) + .typed_route(social_wx_controller::check_session) } diff --git a/server/src/controller/social_wx_controller.rs b/server/src/controller/social_wx_controller.rs index d0964a0..ff005b5 100644 --- a/server/src/controller/social_wx_controller.rs +++ b/server/src/controller/social_wx_controller.rs @@ -1,18 +1,30 @@ -use domain::dto::social_wx::WxMinAppLogin; +use domain::dto::social_wx::{WxMinAppLogin, WxLoginInfo}; use library::{context::Context, extractor::body_extractor::JsonBody, model::response::ResResult, social::wechat::{MiniAppLoginResult, WeChatAccessToken}}; use macros::{get, post}; use crate::service::social_wx_service; +/// 获取微信access_token #[get("/wechat/access_token")] pub async fn get_wechat_access_token(context: Context) -> ResResult { social_wx_service::get_wechat_access_token(context).await } +/// 微信用户登录,code通过wx.login获取 +/// +/// todo 登录成功后,判断用户是否存在,不存在,则注册用户,存在,则更新用户信息 #[post("/wechat/code_2_session")] pub async fn code_2_session( context: Context, JsonBody(mini_app_login): JsonBody ) -> ResResult { social_wx_service::code_2_session(context, mini_app_login.code.unwrap()).await +} + +/// 微信用户登录校验 +#[post("/wechat/check_session")] +pub async fn check_session( + context: Context, + JsonBody(wx_login_info): JsonBody) -> ResResult<()> { + social_wx_service::check_session(context, wx_login_info).await } \ No newline at end of file diff --git a/server/src/service/social_wx_service.rs b/server/src/service/social_wx_service.rs index 211d917..01b94f7 100644 --- a/server/src/service/social_wx_service.rs +++ b/server/src/service/social_wx_service.rs @@ -1,3 +1,4 @@ +use domain::dto::social_wx::WxLoginInfo; use i18n::{message, message_ids::MessageId}; use library::{ context::Context, @@ -5,6 +6,7 @@ use library::{ social::wechat::{MiniAppLoginResult, WeChatAccessToken, WECHAT_SOCIAL}, }; +/// 获取微信access_token pub async fn get_wechat_access_token(context: Context) -> ResResult { let lang_tag = context.get_lang_tag(); let access_token = match WECHAT_SOCIAL.get_access_token().await { @@ -20,6 +22,9 @@ pub async fn get_wechat_access_token(context: Context) -> ResResult ResResult { let lang_tag = context.get_lang_tag(); let result = match WECHAT_SOCIAL.code_2_session(&code).await { @@ -34,3 +39,22 @@ pub async fn code_2_session(context: Context, code: String) -> ResResult ResResult<()> { + let lang_tag = context.get_lang_tag(); + let session_key = wx_login_info.session_key.unwrap(); + let open_id = wx_login_info.openid.unwrap(); + match WECHAT_SOCIAL.check_session(&session_key, &open_id).await { + Ok(_) => Ok(()), + Err(err) => { + tracing::error!("微信登录校验失败,err:{}", err); + Err(ResErr::service(message!( + lang_tag, + MessageId::FailedWeChatLogin + ))) + } + } +} \ No newline at end of file