コード例 #1
0
        /// <summary>
        ///微信网页授权回调
        ///如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
        ///若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE
        ///http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html
        /// </summary>
        /// <returns></returns>
        public ActionResult OAuthCallback()
        {
            _log.Write("微信网页授权接口发起回调", HttpContext.Request.Url.ToString(), TraceEventType.Verbose);

            string code  = HttpContext.Request.QueryString["code"];
            string state = HttpContext.Request.QueryString["state"];

            //完成网页鉴权后要转回的页面地址
            string redirectUrl = null;

            if (String.IsNullOrEmpty(state) == false)
            {
                redirectUrl = Server.UrlDecode(state);
            }
            else
            {
                _log.Write("没有指定完成网页鉴权后要转回的页面地址: state", TraceEventType.Warning);
                return(new HttpStatusCodeResult(404));
            }

            string domainId = HttpContext.Request.QueryString["domainId"];

            if (String.IsNullOrEmpty(domainId))
            {
                _log.Write("没有指定 domainId ", TraceEventType.Warning);
                return(new HttpStatusCodeResult(404));
            }

            DomainContext domainContext = _domainPool.GetDomainContext(Guid.Parse(domainId));

            if (domainContext == null)
            {
                _log.Write("指定的 domainId 不存在", TraceEventType.Warning);
                return(new HttpStatusCodeResult(404));
            }

            if (String.IsNullOrEmpty(code))
            {
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td1")));
            }

            //domainContext.AppSecret
            RequestApiResult <WeixinWebAccessTokenResult> getWebAccessToken =
                TokenApi.GetWebAccessToken(domainContext.AppId, "domainContext.AppSecret", code);

            if (getWebAccessToken.Success == false)
            {
                _log.Write("请求网页AccessToken失败。", getWebAccessToken.Message, TraceEventType.Warning);
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td2")));
            }

            //此处拿到OpenId了,接下来判断该用户是否是已关注用户
            RequestApiResult <WeixinUser> getUserInfoResult =
                UserApiWrapper.GetUserInfo(domainContext, getWebAccessToken.ApiResult.OpenId);

            if (getUserInfoResult.Success == false)
            {
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td3")));
            }

            //值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
            //跳转到引导关注页面
            if (getUserInfoResult.ApiResult.Subscribe == 0)
            {
                //用户取消关注有消息推送
                //在那时设置member中是否在关注为false
                //重定向到提示关注页面
                return(new RedirectResult("~/Home/GuideSubscribe/" + domainContext.Domain.Id));
            }

            //根据OpenId获取用户信息
            MemberEntity member = _memberManager.GetMemberByOpenId(domainContext.Domain.Id, getUserInfoResult.ApiResult.OpenId);

            if (member == null)
            {
                //添加新用户
                member = _memberManager.AddMember(domainContext, getUserInfoResult.ApiResult);
            }
            else
            {
                //更新当前用户信息
                _memberManager.UpdateMember(member, getUserInfoResult.ApiResult);
            }

            MemberContext memberContext = new MemberContext(member);

            SessionContainer.SetMemberContext(HttpContext, memberContext);

            //转回初始业务页面
            return(new RedirectResult(redirectUrl));
        }
        /// <summary>
        ///微信网页授权回调
        ///如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。
        ///若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE
        ///http://mp.weixin.qq.com/wiki/9/01f711493b5a02f24b04365ac5d8fd95.html
        /// </summary>
        /// <returns></returns>
        public ActionResult OAuthCallback()
        {
            _log.Write("微信网页授权接口发起回调", HttpContext.Request.Url.ToString(), TraceEventType.Verbose);

            string code  = HttpContext.Request.QueryString["code"];
            string state = HttpContext.Request.QueryString["state"];

            //第为第三方平台运营时,会返回 appId
            string appId = HttpContext.Request.QueryString["appid"];

            //完成网页鉴权后要转回的页面地址
            string redirectUrl = null;

            if (String.IsNullOrEmpty(state) == false)
            {
                redirectUrl = _cachingService.Get(state);
            }
            else
            {
                _log.Write("微信网页授权接口发起回调",
                           "没有指定完成网页鉴权后要转回的页面地址: state" + HttpContext.Request.Url.ToString(),
                           TraceEventType.Warning);
                return(new HttpStatusCodeResult(404));
            }

            string domainId = HttpContext.Request.QueryString["domainId"];

            if (String.IsNullOrEmpty(domainId))
            {
                _log.Write("微信网页授权接口发起回调", "没有指定 domainId ", TraceEventType.Warning);
                return(new HttpStatusCodeResult(404));
            }

            DomainContext domainContext = _domainPool.GetDomainContext(Guid.Parse(domainId));

            if (domainContext == null)
            {
                _log.Write("微信网页授权接口发起回调", "指定的 domainId 不存在", TraceEventType.Warning);
                return(new HttpStatusCodeResult(404));
            }

            if (domainContext.Authorizer == null)
            {
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td5")));
            }

            //只有微信认证服务号具备此权限
            //用户管理-网页授权获取用户openid/用户基本信息
            if (domainContext.Authorizer.AuthorizationType != EnumAuthorizationType.AuthorizedService)
            {
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td4")));
            }

            if (String.IsNullOrEmpty(code))
            {
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td1")));
            }

            /*
             * 此处偶发 40029 错误,不合法的oauth_code
             * 40029-invalid code, hints: [ req_id: Ft6quA0644ns67 ]
             * https://segmentfault.com/q/1010000002739502?foxhandler=RssReadRenderProcessHandler
             * http://mp.weixin.qq.com/qa/11/3c20059cc944d6edf4a1124c2fd09253.html
             * redirect_uri后面加个随机数没用
             * 通过尝试发起两次请求的方式解决
             */
            //通过code换取网页access_token
            RequestApiResult <WeixinWebAccessTokenResult> getWebAccessToken =
                TokenApi.GetThirdPartyWebAccessToken(domainContext.AppId, code, _configuration.ThirdParty.AppId,
                                                     ThirdPartyAccessTokenGetter.Get());

            if (getWebAccessToken.Success == false)
            {
                //再来一次,防止死循环,只重试一次,在URL后面加个参数以标记
                if (redirectUrl.Contains("RetryGetWebAccessToken"))
                {
                    _log.Write("请求网页AccessToken失败。", getWebAccessToken.Message, TraceEventType.Warning);
                    //重定向到错误页面
                    return(new RedirectResult(String.Format(
                                                  "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td2")));
                }
                else
                {
                    if (redirectUrl.IndexOf('?') >= 0)
                    {
                        redirectUrl += "&RetryGetWebAccessToken=1";
                    }
                    else
                    {
                        redirectUrl += "?RetryGetWebAccessToken=1";
                    }

                    return(new RedirectResult(redirectUrl));
                }
            }

            //将OpenId保存到Session
            SessionContainer.SetOpenId(HttpContext, getWebAccessToken.ApiResult.OpenId);

            //先判断本地数据库中是否已经有了此用户信息
            MemberContext memberContext = null;
            MemberEntity  member        = _memberManager.GetMemberByOpenId(domainContext.Domain.Id, domainContext.AppId, getWebAccessToken.ApiResult.OpenId);

            if (member != null)
            {
                if (member.Attention)
                {
                    //为提高鉴权性能,此处不更新用户信息,只作标记,用windows服务后台更新
                    _memberManager.NeedUpdate(member.Id, true);

                    memberContext = new MemberContext(member);
                    SessionContainer.SetMemberContext(HttpContext, memberContext);

                    //转回初始业务页面
                    return(new RedirectResult(redirectUrl));
                }
                else
                {
                    //如果用户已经取消关注,此处就不需要再调用微信的 GetUserInfo 接口了
                    //直接判断能不能匿名浏览即可
                    return(RedirectUrlOnlyOpenId(redirectUrl, domainId));
                }
            }

            //在本地没有用户信息的情况下,调用weixinApi去取
            //此处拿到OpenId了,接下来判断该用户是否是已关注用户
            RequestApiResult <WeixinUser> getUserInfoResult =
                UserApiWrapper.GetUserInfo(domainContext, getWebAccessToken.ApiResult.OpenId);

            if (getUserInfoResult.Success == false)
            {
                //重定向到错误页面
                return(new RedirectResult(String.Format(
                                              "~/Home/ErrorView/{0}?message={1}", domainContext.Domain.Id, "td3")));
            }

            //值为0时,代表此用户没有关注该公众号,拉取不到其余信息。
            //跳转到引导关注页面
            if (getUserInfoResult.ApiResult.Subscribe == 0)
            {
                //用户取消关注有消息推送
                //在那时设置member中是否在关注为false

                return(RedirectUrlOnlyOpenId(redirectUrl, domainId));
            }
            else
            {
                //根据OpenId获取用户信息
                //添加新用户
                AddMemberArgs args = new AddMemberArgs();
                args.WeixinUser = getUserInfoResult.ApiResult;
                member          = _memberManager.AddMember(domainContext, args);

                memberContext = new MemberContext(member);
                SessionContainer.SetMemberContext(HttpContext, memberContext);

                //转回初始业务页面
                return(new RedirectResult(redirectUrl));
            }
        }
