在SENPARC底层,提供了微信用户授权验证的方式:
在下图位置:
namespace Senparc.Weixin.MP.Sample.Filters { /// <summary> /// OAuth自动验证,可以加在Action或整个Controller上 /// </summary> public class CustomOAuthAttribute : SenparcOAuthAttribute { public CustomOAuthAttribute(string appId, string oauthCallbackUrl) : base(appId, oauthCallbackUrl) { base._appId = base._appId ?? System.Configuration.ConfigurationManager.AppSettings["TenPayV3_AppId"]; } public override bool IsLogined(HttpContextBase httpContext) { return httpContext != null && httpContext.Session["OpenId"] != null; //也可以使用其他方法如Session验证用户登录 //return httpContext != null && httpContext.User.Identity.IsAuthenticated; } } }
在我近期的项目中进行了一些扩展:
//----------------------------------------------------------------------- // <copyright file="CustomOAuthAttribute" company="FenSiShengHuo, Ltd."> // Copyright (c) 2018 , All rights reserved. // </copyright> //----------------------------------------------------------------------- using System.Web; using Senparc.Weixin.MP.MvcExtension; using System.Web.Configuration; using DotNet.MVC.Infrastructure.Utilities; using System; using DotNet.WeChat.CommonService; namespace DotNet.WeChat.MVC.Filters { /// <summary> /// CustomOAuthAttribute /// /// OAuth自动验证,可以加在Action或整个Controller上 /// /// 修改纪录 /// /// 2018-03-29 版本:1.0 JiShiYu 创建文件。 /// /// <author> /// <name>JiShiYu</name> /// <date>2018-03-29</date> /// </author> /// </summary> public class CustomWechatMPOAuthAttribute : SenparcOAuthAttribute { /// <summary> /// OAuth自动验证,可以加在Action或整个Controller上 /// </summary> /// <param name="appId"></param> /// <param name="componentAppId"></param> /// <param name="oauthCallbackUrl"></param> public CustomWechatMPOAuthAttribute(string appId = null, string componentAppId = null, string oauthCallbackUrl = null) : base(appId, oauthCallbackUrl) { base._appId = base._appId ?? WebConfigurationManager.AppSettings["WeixinAppId"]; base._oauthCallbackUrl = base._oauthCallbackUrl ?? "/Admin/WeChatMP/OAuthCallback"; } public override bool IsLogined(HttpContextBase httpContext) { if (httpContext.Request.Url.Host.IndexOf("fensishenghuo.com", StringComparison.OrdinalIgnoreCase) >= 0) { //return httpContext != null && httpContext.Session[GlobalSetting.WechatOpenIdSessionKey] != null; if (httpContext != null) { if (httpContext.Session[GlobalSetting.WechatOpenIdSessionKey] != null) { WechatUserInfo wechatUserInfo = httpContext.Session[GlobalSetting.WechatOpenIdSessionKey] as WechatUserInfo; if (wechatUserInfo != null && wechatUserInfo.MPUserInfo != null) { return true; } } } } return false; //也可以使用其他方法如Session验证用户登录 //return httpContext != null && httpContext.User.Identity.IsAuthenticated; } } }
目前要基于开放平台进行开发,也需要获取用户的信息,开放平台获取用户返回的信息跟公众号的不一样,分别如下:
公众号的用户: Senparc.Weixin.MP.AdvancedAPIs.User.UserInfoJson.cs,我这里增加了 [Serializable]
/*---------------------------------------------------------------- Copyright (C) 2018 Senparc 文件名:UserInfoJson.cs 文件功能描述:获取用户信息返回结果 创建标识:Senparc - 20150211 修改标识:Senparc - 20150303 修改描述:整理接口 修改标识:jsionr - 20150321 修改描述:添加remark属性 修改标识:Senparc - 20150321 修改描述:添加unionid属性 修改标识:jsionr - 20150513 修改描述:增加个人分组信息 修改标识:Senparc - 20150526 修改描述:修改备注 修改标识:Senparc - 20150727 修改描述:添加批量获取用户基本信息返回结果 修改标识:Senparc - 20150727 修改描述:添加批量获取用户基本信息返回结果 ----------------------------------------------------------------*/ using System; using System.Collections.Generic; using Senparc.Weixin.Entities; namespace Senparc.Weixin.MP.AdvancedAPIs.User { /// <summary> /// 高级接口获取的用户信息 /// </summary> [Serializable] public class UserInfoJson : WxJsonResult { /// <summary> /// 用户是否订阅该公众号标识,值为0时,代表此用户没有关注该公众号,拉取不到其余信息。 /// </summary> public int subscribe { get; set; } /// <summary> /// 用户的标识,对当前公众号唯一 /// </summary> public string openid { get; set; } /// <summary> /// 用户的昵称 /// </summary> public string nickname { get; set; } /// <summary> /// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 /// </summary> public int sex { get; set; } /// <summary> ///用户的语言,简体中文为zh_CN /// </summary> public string language { get; set; } /// <summary> /// 用户所在城市 /// </summary> public string city { get; set; } /// <summary> /// 用户所在省份 /// </summary> public string province { get; set; } /// <summary> /// 用户所在国家 /// </summary> public string country { get; set; } /// <summary> /// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。 /// </summary> public string headimgurl { get; set; } /// <summary> /// 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间 /// </summary> public long subscribe_time { get; set; } /// <summary> /// 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。 /// </summary> public string unionid { get; set; } /// <summary> /// 公众号运营者对粉丝的备注,公众号运营者可在微信公众平台用户管理界面对粉丝添加备注 /// </summary> public string remark { get; set; } /// <summary> /// 用户所在的分组ID(兼容旧的用户分组接口) /// </summary> public int groupid { get; set; } /// <summary> /// 用户标签 /// </summary> public int[] tagid_list { get; set; } /// <summary> /// 返回用户关注的渠道来源,ADD_SCENE_SEARCH 公众号搜索,ADD_SCENE_ACCOUNT_MIGRATION 公众号迁移,ADD_SCENE_PROFILE_CARD 名片分享,ADD_SCENE_QR_CODE 扫描二维码,ADD_SCENEPROFILE LINK 图文页内名称点击,ADD_SCENE_PROFILE_ITEM 图文页右上角菜单,ADD_SCENE_PAID 支付后关注,ADD_SCENE_OTHERS 其他 /// </summary> public string subscribe_scene { get; set; } /// <summary> /// 二维码扫码场景(开发者自定义) /// </summary> public int qr_scene { get; set; } /// <summary> /// 二维码扫码场景描述(开发者自定义) /// </summary> public string qr_scene_str { get; set; } } /// <summary> /// 批量获取用户基本信息返回结果 /// </summary> public class BatchGetUserInfoJsonResult : WxJsonResult { public List<UserInfoJson> user_info_list { get; set; } } }
开放平台的用户:Senparc.Weixin.Open.OAuthAPIs.OAuthUserInfo.cs 增加了 [Serializable],比公众号少了不少属性字段
/*---------------------------------------------------------------- Copyright (C) 2018 Senparc 文件名:OAuthUserInfo.cs 文件功能描述:通过OAuth的获取到的用户信息 创建标识:Senparc - 20150712 ----------------------------------------------------------------*/ using System; namespace Senparc.Weixin.Open.OAuthAPIs { /// <summary> /// 通过OAuth的获取到的用户信息(snsapi_userinfo=scope) /// </summary> [Serializable] public class OAuthUserInfo { public string openid { get; set; } public string nickname { get; set; } /// <summary> /// 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 /// </summary> public int sex { get; set; } public string province { get; set; } public string city { get; set; } public string country { get; set; } /// <summary> /// 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空 /// </summary> public string headimgurl { get; set; } /// <summary> /// 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) /// 作者注:其实这个格式称不上JSON,只是个单纯数组。 /// </summary> public string[] privilege { get; set; } /// <summary> /// 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN /// </summary> public string unionid { get; set; } } }
我想在开放平台也能获取到公众号一样的用户信息,进行了如下改造 CustomWechatOpenOAuthAttribute
//----------------------------------------------------------------------- // <copyright file="CustomOAuthAttribute.cs" company="FenSiShengHuo, Ltd."> // Copyright (c) 2018 , All rights reserved. // </copyright> //----------------------------------------------------------------------- using System; using System.Web; using System.Web.Configuration; using System.Web.Mvc; namespace DotNet.WeChat.MVC.Filters { using DotNet.MVC.Infrastructure.Utilities; using DotNet.Utilities; using DotNet.WeChat.CommonService; using Senparc.Weixin.MP.MvcExtension; /// <summary> /// CustomOAuthAttribute /// 参考 Senparc.Weixin.MP.MvcExtension.SenparcOAuthAttribute 进行了重写 /// /// OAuth自动验证,可以加在Action或整个Controller上 /// /// 修改纪录 /// /// 2018-03-29 版本:1.0 JiShiYu 创建文件。 /// /// <author> /// <name>JiShiYu</name> /// <date>2018-03-29</date> /// </author> /// </summary> public class CustomWechatOpenOAuthAttribute : SenparcOAuthAttribute { /// <summary> /// OAuth自动验证,可以加在Action或整个Controller上 /// </summary> /// <param name="appId"></param> /// <param name="oauthCallbackUrl"></param> public CustomWechatOpenOAuthAttribute(string appId = null, string oauthCallbackUrl = null) : base(appId, oauthCallbackUrl) { base._appId = base._appId ?? WebConfigurationManager.AppSettings["WeixinAppId"]; base._oauthCallbackUrl = base._oauthCallbackUrl ?? "/Admin/WechatOpen/OAuthOpenCallback"; } public override bool IsLogined(HttpContextBase httpContext) { if (httpContext.Request.Url.Host.IndexOf("fensishenghuo.com", StringComparison.OrdinalIgnoreCase) >= 0) { // return httpContext != null && httpContext.Session[GlobalSetting.WechatOpenIdSessionKey] != null; if (httpContext != null) { if (httpContext.Session[GlobalSetting.WechatOpenIdSessionKey] != null) { WechatUserInfo wechatUserInfo = httpContext.Session[GlobalSetting.WechatOpenIdSessionKey] as WechatUserInfo; if (wechatUserInfo != null && wechatUserInfo.OpenUserInfo != null) { return true; } } } } return false; //也可以使用其他方法如Session验证用户登录 //return httpContext != null && httpContext.User.Identity.IsAuthenticated; } #if NET35 || NET40 || NET45 || NET461|| NET462 private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus) { validationStatus = OnCacheAuthorization(new HttpContextWrapper(context)); } #endif #if NET35 || NET40 || NET45 || NET461|| NET462 public override void OnAuthorization(AuthorizationContext filterContext) #else public override void OnAuthorization(AuthorizationFilterContext filterContext) #endif { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } if (AuthorizeCore(filterContext.HttpContext)) { // ** IMPORTANT ** // Since we‘re performing authorization at the action level, the authorization code runs // after the output caching module. In the worst case this could allow an authorized user // to cause the page to be cached, then an unauthorized user would later be served the // cached page. We work around this by telling proxies not to cache the sensitive page, // then we hook our custom authorization code into the caching mechanism so that we have // the final say on whether a page should be served from the cache. #if NET35 || NET40 || NET45 || NET461|| NET462 HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache; cachePolicy.SetProxyMaxAge(new TimeSpan(0)); cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */); #endif } else { if (IsLogined(filterContext.HttpContext)) { } else { var callbackUrl = Senparc.Weixin.HttpUtility.UrlUtility.GenerateOAuthCallbackUrl(filterContext.HttpContext, _oauthCallbackUrl); var state = string.Format("{0}|{1}", "FromSenparc", DateTime.Now.Ticks); //var url = Senparc.Weixin.MP.AdvancedAPIs.OAuthApi.GetAuthorizeUrl(_appId, callbackUrl, state, _oauthScope); string componentAppId = WebConfigurationManager.AppSettings["Component_Appid"]; var url = Senparc.Weixin.Open.OAuthAPIs.OAuthApi.GetAuthorizeUrl(_appId, componentAppId, callbackUrl, state, new[] { Senparc.Weixin.Open.OAuthScope.snsapi_userinfo, Senparc.Weixin.Open.OAuthScope.snsapi_base }); NLogHelper.Info("url=" + url); filterContext.Result = new RedirectResult(url); } } } #if NET35 || NET40 || NET45 || NET461|| NET462 // This method must be thread-safe since it is called by the caching module. protected override HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("httpContext"); } bool isAuthorized = AuthorizeCore(httpContext); return (isAuthorized) ? HttpValidationStatus.Valid : HttpValidationStatus.IgnoreThisRequest; } #endif } }
其中WechatUserInfo是我扩展的一个类:
//----------------------------------------------------------------------- // <copyright file="WechatUserInfo" company="FenSiShengHuo, Ltd."> // Copyright (c) 2018 , All rights reserved. // </copyright> //----------------------------------------------------------------------- using DotNet.Utilities; using Senparc.Weixin.MP.AdvancedAPIs.User; using Senparc.Weixin.Open.OAuthAPIs; using System; namespace DotNet.WeChat.CommonService { /// <summary> /// WechatUserInfo /// /// 微信用户 /// /// 修改纪录 /// /// 2018-07-11 版本:1.0 JiShiYu 创建文件。 /// /// <author> /// <name>JiShiYu</name> /// <date>2018-07-11</date> /// </author> /// </summary> [Serializable] public class WechatUserInfo { /// <summary> /// 公众号的用户信息 /// </summary> public UserInfoJson MPUserInfo { set; get; } /// <summary> /// 开放平台的用户信息 /// </summary> public OAuthUserInfo OpenUserInfo { set; get; } /// <summary> /// 系统的用户信息 /// </summary> public BaseUserInfo UserInfoBase { set; get; } } }
/Admin/WechatOpen/OAuthOpenCallback 开放平台回调方法处理:
/// <summary> /// 开放平台 网页授权回调 CustomWechatOpenOAuthAttribute 中使用 /// 这个搞定,就可以不依赖主公众号的回调地址配置了 /// </summary> /// <param name="code"></param> /// <param name="state"></param> /// <param name="appId"></param> /// <param name="returnUrl"></param> /// <returns></returns> public ActionResult OAuthOpenCallback(string code, string state, string appId, string returnUrl) { if (string.IsNullOrWhiteSpace(code)) { return Content("您拒绝了授权!"); } if (!state.Contains("|")) { //这里的state其实是会暴露给客户端的,验证能力很弱,这里只是演示一下 //实际上可以存任何想传递的数据,比如用户ID return Content("验证失败!请从正规途径进入!1001"); } Senparc.Weixin.Open.OAuthAPIs.OAuthAccessTokenResult result = null; //通过,用code换取access_token try { var componentAccessToken = ComponentContainer.GetComponentAccessToken(componentAppId); result = Senparc.Weixin.Open.OAuthAPIs.OAuthApi.GetAccessToken(appId, componentAppId, componentAccessToken, code); } catch (Exception ex) { return Content(ex.Message); } if (result.errcode != Senparc.Weixin.ReturnCode.请求成功) { return Content("错误:" + result.errmsg); } //下面2个数据也可以自己封装成一个类,储存在数据库中(建议结合缓存) //如果可以确保安全,可以将access_token存入用户的cookie中,每一个人的access_token是不一样的 Session["OAuthAccessTokenStartTime"] = DateTime.Now; Session["OAuthAccessToken"] = result; //因为第一步选择的是OAuthScope.snsapi_userinfo,这里可以进一步获取用户详细信息 try { // 开放平台的用户信息 Senparc.Weixin.Open.OAuthAPIs.OAuthUserInfo openUserInfo = Senparc.Weixin.Open.OAuthAPIs.OAuthApi.GetUserInfo(result.access_token, result.openid); // 公众号的用户信息 //UserInfoJson mpUserInfo = UserApi.Info(result.access_token, result.openid); UserInfoJson mpUserInfo = UserApi.Info(appId, result.openid); // 实现将该用户存储起来 // return View(userInfo); // 改为存储UserInfoJson对象 WechatUserInfo wechatUserInfo = new WechatUserInfo(); wechatUserInfo.OpenUserInfo = openUserInfo; wechatUserInfo.MPUserInfo = mpUserInfo; NLogHelper.Info("OAuthOpenCallback,WechatUserInfo=" + wechatUserInfo.FastToJson() + ",returnUrl=" + returnUrl); // 改为存储UserInfoJson对象 Session[GlobalSetting.WechatOpenIdSessionKey] = wechatUserInfo; } catch (ErrorJsonResultException ex) { return Content(ex.Message); } return Redirect(returnUrl); }
大家对这个在开放平台获取用户授权方式有什么建议呢?