完善微信登陆信息校验

This commit is contained in:
liyunjia 2024-10-07 14:02:30 +08:00
parent 0d9647535f
commit b413898465
11 changed files with 84 additions and 16 deletions

1
Cargo.lock generated
View File

@ -1116,6 +1116,7 @@ dependencies = [
"chrono", "chrono",
"domain", "domain",
"futures-util", "futures-util",
"hex",
"hex-literal", "hex-literal",
"hmac", "hmac",
"http", "http",

View File

@ -56,4 +56,5 @@ hyper = "1.4.1"
tower = "0.4.13" tower = "0.4.13"
csv = "1.3.0" csv = "1.3.0"
strum = "0.26.3" strum = "0.26.3"
strum_macros = "0.26.3" strum_macros = "0.26.3"
hex = "0.4.3"

View File

@ -2,10 +2,22 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
/// 微信用户登录code通过wx.login获取
#[derive(Debug, Serialize, Deserialize, Validate)] #[derive(Debug, Serialize, Deserialize, Validate)]
pub struct WxMinAppLogin { pub struct WxMinAppLogin {
/// 微信code /// 微信code
#[validate(required(message = "ValidateWxMinAppLoginCodeRequired"), length(min = 1, message = "ValidateWxMinAppLoginCodeRequired"))] #[validate(required(message = "ValidateWxMinAppLoginCodeRequired"), length(min = 1, message = "ValidateWxMinAppLoginCodeRequired"))]
pub code: Option<String>, pub code: Option<String>,
}
/// 微信用户登陆后的信息
///
/// 通过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<String>,
#[validate(required(message = "ValidateWxMinAppLoginSessionRequired"), length(min = 1, message = "ValidateWxMinAppLoginSessionRequired"))]
pub session_key: Option<String>,
pub unionid: Option<String>,
} }

View File

@ -16,4 +16,6 @@ BadRequest,bad request,无效请求
InvalidParams,invalid params,无效参数 InvalidParams,invalid params,无效参数
FailedGetWxAaccessToken,Failed to get WeChat access_token,获取微信access_token失败 FailedGetWxAaccessToken,Failed to get WeChat access_token,获取微信access_token失败
FailedWeChatLogin,Failed to check WeChat login code,微信登录失败 FailedWeChatLogin,Failed to check WeChat login code,微信登录失败
ValidateWxMinAppLoginCodeRequired,login code is required,微信登陆code不能为空 ValidateWxMinAppLoginCodeRequired,login code is required,微信登陆code不能为空
ValidateWxMinAppLoginInfoOpenidRequired,openid is required,open id不能为空
ValidateWxMinAppLoginSessionRequired,session key is required,session key不能为空
1 id en-US zh-CN
16 InvalidParams invalid params 无效参数
17 FailedGetWxAaccessToken Failed to get WeChat access_token 获取微信access_token失败
18 FailedWeChatLogin Failed to check WeChat login code 微信登录失败
19 ValidateWxMinAppLoginCodeRequired login code is required 微信登陆code不能为空
20 ValidateWxMinAppLoginInfoOpenidRequired openid is required open id不能为空
21 ValidateWxMinAppLoginSessionRequired session key is required session key不能为空

View File

@ -42,4 +42,10 @@ pub enum MessageId {
FailedGetWxAaccessToken, FailedGetWxAaccessToken,
/// 微信登录失败校验code失败 /// 微信登录失败校验code失败
FailedWeChatLogin, FailedWeChatLogin,
/// 微信用户的openid不能为空
ValidateWxMinAppLoginInfoOpenidRequired,
/// 微信用户的session key不能为空
ValidateWxMinAppLoginSessionRequired,
} }

View File

@ -35,6 +35,7 @@ tokio-cron-scheduler = { workspace = true }
tower-http = { workspace = true, features = ["trace"] } tower-http = { workspace = true, features = ["trace"] }
tower = { workspace = true } tower = { workspace = true }
hyper = { workspace = true } hyper = { workspace = true }
hex = { workspace = true }
domain = { path = "../domain" } domain = { path = "../domain" }
i18n = { path = "../i18n" } i18n = { path = "../i18n" }

View File

@ -12,6 +12,7 @@ const WHITE_LIST: &[(&str, &str)] = &[
("POST", "/account/google"), ("POST", "/account/google"),
("GET", "/wechat/access_token"), ("GET", "/wechat/access_token"),
("POST", "/wechat/code_2_session"), ("POST", "/wechat/code_2_session"),
("POST", "/wechat/check_session"),
]; ];
/// 认证中间件包括网络请求白名单、token验证、登录缓存 /// 认证中间件包括网络请求白名单、token验证、登录缓存

View File