コード例 #3
0
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            /*
             * 在该域名和符合要求的下级域名内,可以代替旗下授权后公众号发起网页授权。
             * 下级域名必须是$APPID$.wx.abc.com的形式
             * ($APPID$为公众号的AppID的替换符,建议第三方用这种方式,若需可做域名映射),
             * 如果不按这种形式来做,旗下公众号违规将可能导致整个网站被封。
             *
             *
             * 此处,使用域名泛解析,DNS上直接设置 *.wxc.shengxunwei.com
             * 要注意的是 wxc.shengxunwei.com 必须是80端口
             * 不论是作为第三方平台运营还是独立运营
             * domainId 还是要放在URL后面
             *
             */

            //1.从SESSION中取出MemberContext,如果没有,跳转到微信网页授权地址
            //网页授权
            //a.取得用户OpenId,并判断用户当前是否关注当前公众号
            //b.如果用户已关注,但没有用户信息,则新建用户
            //c.如果用户不是已关注用户,跳转到引导关注页面

            Guid   domainId    = Guid.Empty;
            object objDomainId = filterContext.RouteData.Values["domainId"];

            if (objDomainId == null || Guid.TryParse(objDomainId.ToString(), out domainId) == false)
            {
                //TODO:重定向到错误页面
                //filterContext.Result = new RedirectResult("~/Home/Login");
                filterContext.Result = new HttpStatusCodeResult(404);
                return;
            }

            DomainContext = ClientDomainPool.Instance.GetDomainContext(domainId);

            if (DomainContext == null)
            {
                Log.Write("指定的 domoainId 不存在", domainId.ToString(), TraceEventType.Warning);
                filterContext.Result = new HttpStatusCodeResult(404);
                return;
            }

            //允许匿名浏览,不取用户信息
            object[] objAllowedAnonymousArray =
                filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowedAnonymous), false);
            if (objAllowedAnonymousArray.Length > 0)
            {
                return;
            }

            MemberContext = SessionContainer.GetMemberContext(filterContext.HttpContext);

            // Uri uri = new Uri("http://wxctest.shengxunwei.com/WeixinApi/Handler/F6AAD430-CA1F-4AFD-B2B0-6E0D2FABB622");

            if (MemberContext == null)
            {
                #region 调用微信网页授权接口取用户基本信息

                if (_debug)
                {
                    MemberEntity member = MemberManager.Instance.GetMemberByOpenId(domainId, DomainContext.AppId, "oXCfEwEteoWmCygMuCKqhvqshVnQ");
                    MemberContext = new MemberContext(member);
                    SessionContainer.SetMemberContext(HttpContext, MemberContext);
                    SessionContainer.SetOpenId(HttpContext, "oXCfEwEteoWmCygMuCKqhvqshVnQ");
                }
                else
                {
                    //对于API请求,不存在调用微信网页授权接口取用户基本信息
                    //直接返回会话过期,让页面去刷新
                    //如果该API允许仅OpenId即可浏览且已经有OpenId了,则允许请求
                    object[] objAllowedOnlyOpenIdArray =
                        filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowedOnlyOpenId), false);
                    bool allowedOnlyOpenId = objAllowedOnlyOpenIdArray.Length > 0;
                    if (allowedOnlyOpenId && String.IsNullOrEmpty(this.OpenId) == false)
                    {
                        //do nothing,继续访问API
                    }
                    else
                    {
                        ApiResult apiResult = new ApiResult()
                        {
                            Success = false,
                            Message = "会话已过期",
                            Reason  = 7001
                        };
                        ContentResult result = new ContentResult();
                        result.ContentEncoding = Encoding.UTF8;
                        result.Content         = Newtonsoft.Json.JsonConvert.SerializeObject(apiResult);
                        filterContext.Result   = result;
                        return;
                    }
                }

                #endregion
            }
        }
