微信小程序代码:
//页面就一个按钮
//官方文档是
<!-- <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">获取手机号</button> -->
//但是我用官方文档有问题抛异常 所以改为下面代码 具体原因自行百度
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号</button>
//js中有两个方法
wx.login({ //请求得到code
success: function(res) {
console.log("code=====" + res.code)
if (res.code) {
wx.request({
url: `http://baidu.com?code=${res.code}`,
method: "GET",
success: function(res) {}
})
}
},
})
//这个方法主要用于 请求一次后端生成出session_key 后续的解密需要这个session_key
//这个方法是刚刚按钮的点击事件触发的方法用户获取用户的手机号进行登录
//注:这个获取到的e.detail.iv..这些都是加密的需要请求一下后端进行解密才能看到明文的手机号
//这边有一个坑 就是后端偶尔会报错 Padding is invalid and cannot be removed
//这个问题我没有解决(在现在这个dome中没有解决)可以自行百度(我直接扔给前端了让他搞)
getPhoneNumber(e) {
console.log(e)
var that = this;
if (e.detail.errMsg == 'getPhoneNumber:ok') {
wx.showLoading({
title: "登录中"
})
wx.checkSession({
success: (res) => {
wx.request({
url: "http://baidu.com", //通过iv data key拿到手机号并解密
data: {
iv: e.detail.iv,
encryptedData: e.detail.encryptedData,
session_key: '',
},
method: "POST",
success: function(res) {
//关闭加载效果
wx.hideLoading({
success: (
res
) => {},
})
}
});
},
})
}
},
C#代码:
首先配置注入
startup中
// 添加WeChat单例服务
services.AddSingleton<WeChatService>(new WeChatService(this.Configuration["wxAppID"], this.Configuration["wxAppSecret"]));
//wxAppID 和 wxAppSecret 是小程序中的appid和AppSecret 不知道在哪的 百度
wx.login调用的方法(_weChat.GetCode2Session这个类我会在后面把整个代码贴出来)
[Route("wxChatGetCode")]
[HttpGet]
public async Task<IActionResult> wxChatGetCode(string code)
{
var res = await _weChat.GetCode2Session(new WeChatService.Code2SessionParamter(code));
session_key = res.session_key;
res.session_key = "";
// 注意: 这里是为了方便演示,开发的时候不应该去传给前端。
// 为了数据不被篡改,开发者不应该把 session_key 传到小程序客户端等服务器外的环境。
return new JsonResult(res);
}
getPhoneNumber调用的方法
/// <summary>
/// 获取手机号 微信一键登录
/// </summary>
/// <param name="parameter"></param>
/// <returns></returns>
[Route("wxChatPost")]
[HttpPost]
public async Task<string> wxChatPost(LoginParameterDto parameter)
{
if (!string.IsNullOrEmpty(parameter.code)) await wxChatGetCode(parameter.code);
UserInfo? userInfo = _weChat.GetUserInfo(parameter.iv, parameter.encryptedData, session_key);
if (userInfo == null)
{
//登录验证
}
}
_weChat类
public class WeChatService
{
/// <summary>
/// 小程序 appId
/// </summary>
public string appid { get; set; }
/// <summary>
/// 小程序 appSecret
/// </summary>
public string secret { get; set; }
public class Code2SessionParamter
{
/// <summary>
/// 小程序 appId
/// </summary>
public string appid { get; set; }
/// <summary>
/// 小程序 appSecret
/// </summary>
public string secret { get; set; }
/// <summary>
/// 登录时获取的 code
/// </summary>
public string js_code { get; set; }
/// <summary>
/// 授权类型,此处只需填写 authorization_code
/// </summary>
public string grant_type { get; set; } = "authorization_code";
public Code2SessionParamter(string js_code)
{
this.js_code = js_code;
}
public Code2SessionParamter(string appid, string secret, string js_code)
{
this.appid = appid;
this.secret = secret;
this.js_code = js_code;
}
}
public class Code2SessionResult
{
/// <summary>
/// 用户唯一标识
/// </summary>
public string openid { get; set; }
/// <summary>
/// 会话密钥
/// </summary>
public string session_key { get; set; }
/// <summary>
/// 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。
/// </summary>
public string unionid { get; set; }
/// <summary>
/// 错误码
/// </summary>
public int errcode { get; set; }
/// <summary>
/// 错误信息
/// </summary>
public string errmsg { get; set; }
}
public enum Gender
{
unkown = 0,
man = 1,
woman = 2
}
public class UserInfo
{
/// <summary>
/// 手机号
/// </summary>
public string phoneNumber { get; set; }
}
public class Watermark
{
/// <summary>
/// 敏感数据归属 appId,开发者可校验此参数与自身 appId 是否一致
/// </summary>
public string appid { get; set; }
/// <summary>
/// 敏感数据获取的时间戳, 开发者可以用于数据时效性校验
/// </summary>
public string timestamp { get; set; }
}
public WeChatService(string appid, string secret)
{
this.appid = appid;
this.secret = secret;
}
/// <summary>
/// 登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见 小程序登录。
/// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
/// </summary>
/// <param name="code2SessionParamter">参数</param>
/// <returns></returns>
public async Task<Code2SessionResult?> GetCode2Session(Code2SessionParamter code2SessionParamter)
{
if (string.IsNullOrEmpty(code2SessionParamter.appid)) code2SessionParamter.appid = appid;
if (string.IsNullOrEmpty(code2SessionParamter.secret)) code2SessionParamter.secret = secret;
var result = await Get($"https://api.weixin.qq.com/sns/jscode2session?appid={code2SessionParamter.appid}&secret={code2SessionParamter.secret}&js_code={code2SessionParamter.js_code}&grant_type={code2SessionParamter.grant_type}");
return JsonConvert.DeserializeObject<Code2SessionResult>(result);
}
/// <summary>
/// 内部使用的通用方法
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
static async Task<string> Get(string url)
{
try
{
var httpClient = new HttpClient();
HttpResponseMessage response = await httpClient.GetAsync(url);
return response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : ""; ;
}
catch (Exception ex)
{
throw new Exception("Get 请求出错:" + ex.Message);
}
}
/// <summary>
/// 解密获取用户信息 (不验证签名)
/// </summary>
/// <param name="iv">加密算法的初始向量</param>
/// <param name="encryptedData">包括敏感数据在内的完整用户信息的加密数据</param>
/// <param name="session_key">会话密钥</param>
/// <returns></returns>
public UserInfo? GetUserInfo(string iv, string encryptedData, string session_key)
{
return JsonConvert.DeserializeObject<UserInfo>(AESDecrypt(encryptedData, session_key, iv));
}
/// <summary>
/// 解密获取用户信息 (验证签名)
/// </summary>
/// <param name="iv">加密算法的初始向量</param>
/// <param name="encryptedData">包括敏感数据在内的完整用户信息的加密数据</param>
/// <param name="session_key">会话密钥</param>
/// <param name="rawData">不包括敏感信息的原始数据字符串,用于计算签名</param>
/// <param name="signature">使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息</param>
/// <returns></returns>
public UserInfo? GetUserInfo(string iv, string encryptedData, string session_key, string rawData, string signature)
{
CheckSignature(rawData, session_key, signature);
return GetUserInfo(iv, encryptedData, session_key);
}
/// <summary>
/// 检查签名
/// </summary>
/// <param name="rawData">不包括敏感信息的原始数据字符串,用于计算签名</param>
/// <param name="session_key"></param>
/// <param name="signature">使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息</param>
/// <exception cref="Exception"></exception>
void CheckSignature(string rawData, string session_key, string signature)
{
Console.WriteLine(SHA1Encryption(rawData + session_key));
Console.WriteLine(signature);
if (SHA1Encryption(rawData + session_key).ToUpper() != signature.ToUpper())
{
throw new Exception("CheckSignature 签名校验失败,数据可能损坏。");
}
}
/// <summary>
/// SHA1 加密,返回大写字符串
/// </summary>
/// <param name="content">需要加密字符串</param>
/// <param name="encode">指定加密编码</param>
/// <returns>返回40位大写字符串</returns>
string SHA1Encryption(string content, Encoding encode = null)
{
try
{
if (encode == null) encode = Encoding.UTF8;
SHA1 sha1 = SHA1.Create();
byte[] bytes_in = encode.GetBytes(content);
byte[] bytes_out = sha1.ComputeHash(bytes_in);
sha1.Dispose();
string result = BitConverter.ToString(bytes_out);
result = result.Replace("-", "");
return result;
}
catch (Exception ex)
{
throw new Exception("SHA1Encryption加密出错:" + ex.Message);
}
}
/// <summary>
/// Aes 解密
/// </summary>
/// <param name="encryptedData"></param>
/// <param name="sessionKey"></param>
/// <param name="iv"></param>
/// <returns></returns>
string AESDecrypt(string encryptedData, string sessionKey, string iv)
{
try
{
var encryptedDataByte = Convert.FromBase64String(encryptedData);
var aes = Aes.Create();
aes.Key = Convert.FromBase64String(sessionKey);
aes.IV = Convert.FromBase64String(iv);
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var transform = aes.CreateDecryptor();
var plainText = transform.TransformFinalBlock(encryptedDataByte, 0, encryptedDataByte.Length);
var result = Encoding.Default.GetString(plainText);
return result;
}
catch (Exception ex)
{
throw new Exception("AESDecrypt解密出错:" + ex.Message);
}
}
}