ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证
@@oidc connect/authorize
在上一章中,我们了解到,Cookie
认证是一种本地认证方式,通常认证与授权都在同一个服务中,也可以使用Cookie
共享的方式分开部署,但局限性较大,而如今随着微服务的流行,更加偏向于将以前的单体应用拆分为多个服务并独立部署,而此时,就需要一个统一的认证中心,以及一种远程认证方式,本文就来介绍一下如今最为流行的远程认证方式:OAuth
和 OpenID Connect
。
OAuth 2.0
在介绍OAuth
之前,我们先简单介绍一下OpenID
。OpenID
是一个以用户为中心的数字身份识别框架,它具有开放、分散性。OpenID
的创建基于这样一个概念:我们可以通过 URI (又叫URL或网站地址)来认证一个网站的唯一身份,同理,我们也可以通过这种方式来作为用户的身份认证。
OpenID
的认证非常简单,当你访问需要认证的A网站时,A网站要求你输入你的OpenID
用户名,然后会跳转你的OpenID服务网站,输入用户名密码验证通过后,再跳回A网站,而些时已经显示认证成功。除了一处注册,到处通行外,OpenID还可以使所有支持OpenID的网站共享用户资源,而用户可以控制哪些信息可以被共享,例如姓名、地址、电话号码等。
而OAuth
是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,在官网对其是这样定义的:
An open protocol to allow secure API authorization in a simple and standard method from desktop and web applications.
OAuth关注的是第三方应用访问其受保护资源的能力,而OpenID关注的是第三方应用获取用户身份的能力。
如今大多网站都已不再支持OpenID,最为流行的是OAuth 2.0 (在本文中提到OAuth也均指2.0版本),而OpenID
的最新版是OpenID Connect,是OpenID的第三代技术,下文会来介绍。
关于OAuth的介绍,网上非常之多,本文就不再过多叙述,而是主要讲解如何在 ASP.NET Core 中使用 OAuth 认证。如果你对 OAuth 并不了解,那么建议先去网上查看一下这方面的资料,再来阅读本文。而在本文中提到的OAuth
认证指的是 ASP.NET Core 中的一种认证方式,而OAuth
本身只是一种授权协议,希望不要混淆。
在 OAuth
协议中包含以下四种授权模式:
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
在以上四种模式中,只有第一种Code
模式需要服务端参与,其它的只在客户端就可完成,因此,在 ASP.NET Core 的 OAuth
认证中,也就只有Code
模式。
用例
先来看一下具体的用法:
对于项目的创建可以参考上一章,然后添OAuth
的Nuget包引用:
dotnet add package Microsoft.AspNetCore.Authentication.OAuth --version 2.0.0
- 1
然后在ConfigureServices
配置服务:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OAuthDefaults.DisplayName;
})
.AddCookie()
.AddOAuth(OAuthDefaults.DisplayName, options =>
{
options.ClientId = "oauth.code";
options.ClientSecret = "secret";
options.AuthorizationEndpoint = "https://oidc.faasx.com/connect/authorize";
options.TokenEndpoint = "https://oidc.faasx.com/connect/token";
options.CallbackPath = "/signin-oauth";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SaveTokens = true;
// 事件执行顺序 :
// 1.创建Ticket之前触发
options.Events.OnCreatingTicket = context => Task.CompletedTask;
// 2.创建Ticket失败时触发
options.Events.OnRemoteFailure = context => Task.CompletedTask;
// 3.Ticket接收完成之后触发
options.Events.OnTicketReceived = context => Task.CompletedTask;
// 4.Challenge时触发,默认跳转到OAuth服务器
// options.Events.OnRedirectToAuthorizationEndpoint = context => context.Response.Redirect(context.RedirectUri);
});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
上面前六个参数是都必填的(在IdentityServer
中Scope
必须包含openid
),否则会报错,SaveTokens
属性用来设置是否将OAuth
服务器返回的Token
信息保存到AuthenticationProperties
中。
https://oidc.faasx.com 是我使用 IdentityServer4
搭建的一个OIDC
服务,源码地址在 IdentityServerSample ,而本文中并不会涉及到IdentityServer的相关知识,后续有机会再来单独介绍一下它。
最后,注册中间件:
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
// 授权,与上一章Cookie认证中的实现一样
app.UseAuthorize();
// 我的信息
app.Map("/profile", builder => builder.Run(async context =>
{
await context.Response.WriteHtmlAsync(async res =>
{
await res.WriteAsync($"<h1>你好,当前登录用户: {HttpResponseExtensions.HtmlEncode(context.User.Identity.Name)}</h1>");
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/Account/Logout\">退出</a>");
await res.WriteAsync($"<h2>AuthenticationType:{context.User.Identity.AuthenticationType}</h2>");
await res.WriteAsync("<h2>Claims:</h2>");
await res.WriteTableHeader(new string[] { "Claim Type", "Value" }, context.User.Claims.Select(c => new string[] { c.Type, c.Value }));
// 在第一章中介绍过HandleAuthenticateOnceAsync方法,在此调用并不会有多余的性能损耗。
var result = await context.AuthenticateAsync();
await res.WriteAsync("<h2>Tokens:</h2>");
await res.WriteTableHeader(new string[] { "Token Type", "Value" }, result.Properties.GetTokens().Select(token => new string[] { token.Name, token.Value }));
});
}));
// 退出
app.Map("/Account/Logout", builder => builder.Run(async context =>
{
await context.SignOutAsync();
context.Response.Redirect("/");
}));
// 首页
app.Run(async context =>
{
await context.Response.WriteHtmlAsync(async res =>
{
await res.WriteAsync($"<h2>Hello OAuth Authentication</h2>");
await res.WriteAsync("<a class=\"btn btn-default\" href=\"/profile\">我的信息</a>")