コード例 #4
0
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            if (filterContext.Result != null)
            {
                return;
            }

            //避免在没有指定 domainId 时反复进入 OnActionExecuting 形成死循环
            if (filterContext.RouteData.Values["action"].ToString() == "ErrorView")
            {
                return;
            }

            /*
             * 在该域名和符合要求的下级域名内,可以代替旗下授权后公众号发起网页授权。
             * 下级域名必须是$APPID$.wx.abc.com的形式
             * ($APPID$为公众号的AppID的替换符,建议第三方用这种方式,若需可做域名映射),
             * 如果不按这种形式来做,旗下公众号违规将可能导致整个网站被封。
             *
             *
             * 此处,使用域名泛解析,DNS上直接设置 *.wxc.shengxunwei.com
             * 要注意的是 wxc.shengxunwei.com 必须是80端口
             * 不论是作为第三方平台运营还是独立运营
             * domainId 还是要放在URL后面
             *
             */

            //1.从SESSION中取出MemberContext,如果没有,跳转到微信网页授权地址
            //网页授权
            //a.取得用户OpenId,并判断用户当前是否关注当前公众号
            //b.如果用户已关注,但没有用户信息,则新建用户
            //c.如果用户不是已关注用户,跳转到引导关注页面

            Guid   domainId    = Guid.Empty;
            object objDomainId = filterContext.RouteData.Values["domainId"];

            if (objDomainId == null || Guid.TryParse(objDomainId.ToString(), out domainId) == false)
            {
                //判断是否允许不带domainId
                object[] objAllowedEmptyDomainArray =
                    filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowedEmptyDomain), false);
                if (objAllowedEmptyDomainArray.Length > 0)
                {
                    return;
                }

                //重定向到错误页面
                filterContext.Result = new RedirectResult(String.Format(
                                                              "~/Home/ErrorView/?message={0}", "td7"));
                return;
            }

            DomainContext = ClientDomainPool.Instance.GetDomainContext(domainId);

            if (DomainContext == null || DomainContext.Domain == null)
            {
                Log.Write("指定的 domoainId 不存在", domainId.ToString(), TraceEventType.Warning);
                //重定向到错误页面
                filterContext.Result = new RedirectResult(String.Format(
                                                              "~/Home/ErrorView/?message={0}", "td6"));
                return;
            }

            if (DomainContext.Authorizer == null)
            {
                Log.Write("指定的 Domain 没有授权公众号信息", domainId.ToString(), TraceEventType.Warning);
                //重定向到错误页面
                filterContext.Result = new RedirectResult(String.Format(
                                                              "~/Home/ErrorView/{0}?message={1}", DomainContext.Domain.Id, "td5"));
                return;
            }

            ViewBag.Domain        = DomainContext.Domain;
            ViewBag.DomainContext = DomainContext;
            ViewBag.Authorizer    = DomainContext.Authorizer;

            //获取快捷菜单
            ShortcutMenuEntity shortcutMenuEntity =
                _portalManager.GetShortcutMenu(this.DomainContext.Domain.Id, this.DomainContext.AppId);

            ViewBag.ShortcutMenu = shortcutMenuEntity;

            //使用匿名浏览
            //及时匿名,Domain信息还是要取的
            object[] objAllowedAnonymousArray =
                filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowedAnonymous), false);
            if (objAllowedAnonymousArray.Length > 0)
            {
                return;
            }

            //只有微信认证服务号具备此权限
            //用户管理-网页授权获取用户openid/用户基本信息
            if (DomainContext.Authorizer.AuthorizationType != EnumAuthorizationType.AuthorizedService)
            {
                //重定向到错误页面
                filterContext.Result = new RedirectResult(String.Format(
                                                              "~/Home/ErrorView/{0}?message={1}", DomainContext.Domain.Id, "td4"));
                return;
            }

            MemberContext = SessionContainer.GetMemberContext(filterContext.HttpContext);

            // Uri uri = new Uri("http://wxctest.shengxunwei.com/WeixinApi/Handler/F6AAD430-CA1F-4AFD-B2B0-6E0D2FABB622");

            //如果有OpenId却没有MemberContext,说明此页面是AllowedOnlyOpenId,已经取到过OpenId了
            //这里要判断此页面是否允许匿名浏览,因为当没有关注的人访问页面时
            //首先跳到微信鉴权页面,然后回调时发现没关系,转到引导关注页面,但是他再点开主个页面时
            //此时Session中就已经有了OpenId,但是没有MemberContext
            if (MemberContext == null) // && String.IsNullOrEmpty(this.OpenId)
            {
                #region 调用微信网页授权接口取用户基本信息

                if (_debug)
                {
                    MemberEntity member = MemberManager.Instance.GetMemberByOpenId(domainId, DomainContext.AppId, "oXCfEwEteoWmCygMuCKqhvqshVnQ");
                    MemberContext = new MemberContext(member);
                    SessionContainer.SetMemberContext(HttpContext, MemberContext);
                    SessionContainer.SetOpenId(HttpContext, "oXCfEwEteoWmCygMuCKqhvqshVnQ");
                }
                else
                {
                    Uri    currentUrl    = filterContext.HttpContext.Request.Url;
                    string strCurrentUrl = currentUrl.ToString();
                    if (strCurrentUrl.Contains("AllowedOnlyOpenId") && String.IsNullOrEmpty(this.OpenId) == false)
                    {
                        //允许匿名浏览并且已经走过微信WEB鉴权了 //do nothing
                        //这里的逻辑是:
                        //1.第一次打开页面时,URL中是没有AllowedOnlyOpenId的,会被转到微信鉴权,转的时候带上AllowedOnlyOpenId(如果允许)
                        //2.鉴权完毕回来时候,URL上是还会带着 AllowedOnlyOpenId 的
                        //3.这里取到,并判断已经有了OpenId,那肯定是走过微信鉴权了,就不能再走了,否则死循环
                        //在这种情况下啥也不做,走完 OnActionExecuting 正常进入 Action
                    }
                    else
                    {
                        string redirectUrl = String.Format("{0}://{1}", currentUrl.Scheme, currentUrl.Host);
                        if (currentUrl.Port != 80)
                        {
                            redirectUrl += ":" + currentUrl.Port;
                        }

                        //微信鉴权以后回到OAuthCallback
                        redirectUrl += "/ThirdPartyWeixinApi/OAuthCallback?domainId=" + objDomainId.ToString();

                        //当前请求的页面,OAuthCallback中的逻辑走完以后,回到当前请求的页面
                        string state = filterContext.HttpContext.Request.Url.ToString();

                        //判断当前请求的页面是否允许未关注用户浏览,如果允许,在URL后面加参数
                        //以便完成weixin鉴权后判断是否跳转
                        object[] objAllowedOnlyOpenIdArray =
                            filterContext.ActionDescriptor.GetCustomAttributes(typeof(AllowedOnlyOpenId), false);
                        bool allowedOnlyOpenId = objAllowedOnlyOpenIdArray.Length > 0;

                        if (allowedOnlyOpenId && state.Contains("AllowedOnlyOpenId") == false)
                        {
                            if (state.IndexOf('?') >= 0)
                            {
                                state += "&AllowedOnlyOpenId=1";
                            }
                            else
                            {
                                state += "?AllowedOnlyOpenId=1";
                            }
                        }

                        //此处如果直接把网址放到 state 中传递,微信oauth鉴权时可能会提示state参数过长
                        //如 http://appid.wxc.shengxunwei.com/Home/QRCode/2a58d820-de07-4c8f-80b9-b5cb5a1028b4?type=member&memberId=ed638a09-f7aa-4d24-850e-a889a95bd454&cardNumber=13600864926982
                        //此处把要跳转的网址放在缓存中,用guid做key
                        //因为鉴权回调时并不是直接去这个state网址,而是去/ThirdPartyWeixinApi/OAuthCallback
                        //所以在回调的OAuthCallback方法中从缓存中取出这个地址即可
                        string stateKey = Guid.NewGuid().ToString();
                        _cachingService.Set(stateKey, state, _shortUrlCachingTime);
                        //TODO:临时解决方案。连调用2次防止写入失败
                        _cachingService.Set(stateKey, state, _shortUrlCachingTime);
                        state = stateKey;

                        //作为第三方平台运营时,多一个 component_appid 参数
                        //跳转到微信网页授权地址,取用户信息
                        string weiXinOAuthUrl = String.Format(
                            "https://open.weixin.qq.com/connect/oauth2/authorize?appid={0}&redirect_uri={1}&response_type=code&scope=snsapi_base&state={2}&component_appid={3}#wechat_redirect",
                            DomainContext.AppId, Server.UrlEncode(redirectUrl), state, _configuration.ThirdParty.AppId);//Server.UrlEncode(state)

                        Log.Write("调用微信网页授权接口", weiXinOAuthUrl, TraceEventType.Verbose);

                        filterContext.Result = new RedirectResult(weiXinOAuthUrl);
                        return;
                    }
                }

                #endregion
            }

            //这里 MemberContext 不为空,表示走过 OAuth 鉴权了
            //这里只是更新MemberContext中的数据
            if (MemberContext != null)
            {
                //刷新用户的积分等信息Point,CashAccount,Staff,CardLevel
                //MemberContext.Member.Point = _memberManager.GetMemberPoint(MemberContext.Member.Id);
                //MemberContext.Member.CashAccount = _memberManager.GetMemberCashAccountBalances(MemberContext.Member.Id);
                //MemberContext.Member.Staff = _memberManager.IsStaff(MemberContext.Member.Id);
                MemberContext.Member     = _memberManager.GetMember(MemberContext.Member.Id);
                ViewBag.Member           = MemberContext.Member;
                ViewBag.ValidCouponCount = CouponManager.Instance.GetValidCouponCountByMember(
                    DomainContext.Domain.Id, DomainContext.AppId, MemberContext.Member.Id);
            }

            //这里不可放初始化相关代码
            //如:取DOMAIN,取快捷菜单,因为当匿名浏览时,走不到这里
        }