万州网站建设公司,美容手机网站模板,深圳做网站 创同盟,qq是用什么开发的新年新气象#xff0c;趁着新年的喜庆#xff0c;肝了十来天#xff0c;终于发了第一版#xff0c;希望大家喜欢。如果有不喜欢看文字的童鞋#xff0c;可以直接看下面的地址体验一下#xff1a;Github: https://github.com/mrhuo/MrHuo.OAuth唯一官网#xff1a;https:… 新年新气象趁着新年的喜庆肝了十来天终于发了第一版希望大家喜欢。如果有不喜欢看文字的童鞋可以直接看下面的地址体验一下Github: https://github.com/mrhuo/MrHuo.OAuth唯一官网https://oauthlogin.net前言此次带来的这个小项目是 OAuth2 登录组件看到 Java 平台 JustAuth 项目很方便的接入第三方平台登录心里痒痒啊搜了一大圈发现我大 .netcore 能用的可说是少之又少而且代码写得一塌糊涂全在一个库里代码风格也看不惯所以下定决定操起键盘开干。关于 OAuth2 的一些基础、原理介绍文章太多了写的好的不在少数在页尾我提供了几个链接喜欢的朋友看一下这里就不深入解释直入主题。如何使用 这里拿接入 github 登录做演示新建 Asp.NET Core Web应用程序 项目名叫 GithubLoginPS:你可以自己起个和更牛×的名字选择模型视图控制器这个当然你可以选择其他的。第一步安装安装这个 nuget 包Install-Package MrHuo.OAuth.Github -Version 1.0.0
第二步配置打开 appsettings.json 写入下面的配置{oauth: {github: {app_id: github_app_id,app_key: github_app_key,redirect_uri: https://oauthlogin.net/oauth/githubcallback,scope: repo}}
}
这里的配置可以通过 https://github.com/settings/applications/new 来注册redirect_uri 可以填写本地 localhost 地址的超级方便这也是为什么使用 github 登录做演示的原因。创建完成后在这个界面里生成 client secret输入密码生成成功后是这样的把界面里的 Client IDClient secret连同上一个界面里填写的 Authorization callback URL 全部填写到配置文件对应位置。现在配置文件 appsettings.json 是这样的{Logging: {LogLevel: {Default: Information,Microsoft: Warning,Microsoft.Hosting.Lifetime: Information}},AllowedHosts: *,oauth: {github: {app_id: c95fxxxxxx0d09,app_key: c6a73xxxxxx6375,redirect_uri: http://localhost:5000/oauth/githubcallback,scope: repo}}
}
下面的 scope 暂且不管他你想深入了解它的作用的话后面再说。第三步写代码在 Startup.cs 文件中注入组件// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{services.AddControllersWithViews();services.AddSingleton(new GithubOAuth(OAuthConfig.LoadFrom(Configuration, oauth:github)));
}
文件中其他代码没有修改只加了这一行而已。新建一个 OAuthController 类代码如下using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using MrHuo.OAuth.Github;
namespace GithubLogin.Controllers
{public class OAuthController: Controller{[HttpGet(oauth/github)]public IActionResult Github([FromServices] GithubOAuth githubOAuth){return Redirect(githubOAuth.GetAuthorizeUrl());}[HttpGet(oauth/githubcallback)]public async TaskIActionResult GithubCallback([FromServices] GithubOAuth githubOAuth,[FromQuery] string code){return Json(await githubOAuth.AuthorizeCallback(code));}}
}
你没看错就这点代码就好了。我们来运行一下试试项目运行之后在地址栏里输入下面这个地址http://localhost:5000/oauth/github因为我们没有修改任何代码没有在视图上做任何链接所以就劳烦手动啦~~回车之后顺利跳转到 github 授权点击绿色的 Authorize 按钮之后稍等片刻你会看到下面这个结果顺利拿到了用户信息PS:请忽略我少的可怜的粉丝曾经我不强求 --ToT好了到这里我的表演结束了可以看到接入流程非常流畅卡人主要是在申请这些步骤。下面讲讲原理之类的随便说一些...如果觉得我啰嗦那么就不用往下看了因为下面我会更啰嗦。当然除了 github 现在已经接入了12个平台其中 QQ 和抖音我没有注册到应用无法测试所以暂时没有 nuget 包一个人的力量总是有限的在这里我请求各位有闲时间或者有 appid 资源的大佬为这个小项目做一些贡献是她走的远一些。更多的 nuget 包进这里 https://www.nuget.org/profiles/mrhuo 或者在 VS nuget 包管理器里搜索 MrHuo.OAuth就可以了。请忽略 nuget 上其他几个 垃圾 包那是很多年很多年以前写的舍不得删。开发背景 第三方平台登录说白了就是实现 OAuth2 协议很多平台比如支付宝、百度、github、微软甚至是抖音、快手很多平台都提供了开放接口。但是很多平台会在这个标准协议的基础上增加、修改一些东西比如标准协议里获取 authorize code 时应提供 client_id微信公众平台非要把它改成 appid。再比如获取用户信息时只需要 access_token 参数微信公众平台这边非要提供一个 openid当然这是在所难免的因为各个平台实际业务还是千差万别无法做到完全的统一那这就给我们开发者带来一个困扰开发第三方登录时很困难当然开发一两个也无所谓要是多了呢假如有这么一个产品经理他想接入很多的登录方式让使用者无论使用哪种平台都能在这里顺利登录找到回家的路呢PS:产品经理你别跑看我40米的大刀。无疑给我们一个考验如何做到一个标准化可配置可扩展呢这就是一个需要深究的问题。下面我就说说我肝这个项目的一些想法说的不好别喷我我还年轻PS:三十多岁老大叔别装嫩还要脸......制定标准 看了很多文档之后我们会发现万变不离其宗总有规律可循总的来说有下面3个步骤GetAuthorizeUrl这一步通过 client_idredirect_uri 等几个参数来获取授权 url跳转到这个 url 之后将在第三方平台上完成登录完成登录之后会跳转到上面提供的 redirect_uri 这个地址并且带上一个 code 参数。GetAccessToken这一步里拿到上面的 code 之后去第三方平台换 access_token。GetUserInfo这一步并非必须但是我们既然是做第三方登录登录之后还是需要和自己平台的一些业务绑定用户账号或者使用现有信息注册一个用户这个方法就显得尤为重要了。到此就这3个步骤我觉得是需要制定在标准里面的所以我就写了下面这个接口来规范它/// summary
/// OAuth 登录 API 接口规范
/// /summary
public interface IOAuthLoginApiTAccessTokenModel, TUserInfoModelwhere TAccessTokenModel : IAccessTokenModelwhere TUserInfoModel : IUserInfoModel
{/// summary/// 获取跳转授权的 URL/// /summary/// param namestate/param/// returns/returnsstring GetAuthorizeUrl(string state );/// summary/// 异步获取 AccessToken/// /summary/// param namecode/param/// param namestate/param/// returns/returnsTaskTAccessTokenModel GetAccessTokenAsync(string code, string state );/// summary/// 异步获取用户详细信息/// /summary/// param nameaccessTokenModel/param/// returns/returnsTaskTUserInfoModel GetUserInfoAsync(TAccessTokenModel accessTokenModel);
}
可以看到我将 AccessToken 和 UserInfo 做成了泛型参数因为他们是这个规范里的可变部分。代码中 state 参数的作用呢就是为了防止 CORS 攻击做的防伪验证这里暂不做解释其他文档里都有这个参数的解释。如何扩展新的平台 这里拿 Gitee 来做演示第一步找平台对应 OAuth 文档找到获取用户信息接口返回JSON转换为 C# 实体类。如下根据自己需要和接口标准扩展用户属性public class GiteeUserModel : IUserInfoModel
{[JsonPropertyName(name)]public string Name { get; set; }[JsonPropertyName(avatar_url)]public string Avatar { get; set; }[JsonPropertyName(message)]public string ErrorMessage { get; set; }[JsonPropertyName(email)]public string Email { get; set; }[JsonPropertyName(blog)]public string Blog { get; set; }//...其他属性类似如上
}
这里使用了 .netcore 内置的 Json 序列化库据说性能提高了不少第二步写对应平台的授权接口/// summary
/// https://gitee.com/api/v5/oauth_doc#/
/// /summary
public class GiteeOAuth : OAuthLoginBaseGiteeUserModel
{public GiteeOAuth(OAuthConfig oauthConfig) : base(oauthConfig) { }protected override string AuthorizeUrl https://gitee.com/oauth/authorize;protected override string AccessTokenUrl https://gitee.com/oauth/token;protected override string UserInfoUrl https://gitee.com/api/v5/user;
}
加上注释总共十行如你所见非常方便。如果该平台协议遵循 OAuth2 标准开发那么就这么几行就好了。当然如果不按规矩自定义字段的平台也可以扩展比如微信公众平台。WechatAccessTokenModel.cs AccessToken 类扩展namespace MrHuo.OAuth.Wechat
{public class WechatAccessTokenModel : DefaultAccessTokenModel{[JsonPropertyName(openid)]public string OpenId { get; set; }}
}
继承自 DefaultAccessTokenModel新增字段 OpenId因为获取用户信息需要获取 OpenId所以这里需要它。WechatUserInfoModel.cs 用户信息类using System.Collections.Generic;
using System.Text.Json.Serialization;namespace MrHuo.OAuth.Wechat
{public class WechatUserInfoModel : IUserInfoModel{[JsonPropertyName(nickname)]public string Name { get; set; }[JsonPropertyName(headimgurl)]public string Avatar { get; set; }[JsonPropertyName(language)]public string Language { get; set; }[JsonPropertyName(openid)]public string Openid { get; set; }[JsonPropertyName(sex)]public int Sex { get; set; }[JsonPropertyName(province)]public string Province { get; set; }[JsonPropertyName(city)]public string City { get; set; }[JsonPropertyName(country)]public string Country { get; set; }/// summary/// 用户特权信息json 数组如微信沃卡用户为chinaunicom/// /summary[JsonPropertyName(privilege)]public Liststring Privilege { get; set; }[JsonPropertyName(unionid)]public string UnionId { get; set; }[JsonPropertyName(errmsg)]public string ErrorMessage { get; set; }}
}
这里用户信息字段上边的 [JsonPropertyName(xxxx)] 完全按照文档里的字段写否则获取不到正确的值。如果不需要太多的字段自行删减。WechatOAuth.cs 核心类using System.Collections.Generic;
namespace MrHuo.OAuth.Wechat
{/// summary/// Wechat OAuth 相关文档参考/// parahttps://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html/para/// /summarypublic class WechatOAuth : OAuthLoginBaseWechatAccessTokenModel, WechatUserInfoModel{public WechatOAuth(OAuthConfig oauthConfig) : base(oauthConfig) { }protected override string AuthorizeUrl https://open.weixin.qq.com/connect/oauth2/authorize;protected override string AccessTokenUrl https://api.weixin.qq.com/sns/oauth2/access_token;protected override string UserInfoUrl https://api.weixin.qq.com/sns/userinfo;protected override Dictionarystring, string BuildAuthorizeParams(string state){return new Dictionarystring, string(){[response_type] code,[appid] oauthConfig.AppId,[redirect_uri] System.Web.HttpUtility.UrlEncode(oauthConfig.RedirectUri),[scope] oauthConfig.Scope,[state] state};}public override string GetAuthorizeUrl(string state ){return ${base.GetAuthorizeUrl(state)}#wechat_redirect;}protected override Dictionarystring, string BuildGetAccessTokenParams(Dictionarystring, string authorizeCallbackParams){return new Dictionarystring, string(){[grant_type] authorization_code,[appid] ${oauthConfig.AppId},[secret] ${oauthConfig.AppKey},[code] ${authorizeCallbackParams[code]}};}protected override Dictionarystring, string BuildGetUserInfoParams(WechatAccessTokenModel accessTokenModel){return new Dictionarystring, string(){[access_token] accessTokenModel.AccessToken,[openid] accessTokenModel.OpenId,[lang] zh_CN,};}}
}
乍一看好多内容懵了先别懵我一个一个来说一下protected override Dictionarystring, string BuildAuthorizeParams(string state)
{return new Dictionarystring, string(){[response_type] code,[appid] oauthConfig.AppId,[redirect_uri] System.Web.HttpUtility.UrlEncode(oauthConfig.RedirectUri),[scope] oauthConfig.Scope,[state] state};
}
细心的读者发现了这一段就是为了构造 Authorize Url 时后边的参数列表返回一个 Dictionarystring, string 即可以为微信公众号把 client_id 字段修改为 appid所以这里需要处理一下。public override string GetAuthorizeUrl(string state )
{return ${base.GetAuthorizeUrl(state)}#wechat_redirect;
}
这一段在 Authorize Url 后边缀了个 #wechat_redirect虽然不知道微信在这个参数上做了什么文章PS:知道的朋友言传一下~~但是他文档里写就给他写上吧。protected override Dictionarystring, string BuildGetAccessTokenParams(Dictionarystring, string authorizeCallbackParams)
{return new Dictionarystring, string(){[grant_type] authorization_code,[appid] ${oauthConfig.AppId},[secret] ${oauthConfig.AppKey},[code] ${authorizeCallbackParams[code]}};
}
同理这一段是为了构造 GetAccessToken 接口参数。protected override Dictionarystring, string BuildGetUserInfoParams(WechatAccessTokenModel accessTokenModel)
{return new Dictionarystring, string(){[access_token] accessTokenModel.AccessToken,[openid] accessTokenModel.OpenId,[lang] zh_CN,};
}
同理这一段是为了构造 GetUserInfo 接口参数。可以看到哈这个框架本着自由、开放的原则任何能自定义的地方都可以自定义。还有我原本的出发点并非只针对 OAuth 登录这一个方向我想把他平台里面提供的 API 全部接入进来因为扩展太容易了但是吧时间精力有限再说人上了年纪过了30岁脑袋就不怎么灵光了所以机会留给年轻人。加入贡献 我期待更多的朋友能加入到这个项目中贡献代码也好贡献 appid 资源做测试也好提供意见建议也好。如果你也感兴趣请联系我。如果觉得有用帮到你了贡献一颗幼儿园之星 ⭐点个关注fork 走一波~~PS: 手动调皮相关文档 OAuth2https://oauth.net/2/rfc6749https://tools.ietf.org/html/rfc6749ruanyifenghttp://www.ruanyifeng.com/blog/2019/04/github-oauth.html