ASP.NET Core集成微信登录

工具:

Visual Studio 2015 update 3

Asp.Net Core 1.0

1 准备工作

申请微信公众平台接口测试帐号,申请网址:(http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login)。申请接口测试号无需公众帐号,可以直接体验和测试公众平台所有高级接口。

1.1 配置接口信息

1.2 修改网页授权信息

点击“修改”后在弹出页面填入你的网站域名:

2  新建网站项目

2.1 选择ASP.NET Core Web Application 模板

2.2 选择Web 应用程序,并更改身份验证为个人用户账户

3 集成微信登录功能

3.1添加引用

打开project.json文件,添加引用Microsoft.AspNetCore.Authentication.OAuth

3.2 添加代码文件

在项目中新建文件夹,命名为WeChatOAuth,并添加代码文件(本文最后附全部代码)。

3.3 注册微信登录中间件

打开Startup.cs文件,在Configure中添加代码:

app.UseWeChatAuthentication(new WeChatOptions()
{
    AppId = "******",
    AppSecret = "******"
});

注意该代码的插入位置必须在app.UseIdentity()下方。

4 代码

 1 // Copyright (c) .NET Foundation. All rights reserved.
 2 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 3
 4 using System;
 5 using Microsoft.AspNetCore.Authentication.WeChat;
 6 using Microsoft.Extensions.Options;
 7
 8 namespace Microsoft.AspNetCore.Builder
 9 {
10     /// <summary>
11     /// Extension methods to add WeChat authentication capabilities to an HTTP application pipeline.
12     /// </summary>
13     public static class WeChatAppBuilderExtensions
14     {
15         /// <summary>
16         /// Adds the <see cref="WeChatMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>, which enables WeChat authentication capabilities.
17         /// </summary>
18         /// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
19         /// <returns>A reference to this instance after the operation has completed.</returns>
20         public static IApplicationBuilder UseWeChatAuthentication(this IApplicationBuilder app)
21         {
22             if (app == null)
23             {
24                 throw new ArgumentNullException(nameof(app));
25             }
26
27             return app.UseMiddleware<WeChatMiddleware>();
28         }
29
30         /// <summary>
31         /// Adds the <see cref="WeChatMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/>, which enables WeChat authentication capabilities.
32         /// </summary>
33         /// <param name="app">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
34         /// <param name="options">A <see cref="WeChatOptions"/> that specifies options for the middleware.</param>
35         /// <returns>A reference to this instance after the operation has completed.</returns>
36         public static IApplicationBuilder UseWeChatAuthentication(this IApplicationBuilder app, WeChatOptions options)
37         {
38             if (app == null)
39             {
40                 throw new ArgumentNullException(nameof(app));
41             }
42             if (options == null)
43             {
44                 throw new ArgumentNullException(nameof(options));
45             }
46
47             return app.UseMiddleware<WeChatMiddleware>(Options.Create(options));
48         }
49     }
50 }

WeChatAppBuilderExtensions.cs

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.AspNetCore.Authentication.WeChat
{
    public static class WeChatDefaults
    {
        public const string AuthenticationScheme = "WeChat";

        public static readonly string AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize";

        public static readonly string TokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token";

        public static readonly string UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo";
    }
}

WeChatDefaults.cs

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

namespace Microsoft.AspNetCore.Authentication.WeChat
{
    internal class WeChatHandler : OAuthHandler<WeChatOptions>
    {
        public WeChatHandler(HttpClient httpClient)
            : base(httpClient)
        {
        }