@ -164,28 +164,35 @@ impl WechatSocial {
/// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html /// https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html
/// todo 使用的hmac签名是否正确签名结果转换成字符串是否正确目前未经验证 /// todo 使用的hmac签名是否正确签名结果转换成字符串是否正确目前未经验证
pub async fn check_session(&self, session_key: &str, open_id: &str) -> SocialResult<()> { 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 token = self.get_access_token().await?;
let mut mac = let mut mac =
Hmac::<Sha256>::new_from_slice(session_key.as_bytes()).expect("初始化hmac失败"); Hmac::<Sha256>::new_from_slice(session_key.as_bytes()).expect("初始化hmac失败");
mac.update(b""); mac.update("".as_bytes());
let out = mac.finalize().into_bytes(); let out = mac.finalize().into_bytes();
let signature = std::str::from_utf8(out.trim_ascii()) let signature = hex::encode(out);
.unwrap_or_default() let url = format!(
.trim_ascii()
.to_string();
let response = reqwest::get(format!(
"https://api.weixin.qq.com/wxa/checksession?access_token={}&signature={}&openid={}&sig_method=hmac_sha256", "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, signature,
open_id open_id
)) );
let response = reqwest::get(url)
.await .await
.unwrap(); .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 { 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!( return Err(Box::new(ResErr::social(format!(
"微信登录校验失败,errcode:{},errmsg:{}", "微信登录信息校验失败,errcode:{},errmsg:{}",
result.errcode, result.errmsg result.errcode, result.errmsg
)))); ))));
} }

View File

@ -18,4 +18,5 @@ pub fn init() -> Router {
// 微信相关路由 // 微信相关路由
.typed_route(social_wx_controller::get_wechat_access_token) .typed_route(social_wx_controller::get_wechat_access_token)
.typed_route(social_wx_controller::code_2_session) .typed_route(social_wx_controller::code_2_session)
.typed_route(social_wx_controller::check_session)
} }

View File

@ -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 library::{context::Context, extractor::body_extractor::JsonBody, model::response::ResResult, social::wechat::{MiniAppLoginResult, WeChatAccessToken}};
use macros::{get, post}; use macros::{get, post};
use crate::service::social_wx_service; use crate::service::social_wx_service;
/// 获取微信access_token
#[get("/wechat/access_token")] #[get("/wechat/access_token")]
pub async fn get_wechat_access_token(context: Context) -> ResResult<WeChatAccessToken> { pub async fn get_wechat_access_token(context: Context) -> ResResult<WeChatAccessToken> {
social_wx_service::get_wechat_access_token(context).await social_wx_service::get_wechat_access_token(context).await
} }
/// 微信用户登录code通过wx.login获取
///
/// todo 登录成功后,判断用户是否存在,不存在,则注册用户,存在,则更新用户信息
#[post("/wechat/code_2_session")] #[post("/wechat/code_2_session")]
pub async fn code_2_session( pub async fn code_2_session(
context: Context, context: Context,
JsonBody(mini_app_login): JsonBody<WxMinAppLogin> JsonBody(mini_app_login): JsonBody<WxMinAppLogin>
) -> ResResult<MiniAppLoginResult> { ) -> ResResult<MiniAppLoginResult> {
social_wx_service::code_2_session(context, mini_app_login.code.unwrap()).await 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<WxLoginInfo>) -> ResResult<()> {
social_wx_service::check_session(context, wx_login_info).await
} }

View File

@ -1,3 +1,4 @@
use domain::dto::social_wx::WxLoginInfo;
use i18n::{message, message_ids::MessageId}; use i18n::{message, message_ids::MessageId};
use library::{ use library::{
context::Context, context::Context,
@ -5,6 +6,7 @@ use library::{
social::wechat::{MiniAppLoginResult, WeChatAccessToken, WECHAT_SOCIAL}, social::wechat::{MiniAppLoginResult, WeChatAccessToken, WECHAT_SOCIAL},
}; };
/// 获取微信access_token
pub async fn get_wechat_access_token(context: Context) -> ResResult<WeChatAccessToken> { pub async fn get_wechat_access_token(context: Context) -> ResResult<WeChatAccessToken> {
let lang_tag = context.get_lang_tag(); let lang_tag = context.get_lang_tag();
let access_token = match WECHAT_SOCIAL.get_access_token().await { let access_token = match WECHAT_SOCIAL.get_access_token().await {
@ -20,6 +22,9 @@ pub async fn get_wechat_access_token(context: Context) -> ResResult<WeChatAccess
Ok(access_token) Ok(access_token)
} }
/// 微信登录code通过wx.login获取
///
/// todo 登录成功后,判断用户是否存在,不存在,则注册用户,存在,则更新用户信息
pub async fn code_2_session(context: Context, code: String) -> ResResult<MiniAppLoginResult> { pub async fn code_2_session(context: Context, code: String) -> ResResult<MiniAppLoginResult> {
let lang_tag = context.get_lang_tag(); let lang_tag = context.get_lang_tag();
let result = match WECHAT_SOCIAL.code_2_session(&code).await { 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<MiniApp
}; };
Ok(result) Ok(result)
} }
/// 微信登录校验
///
/// todo 校验成功后,判断用户是否存在
pub async fn check_session(context: Context, wx_login_info: WxLoginInfo) -> 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
)))
}
}
}