aspnet登录界面代码_SPA+.NET Core3.1 GitHub第三方授权登录
GitHub第三方授權(quán)登錄
有許多文章都講過(guò)GitHub第三方授權(quán)登錄,但就是沒(méi)有.NET?Core配合前后端分離的項(xiàng)目(Vue,React)的實(shí)踐。所以本文以前后端分離項(xiàng)目中如何在授權(quán)登錄后,生成Token的過(guò)程。
后端 .NET Core,使用類(lèi)庫(kù)AspNet.Security.OAuth.GitHub
前端技術(shù)棧如下:VUE+Vue-Router+axios
AspNet.Security.OAuth.GitHub
GitHub?https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
GitHub授權(quán)登錄
什么配置的過(guò)程不說(shuō)了,有許多文章都講過(guò),這里不詳細(xì)展開(kāi)。直接看配置的內(nèi)容。
可參考如下網(wǎng)站
GitHub 第三方登錄 https://www.jianshu.com/p/78d186aeb526
給你的網(wǎng)站添加第三方登錄以及短信驗(yàn)證功能 https://juejin.im/post/5dfb04cee51d45583a66c2f3
配置后,我們能得到一個(gè)client_id,client_secret,這里是我創(chuàng)建的一個(gè)應(yīng)用test。配置如下。
得到的client_id,client_secret在下面會(huì)用到。
client_id:0be6b05fc717bfc4fb67client_secret:dcaced9f176afba64e89d88b9b06ffc4a887a609
瀏覽器打開(kāi)下面地址,Get請(qǐng)求,替換自己的client_id
https://github.com/login/oauth/authorize?client_id=0be6b05fc717bfc4fb67&redirect_uri=https://localhost:5001/signin-github會(huì)重定向到
https://localhost:5001/signin-github?code=07537a84d12bbae08361
這個(gè)code放到下面的請(qǐng)求中,會(huì)得到一個(gè)獲取access_token
以POST方式(PostMan去請(qǐng)求)
https://localhost:5001/signin-github?code=07537a84d12bbae08361
這個(gè)code放到下面的請(qǐng)求中,獲取access_token POST方式(PostMan去請(qǐng)求)
https://github.com/login/oauth/access_token?client_id=0be6b05fc717bfc4fb67&client_secret=dcaced9f176afba64e89d88b9b06ffc4a887a609&code=07537a84d12bbae08361Get方式請(qǐng)求如下地址,攜帶上一個(gè)POST的access_token值。
https://api.github.com/user?access_token=787506afa3271d077b98f18af56d7cfdc8db43b4然后就能獲取用戶(hù)信息
{"login": "luoyunchong",
"id": 18613266,
"node_id": "MDQ6VXNlcjE4NjEzMjY2",
"avatar_url": "https://avatars1.githubusercontent.com/u/18613266?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/luoyunchong",
"html_url": "https://github.com/luoyunchong",
"followers_url": "https://api.github.com/users/luoyunchong/followers",
"following_url": "https://api.github.com/users/luoyunchong/following{/other_user}",
"gists_url": "https://api.github.com/users/luoyunchong/gists{/gist_id}",
"starred_url": "https://api.github.com/users/luoyunchong/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/luoyunchong/subscriptions",
"organizations_url": "https://api.github.com/users/luoyunchong/orgs",
"repos_url": "https://api.github.com/users/luoyunchong/repos",
"events_url": "https://api.github.com/users/luoyunchong/events{/privacy}",
"received_events_url": "https://api.github.com/users/luoyunchong/received_events",
"type": "User",
"site_admin": false,
"name": "IGeekFan",
"company": null,
"blog": "https://blog.igeekfan.cn",
"location": null,
"email": "luoyunchong@foxmail.com",
"hireable": null,
"bio": "學(xué)習(xí)之路漫漫無(wú)期。",
"public_repos": 14,
"public_gists": 0,
"followers": 16,
"following": 11,
"created_at": "2016-04-22T10:33:44Z",
"updated_at": "2019-12-21T14:49:33Z"
}
.NET Core3.1
講完了GitHub授權(quán)登錄的過(guò)程,我們來(lái)說(shuō)一個(gè)在.NET?Core下的實(shí)踐。以下代碼為主要代碼,完整代碼請(qǐng)看查看最下面的鏈接。
前端運(yùn)行在:http://localhost:8081
后端運(yùn)行在:https://localhost:5001
本地測(cè)試時(shí)GitHub回調(diào)地址設(shè)置:?https://localhost:5001/signin-github。
GitHub回調(diào)地址設(shè)置 http(s)://ip:端口/signin-github
1. Github授權(quán)登錄回調(diào)地址明明填寫(xiě)的是后端的地址,那后端怎么把結(jié)果通知前端呢?
我們先來(lái)了解一些登錄的流程。
GitHub登錄流程:前端放一個(gè)GitHub登錄的按鈕,點(diǎn)擊后,調(diào)用signin方法,然后調(diào)用后臺(tái)接口signin方法。
提供參數(shù)provider為GitHub,
redirectUrl為GitHub授權(quán)登錄后,回調(diào)signin-github后,后端要重定向的地址,這里填前端的一個(gè)路由。
<script>export default {name: "app",components: {},methods: {
signin() {window.open("https://localhost:5001/signin?provider=GitHub&redirectUrl=http://localhost:8080/login-result"
);
}
}
};script>
2. 后端只提供了signin,signin-callback路由,沒(méi)有signin-github,那github上配置的路由是怎么回調(diào)回來(lái)呢?
google-登錄,微軟文檔,在這個(gè)文檔中有詳細(xì)的關(guān)于外部登錄設(shè)置,其中有一個(gè)更改默認(rèn)回調(diào) URI,通過(guò) AddGitHub中的CallbackPath屬性配置。
介紹了回調(diào)地址應(yīng)配置signin-google,所以這里應(yīng)該是signin-github,他是可以配置的,不需要自己寫(xiě)程序處理signin-google這個(gè)路由,內(nèi)部有中間件已經(jīng)處理了。
3. 回調(diào)到signin-github后,后端怎么處理,才能讓前端刷新。獲取登錄后的信息呢。
具體上面的根據(jù)code獲取access_token,根據(jù)access_token獲取用戶(hù)的信息的過(guò)程,這些處理的過(guò)程,都不需要我們自己處理。我們可以用直接獲取用戶(hù)信息。
一個(gè)方法SignIn,只要return Challenge(properties, provider);,
provider 為 GitHub,
properties 是對(duì)象 var properties = new AuthenticationProperties { RedirectUri = url };
url:https://localhost:5001/signin-callback?provider=GitHub&redirectUrl=http://localhost:8080/login-result
前臺(tái)傳的參數(shù)為GitHub和redirectUrl.這個(gè)url是回調(diào)sigin-github后,這個(gè)類(lèi)庫(kù)幫我們重定向的地址。我們只要拼接好地址,讓他回調(diào)到signin-callback方法即可。
var request = _contextAccessor.HttpContext.Request;var url = $"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}-callback?provider={provider}&redirectUrl={redirectUrl}";
需要注入
public void ConfigureServices(IServiceCollection services){
services.AddSingleton();
}private readonly IHttpContextAccessor _contextAccessor;
public AuthenticationController( IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
signin-callback方法,我們可通過(guò)如下方法獲取到授權(quán)登錄的email值,name值。
var authenticateResult = await _contextAccessor.HttpContext.AuthenticateAsync(provider);string email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;
string name = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value;
代碼實(shí)現(xiàn)
打開(kāi)NuGet包管理,安裝包
Install-Package AspNet.Security.OAuth.GitHubappSettings.json
"Authentication": {"GitHub": {
"ClientId": "0be6b05fc717bfc4fb67",
"ClientSecret": "dcaced9f176afba64e89d88b9b06ffc4a887a609"
}
}
add擴(kuò)展方法 因?yàn)槲覀円梢粋€(gè)Token值,所以我們需要配置Jwt, 這里增加一個(gè)擴(kuò)展方法。
public static class JwtConfiguration{
public static void AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration)
{
services.AddAuthentication(opts =>
{
opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie().AddGitHub(options =>
{
options.ClientId = configuration["Authentication:GitHub:ClientId"];
options.ClientSecret = configuration["Authentication:GitHub:ClientSecret"];
});
}
}
默認(rèn)情況下,如頭像,email,是沒(méi)有獲取的。
.AddGitHub(options =>{
options.ClientId = configuration["Authentication:GitHub:ClientId"];
options.ClientSecret = configuration["Authentication:GitHub:ClientSecret"];
//options.CallbackPath = new PathString("~/signin-github");//與GitHub上的回調(diào)地址相同,默認(rèn)即是/signin-github
options.Scope.Add("user:email");
//authenticateResult.Principal.FindFirst(LinConsts.Claims.AvatarUrl)?.Value; 得到GitHub頭像
options.ClaimActions.MapJsonKey(LinConsts.Claims.AvatarUrl, "avatar_url");
options.ClaimActions.MapJsonKey(LinConsts.Claims.BIO, "bio");
options.ClaimActions.MapJsonKey(LinConsts.Claims.BlogAddress, "blog");
});
#其中LinConsts類(lèi)為靜態(tài)常量
public static class LinConsts
{
public static class Claims
{
public const string BIO = "urn:github:bio";
public const string AvatarUrl = "urn:github:avatar_url";
public const string BlogAddress = "urn:github:blog";
}
}
startup.cs
ConfigureServices中配置此服務(wù)
services.AddSingletonHttpContextAccessor>();services.AddJwtConfiguration(Configuration);
創(chuàng)建AuthenticationController.cs 增加SignIn,用于處理用戶(hù)授權(quán)成功后,重定回signin-callback,并將參數(shù)帶回。
private readonly IHttpContextAccessor _contextAccessor;private readonly IConfiguration _configuration;
public AuthenticationController(IHttpContextAccessor contextAccessor, IConfiguration configuration)
{
_contextAccessor = contextAccessor;
_configuration = configuration;
}
[HttpGet("~/signin")]
public async TaskSignIn(string provider, string redirectUrl)
{
var request = _contextAccessor.HttpContext.Request;
var url =
$"{request.Scheme}://{request.Host}{request.PathBase}{request.Path}-callback?provider={provider}&redirectUrl={redirectUrl}";
var properties = new AuthenticationProperties { RedirectUri = url };
properties.Items["LoginProviderKey"] = provider;
return Challenge(properties, provider);
}
在signin方法中,用戶(hù)點(diǎn)擊授權(quán)后(第一次),會(huì)根據(jù)其傳遞的URL,重定向到這個(gè)地址,signin-callback,參數(shù)也會(huì)一同攜帶。provider為GitHub,redirectUrl為:http://localhost:8081/login-result.
[HttpGet("~/signin-callback")]public async TaskHome(string provider = null, string redirectUrl = "")
{
var authenticateResult = await _contextAccessor.HttpContext.AuthenticateAsync(provider);if (!authenticateResult.Succeeded) return Redirect(redirectUrl);
var openIdClaim = authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier);if (openIdClaim == null || string.IsNullOrWhiteSpace(openIdClaim.Value))
return Redirect(redirectUrl);//TODO 記錄授權(quán)成功后的信息 string email = authenticateResult.Principal.FindFirst(ClaimTypes.Email)?.Value;string name = authenticateResult.Principal.FindFirst(ClaimTypes.Name)?.Value;string gitHubName = authenticateResult.Principal.FindFirst(GitHubAuthenticationConstants.Claims.Name)?.Value;string gitHubUrl = authenticateResult.Principal.FindFirst(GitHubAuthenticationConstants.Claims.Url)?.Value;//startup 中 AddGitHub配置項(xiàng) options.ClaimActions.MapJsonKey(LinConsts.Claims.AvatarUrl, "avatar_url");string avatarUrl = authenticateResult.Principal.FindFirst(LinConsts.Claims.AvatarUrl)?.Value;
return Redirect($"{redirectUrl}?openId={openIdClaim.Value}");
}
這時(shí)候我們能獲取用戶(hù)信息了。那么前端怎么辦呢。我們寫(xiě)個(gè)方法,獲取用戶(hù)信息,看看效果。
瀏覽器直接打開(kāi)能得到github的id。
axios GET請(qǐng)求?https://localhost:5001/OpenId?得到null
public async Task<string> OpenId(string provider = null)
{
var authenticateResult = await _contextAccessor.HttpContext.AuthenticateAsync(provider);
if (!authenticateResult.Succeeded) return null;
var openIdClaim = authenticateResult.Principal.FindFirst(ClaimTypes.NameIdentifier);
return openIdClaim?.Value;
}
我記得之前傳Token時(shí),后臺(tái)是可以這樣獲取的。
[HttpGet("~/GetOpenIdByToken")]public string GetOpenIdByToken()
{
return User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
LoginResult.vue在created生命周期中。都是得到null
axios({methods: "get",
url: "https://localhost:5001/OpenId?provider=GitHub"
})
.then(function(response) {
// handle success
console.log(response);
})
axios({
methods: "get",
url: "https://localhost:5001/GetOpenIdByToken"
})
.then(function(response) {
// handle success
console.log(response);
})
為什么呢???
因?yàn)榍昂蠖朔蛛x,不是基于Cookies的。http是無(wú)狀態(tài)的。每次請(qǐng)求無(wú)法區(qū)分用戶(hù)的。我們可以根據(jù)當(dāng)前的ClaimsPrincipal,根據(jù)JWT生成相應(yīng)的Token,axios請(qǐng)求時(shí),放到headers中。
安裝包
Install-Package Microsoft.AspNetCore.Authentication.JwtBearerAppSettings.json配置改成
"Authentication": {"JwtBearer": {
"SecurityKey": "JWTStudyWebsite_DI20DXU3",
"Issuer": "JWTStudy",
"Audience": "JWTStudyWebsite"
},
"GitHub": {
"ClientId": "0be6b05fc717bfc4fb67",
"ClientSecret": "dcaced9f176afba64e89d88b9b06ffc4a887a609"
}
}
在signin-callback路由中,得到authenticateResult.Principal,其中默認(rèn)包含了(id,login,name,url),授權(quán)得到eamil,另外MapJsonKey擴(kuò)展了以下字段(avatar_url、bio、blog)
var authenticateResult = await _contextAccessor.HttpContext.AuthenticateAsync(provider);string token = this.CreateToken(authenticateResult.Principal);
根據(jù)ClaimsPrincipal值生成token值。
private string CreateToken(ClaimsPrincipal claimsPrincipal){
var handler = new JwtSecurityTokenHandler();
var key = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Authentication:JwtBearer:SecurityKey"]));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
_configuration["Authentication:JwtBearer:Issuer"],
_configuration["Authentication:JwtBearer:Audience"],
claimsPrincipal.Claims,
expires: DateTime.Now.AddMinutes(30),
signingCredentials: credentials
);
return handler.WriteToken(token);
}
這里的claimsPrincipal是什么呢。簡(jiǎn)單的說(shuō)就是一個(gè)存有g(shù)ithub授權(quán)信息的對(duì)象,可以解析出對(duì)應(yīng)的Clamis,這里其實(shí)就是用了Clamis的屬性值。
| id、name,url,email,avatar_url等 | 由多組Claim組成,這里可指GitHub授權(quán)登錄后得到的那個(gè)對(duì)象。 | ClaimsIdentity的持有者 |
具體Jwt的生成與配置項(xiàng)。這里不詳細(xì)說(shuō)明??梢钥催@個(gè)示例(.NET Core2.2)https://github.com/luoyunchong/BasicTemplate
AddJwtConfiguration改成如下內(nèi)容
public static void AddJwtConfiguration(this IServiceCollection services, IConfiguration configuration){
services.AddAuthentication(opts =>
{
opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Audience = configuration["Authentication:JwtBearer:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.ASCII.GetBytes(configuration["Authentication:JwtBearer:SecurityKey"])),
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = configuration["Authentication:JwtBearer:Issuer"],
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = configuration["Authentication:JwtBearer:Audience"],
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here
//ClockSkew = TimeSpan.Zero
};
}).AddGitHub(options =>
{
options.ClientId = configuration["Authentication:GitHub:ClientId"];
options.ClientSecret = configuration["Authentication:GitHub:ClientSecret"];
//options.CallbackPath = new PathString("~/signin-github");//與GitHub上的回調(diào)地址相同,默認(rèn)即是/signin-github
options.Scope.Add("user:email");
//authenticateResult.Principal.FindFirst(LinConsts.Claims.AvatarUrl)?.Value; 得到GitHub頭像
options.ClaimActions.MapJsonKey(LinConsts.Claims.AvatarUrl, "avatar_url");
options.ClaimActions.MapJsonKey(LinConsts.Claims.BIO, "bio");
options.ClaimActions.MapJsonKey(LinConsts.Claims.BlogAddress, "blog");
});
}
前端完整的LoginResult.vue代碼
即 localhost:8080/login-result
<template><div class="main"><h2>Login-Resulth2><p>OpenId:{{OpenId1}}p><p>GetOpenIdByToken{{OpenId2}}p>div>template><script>const axios = require("axios");function parseUrlParams() {if (window.location.search.length <= 0) return false;var info = window.location.search.slice(1);var result = {};info.split("&").forEach(item => {
result[decodeURIComponent(item.split("=")[0])] = decodeURIComponent(
item.split("=")[1]
);
});return result;
}export default {name: "LoginResult",props: {},
data() {return {OpenId1: "",OpenId2: ""
};
},
created() {var result = parseUrlParams();if (!(result && result.token)) {
alert("無(wú)效的登錄");return;
}var that = this;
axios({methods: "get",url: "https://localhost:5001/OpenId?provider=GitHub",headers: {Authorization: "Bearer " + result.token
}
}).then(function(response) {console.log(response);
that.OpenId1 = response.data;
});
axios({methods: "get",url: "https://localhost:5001/GetOpenIdByToken",headers: {Authorization: "Bearer " + result.token
}
}).then(function(response) {console.log(response);
that.OpenId2 = response.data;
});
}
};script>
前端運(yùn)行
yarn installyarn serve
點(diǎn)擊GitHub登錄,第一次,我們會(huì)跳到github的網(wǎng)站,然后登錄成功,重定向我們的后端,可以看到GetOpenIdByToken方法根據(jù)生成的token值,解析出了用戶(hù)id,這樣前端在login-result這個(gè)組件中,把token保存好,并重定向自己的主頁(yè),獲取用戶(hù)所有信息即可。
data: 18613266status: 200
config: {url: "https://localhost:5001/GetOpenIdByToken"}
OpenId?provider=GitHub則得不到數(shù)據(jù),只能瀏覽器直接請(qǐng)求https://localhost:5001/OpenId?provider=GitHub,才能到github 的id。這個(gè)適應(yīng)于前后端不分離,或者屬于之前我們經(jīng)常使用MVC結(jié)構(gòu),同一域名下,同一端口,基于Cookies登錄的判斷。
參考
.net Core2.2 WebApi通過(guò)OAuth2.0實(shí)現(xiàn)微信登錄
AspNetCore3.0 和 JWT
用戶(hù)系統(tǒng)設(shè)計(jì):第三方授權(quán)、賬號(hào)綁定及解綁(下)
Demo 示例
GitHub?https://github.com/luoyunchong/dotnetcore-examples/blob/master/aspnetcore-oatuth2
總結(jié)
以上是生活随笔為你收集整理的aspnet登录界面代码_SPA+.NET Core3.1 GitHub第三方授权登录的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何修改matlab中的语句,求大神帮忙
- 下一篇: vb.net html标签,VB.Net