        protected override async Task<AuthenticateResult> HandleRemoteAuthenticateAsync()
        {
            AuthenticationProperties properties = null;
            var query = Request.Query;

            var error = query["error"];
            if (!StringValues.IsNullOrEmpty(error))
            {
                var failureMessage = new StringBuilder();
                failureMessage.Append(error);
                var errorDescription = query["error_description"];
                if (!StringValues.IsNullOrEmpty(errorDescription))
                {
                    failureMessage.Append(";Description=").Append(errorDescription);
                }
                var errorUri = query["error_uri"];
                if (!StringValues.IsNullOrEmpty(errorUri))
                {
                    failureMessage.Append(";Uri=").Append(errorUri);
                }

                return AuthenticateResult.Fail(failureMessage.ToString());
            }

            var code = query["code"];
            var state = query["state"];
            var oauthState = query["oauthstate"];

            properties = Options.StateDataFormat.Unprotect(oauthState);

            if (state != Options.StateAddition || properties == null)
            {
                return AuthenticateResult.Fail("The oauth state was missing or invalid.");
            }

            // OAuth2 10.12 CSRF
            if (!ValidateCorrelationId(properties))
            {
                return AuthenticateResult.Fail("Correlation failed.");
            }

            if (StringValues.IsNullOrEmpty(code))
            {
                return AuthenticateResult.Fail("Code was not found.");
            }

            //获取tokens
            var tokens = await ExchangeCodeAsync(code, BuildRedirectUri(Options.CallbackPath));

            var identity = new ClaimsIdentity(Options.ClaimsIssuer);

            AuthenticationTicket ticket = null;

            if (Options.WeChatScope == Options.InfoScope)
            {
                //获取用户信息
                ticket = await CreateTicketAsync(identity, properties, tokens);
            }
            else
            {
                //不获取信息,只使用openid
                identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, tokens.TokenType, ClaimValueTypes.String, Options.ClaimsIssuer));
                ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Options.AuthenticationScheme);
            }

            if (ticket != null)
            {
                return AuthenticateResult.Success(ticket);
            }
            else
            {
                return AuthenticateResult.Fail("Failed to retrieve user information from remote server.");
            }
        }

        /// <summary>
        /// OAuth第一步,获取code
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="redirectUri"></param>
        /// <returns></returns>
        protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)
        {
            //加密OAuth状态
            var oauthstate = Options.StateDataFormat.Protect(properties);

            //
            redirectUri = $"{redirectUri}?{nameof(oauthstate)}={oauthstate}";

            var queryBuilder = new QueryBuilder()
            {
                { "appid", Options.ClientId },
                { "redirect_uri", redirectUri },
                { "response_type", "code" },
                { "scope", Options.WeChatScope },
                { "state",  Options.StateAddition },
            };
            return Options.AuthorizationEndpoint + queryBuilder.ToString();
        }

        /// <summary>
        /// OAuth第二步,获取token
        /// </summary>
        /// <param name="code"></param>
        /// <param name="redirectUri"></param>
        /// <returns></returns>
        protected override  async Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
        {
            var tokenRequestParameters = new Dictionary<string, string>()
            {
                { "appid", Options.ClientId },
                { "secret", Options.ClientSecret },
                { "code", code },
                { "grant_type", "authorization_code" },
            };

            var requestContent = new FormUrlEncodedContent(tokenRequestParameters);

            var requestMessage = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint);
            requestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            requestMessage.Content = requestContent;
            var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted);
            if (response.IsSuccessStatusCode)
            {
                var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

                string ErrCode = payload.Value<string>("errcode");
                string ErrMsg = payload.Value<string>("errmsg");

                if (!string.IsNullOrEmpty(ErrCode) | !string.IsNullOrEmpty(ErrMsg))
                {
                    return OAuthTokenResponse.Failed(new Exception($"ErrCode:{ErrCode},ErrMsg:{ErrMsg}"));
                }

                var tokens = OAuthTokenResponse.Success(payload);

                //借用TokenType属性保存openid
                tokens.TokenType = payload.Value<string>("openid");

                return tokens;
            }
            else
            {
                var error = "OAuth token endpoint failure";
                return OAuthTokenResponse.Failed(new Exception(error));
            }
        }

        /// <summary>
        /// OAuth第四步,获取用户信息
        /// </summary>
        /// <param name="identity"></param>
        /// <param name="properties"></param>
        /// <param name="tokens"></param>
        /// <returns></returns>
        protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
        {
            var queryBuilder = new QueryBuilder()
            {
                { "access_token", tokens.AccessToken },
                { "openid",  tokens.TokenType },//在第二步中,openid被存入TokenType属性
                { "lang", "zh_CN" }
            };

            var infoRequest = Options.UserInformationEndpoint + queryBuilder.ToString();

            var response = await Backchannel.GetAsync(infoRequest, Context.RequestAborted);
            if (!response.IsSuccessStatusCode)
            {
                throw new HttpRequestException($"Failed to retrieve WeChat user information ({response.StatusCode}) Please check if the authentication information is correct and the corresponding WeChat Graph API is enabled.");
            }

            var user = JObject.Parse(await response.Content.ReadAsStringAsync());
            var ticket = new AuthenticationTicket(new ClaimsPrincipal(identity), properties, Options.AuthenticationScheme);
            var context = new OAuthCreatingTicketContext(ticket, Context, Options, Backchannel, tokens, user);

            var identifier = user.Value<string>("openid");
            if (!string.IsNullOrEmpty(identifier))
            {
                identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var nickname = user.Value<string>("nickname");
            if (!string.IsNullOrEmpty(nickname))
            {
                identity.AddClaim(new Claim(ClaimTypes.Name, nickname, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var sex = user.Value<string>("sex");
            if (!string.IsNullOrEmpty(sex))
            {
                identity.AddClaim(new Claim("urn:WeChat:sex", sex, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var country = user.Value<string>("country");
            if (!string.IsNullOrEmpty(country))
            {
                identity.AddClaim(new Claim(ClaimTypes.Country, country, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var province = user.Value<string>("province");
            if (!string.IsNullOrEmpty(province))
            {
                identity.AddClaim(new Claim(ClaimTypes.StateOrProvince, province, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var city = user.Value<string>("city");
            if (!string.IsNullOrEmpty(city))
            {
                identity.AddClaim(new Claim("urn:WeChat:city", city, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var headimgurl = user.Value<string>("headimgurl");
            if (!string.IsNullOrEmpty(headimgurl))
            {
                identity.AddClaim(new Claim("urn:WeChat:headimgurl", headimgurl, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            var unionid = user.Value<string>("unionid");
            if (!string.IsNullOrEmpty(unionid))
            {
                identity.AddClaim(new Claim("urn:WeChat:unionid", unionid, ClaimValueTypes.String, Options.ClaimsIssuer));
            }

            await Options.Events.CreatingTicket(context);
            return context.Ticket;
        }
    }
}

WeChatHandler.cs

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Microsoft.AspNetCore.Authentication.WeChat
{
    /// <summary>
    /// An ASP.NET Core middleware for authenticating users using WeChat.
    /// </summary>
    public class WeChatMiddleware : OAuthMiddleware<WeChatOptions>
    {
        /// <summary>
        /// Initializes a new <see cref="WeChatMiddleware"/>.
        /// </summary>
        /// <param name="next">The next middleware in the HTTP pipeline to invoke.</param>
        /// <param name="dataProtectionProvider"></param>
        /// <param name="loggerFactory"></param>
        /// <param name="encoder"></param>
        /// <param name="sharedOptions"></param>
        /// <param name="options">Configuration options for the middleware.</param>
        public WeChatMiddleware(
            RequestDelegate next,
            IDataProtectionProvider dataProtectionProvider,
            ILoggerFactory loggerFactory,
            UrlEncoder encoder,
            IOptions<SharedAuthenticationOptions> sharedOptions,
            IOptions<WeChatOptions> options)
            : base(next, dataProtectionProvider, loggerFactory, encoder, sharedOptions, options)
        {
            if (next == null)
            {
                throw new ArgumentNullException(nameof(next));
            }

            if (dataProtectionProvider == null)
            {
                throw new ArgumentNullException(nameof(dataProtectionProvider));
            }

            if (loggerFactory == null)
            {
                throw new ArgumentNullException(nameof(loggerFactory));
            }

            if (encoder == null)
            {
                throw new ArgumentNullException(nameof(encoder));
            }

            if (sharedOptions == null)
            {
                throw new ArgumentNullException(nameof(sharedOptions));
            }

            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (string.IsNullOrEmpty(Options.AppId))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, nameof(Options.AppId)));
            }

            if (string.IsNullOrEmpty(Options.AppSecret))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, nameof(Options.AppSecret)));
            }
        }

        /// <summary>
        /// Provides the <see cref="AuthenticationHandler{T}"/> object for processing authentication-related requests.
        /// </summary>
        /// <returns>An <see cref="AuthenticationHandler{T}"/> configured with the <see cref="WeChatOptions"/> supplied to the constructor.</returns>
        protected override AuthenticationHandler<WeChatOptions> CreateHandler()
        {
            return new WeChatHandler(Backchannel);
        }
    }
}

WeChatMiddleware.cs

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication.WeChat;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;

namespace Microsoft.AspNetCore.Builder
{
    /// <summary>
    /// Configuration options for <see cref="WeChatMiddleware"/>.
    /// </summary>
    public class WeChatOptions : OAuthOptions
    {
        /// <summary>
        /// Initializes a new <see cref="WeChatOptions"/>.
        /// </summary>
        public WeChatOptions()
        {
            AuthenticationScheme = WeChatDefaults.AuthenticationScheme;
            DisplayName = AuthenticationScheme;
            CallbackPath = new PathString("/signin-wechat");
            StateAddition = "#wechat_redirect";
            AuthorizationEndpoint = WeChatDefaults.AuthorizationEndpoint;
            TokenEndpoint = WeChatDefaults.TokenEndpoint;
            UserInformationEndpoint = WeChatDefaults.UserInformationEndpoint;
            //SaveTokens = true;           

            //BaseScope (不弹出授权页面,直接跳转,只能获取用户openid),
            //InfoScope (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
            WeChatScope = InfoScope;
        }

        // WeChat uses a non-standard term for this field.
        /// <summary>
        /// Gets or sets the WeChat-assigned appId.
        /// </summary>
        public string AppId
        {
            get { return ClientId; }
            set { ClientId = value; }
        }

        // WeChat uses a non-standard term for this field.
        /// <summary>
        /// Gets or sets the WeChat-assigned app secret.
        /// </summary>
        public string AppSecret
        {
            get { return ClientSecret; }
            set { ClientSecret = value; }
        }

        public string StateAddition { get; set; }
        public string WeChatScope { get; set; }

        public string BaseScope = "snsapi_base";

        public string InfoScope = "snsapi_userinfo";
    }
}

WeChatOptions.cs

时间: 2024-10-05 04:58:57

ASP.NET Core集成微信登录的相关文章

ABP官方文档翻译 6.2.1 ASP.NET Core集成

ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Action过滤器 异常过滤器 结果过滤器 Ajax请求的结果缓存 模型绑定器 视图 客户端代理 集成测试 介绍 本文档描述了ABP如何集成ASP.NET Core.ASP.NET Core通过Abp.AspNetCore nuget包实现集成. 迁移到ASP.NET Core? 如果你已经有一个工程并考虑

php的laravel框架快速集成微信登录

最终的解决方案是:https://github.com/liuyunzhuge/php_weixin_provider,详细的介绍请往下阅读. 本文面向的是php语言laravel框架的用户,介绍的是基于该框架实现的一个简易集成微信登录的方法.使用方法如下: 1. 安装php_weixin_provider 在项目下运行composer require thirdproviders/weixin,即可完成安装.安装成功后,在项目的vendor目录下应该能看到php_weixin_provider

asp.net core 集成 log4net 日志框架

原文:asp.net core 集成 log4net 日志框架 asp.net core 集成 log4net 日志框架 Intro 在 asp.net core 中有些日志我们可能想输出到数据库或文件或elasticsearch等,如果不自己去实现一个 LoggerProvider 的话就需要借助第三方日志框架实现了,而一些第三方框架的实现大多比较完善和成熟,不失为一个好办法. 自己写了一个 log4net 的扩展 WeihanLi.Common.Logging.Log4Net,提供了在 .n

ASP.NET Core集成现有系统认证

我们现在大多数转向ASP.NET Core来使用开发的团队,应该都不是从0开始搭建系统,而是老的业务系统已经在运行,ASP.NET Core用来开发新模块.那么解决用户认证的问题,成为我们的第一个拦路虎.本文将给大家简单阐述一下认证与授权的基本概念,以及基于ASP.NET Core 中间件实现的认证和改造JwtBearer 认证中间件来实现的认证达到与老系统(主要是token-based认证)的集成. 目录 认证与授权 什么是认证 何谓授权 用Middleware拦截 定制JWT Bearer 

如何在ios中集成微信登录功能

在ios中集成微信的登录功能有两种方法 1 用微信原生的api来做,这样做的好处就是轻量级,程序负重小,在Build Settings 中这样设置 然后设置 友盟的设置同上,但是要注意,加入你需要的所有框架到应用程序中

ASP.NET Core AD 域登录

在选择AD登录时,其实可以直接选择 Windows 授权,不过因为有些网站需要的是LDAP获取信息进行授权,而非直接依赖Web Server自带的Windows 授权功能. 当然如果使用的是Azure AD/企业账号登录时,直接在ASP.NET Core创建项目时选择就好了. 来个ABC: 1.新建一个ASP.NET Core项目 2.Nuget引用dependencies / 修改```project.json``` Novell.Directory.Ldap.NETStandard Micr

网站应用集成微信登录

项目需求,需要在网站中添加微信登录的功能,按流程,现在开发者中心中交了300元的认证费用,开通了网站应用的微信登录接口,在PC端打开浏览器,点击微信图标,打开手机微信扫一扫,在弹出的窗口中点击确认,完成验证后实现微信登录: 看起来确实没毛病,但是当用户在手机浏览器中,或者微信中打开网站,点击微信图标同样是弹出扫描二维码的页面,长按二维码也不管用,因为本来就是手机打开的网站,怎么在微信中扫二维码,这里有猫腻. 琢磨了一阵后,还不完善的解决方案是:PC版的没毛病就不说了,主要说说手机打开手机网站,先

Asp .Net Core 2.0 登录授权以及多用户登录

用户登录是一个非常常见的应用场景 .net core 2.0 的登录方式发生了点变化,应该是属于是良性的变化,变得更方便,更容易扩展. 配置 打开项目中的Startup.cs文件,找到ConfigureServices方法,我们通常在这个方法里面做依赖注入的相关配置.添加如下代码: public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthentication

grpc asp.net core 集成时一些配置的说明

一  什么是grpc google出了一款分布式通讯框架:grpc.我想这也不是新的东西了,在13年的一个项目中,用在了数据层和业务端之间的通讯上,当时并没有觉得怎么样,因为wcf很轻松的也可以可以实现哪些功能.但是想在想一想,确实在某些场合,grpc比wcf要更好一点.第一就是比wcf要轻量级的多,第二,跨平台,他出了一款协议Protocol Buffers,来定义接口,然后使用工具去生成不同 的语言代码.而wcf我们也知道,跨平台使用soap协议,基于http,坏处就是慢.而基于tcp或者命