当前位置: 移动技术网 > IT编程>开发语言>.net > ASP.NET Core集成微信登录

ASP.NET Core集成微信登录

2017年12月12日  | 移动技术网IT编程  | 我要评论

小依,薛城房屋出租,韩宫窥春

工具:

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 代码

// 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 microsoft.aspnetcore.authentication.wechat;
using microsoft.extensions.options;

namespace microsoft.aspnetcore.builder
{
 /// <summary>
 /// extension methods to add wechat authentication capabilities to an http application pipeline.
 /// </summary>
 public static class wechatappbuilderextensions
 {
  /// <summary>
  /// adds the <see cref="wechatmiddleware"/> middleware to the specified <see cref="iapplicationbuilder"/>, which enables wechat authentication capabilities.
  /// </summary>
  /// <param name="app">the <see cref="iapplicationbuilder"/> to add the middleware to.</param>
  /// <returns>a reference to this instance after the operation has completed.</returns>
  public static iapplicationbuilder usewechatauthentication(this iapplicationbuilder app)
  {
   if (app == null)
   {
    throw new argumentnullexception(nameof(app));
   }

   return app.usemiddleware<wechatmiddleware>();
  }

  /// <summary>
  /// adds the <see cref="wechatmiddleware"/> middleware to the specified <see cref="iapplicationbuilder"/>, which enables wechat authentication capabilities.
  /// </summary>
  /// <param name="app">the <see cref="iapplicationbuilder"/> to add the middleware to.</param>
  /// <param name="options">a <see cref="wechatoptions"/> that specifies options for the middleware.</param>
  /// <returns>a reference to this instance after the operation has completed.</returns>
  public static iapplicationbuilder usewechatauthentication(this iapplicationbuilder app, wechatoptions options)
  {
   if (app == null)
   {
    throw new argumentnullexception(nameof(app));
   }
   if (options == null)
   {
    throw new argumentnullexception(nameof(options));
   }

   return app.usemiddleware<wechatmiddleware>(options.create(options));
  }
 }
}

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.

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";
 }
}

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 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;
  }
 }
}

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;
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);
  }
 }
}

wechatoptions.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";
 }
}

本文已被整理到了《asp.net微信开发教程汇总》,欢迎大家学习阅读。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网