授权模式
OAuth2.0 定义了四种授权模式:
Implicit:简化模式;直接通过浏览器的链接跳转申请令牌。
Client Credentials:客户端凭证模式;该方法通常用于服务器之间的通讯;该模式仅发生在Client与Identity Server之间。
Resource Owner Password Credentials:密码模式
Authorization Code:授权码模式;
5.1. Client Credentials
客户端凭证模式,是最简单的授权模式,因为授权的流程仅发生在Client与Identity Server之间。
该模式的适用场景为服务器与服务器之间的通信。比如对于一个电子商务网站,将订单和物流系统分拆为两个服务分别部署。订单系统需要访问物流系统进行物流信息的跟踪,物流系统需要访问订单系统的快递单号信息进行物流信息的定时刷新。而这两个系统之间服务的授权就可以通过这种模式来实现。
5.2. Resource Owner Password Credentials
Resource Owner其实就是User,所以可以直译为用户名密码模式。密码模式相较于客户端凭证模式,多了一个参与者,就是User。通过User的用户名和密码向Identity Server申请访问令牌。这种模式下要求客户端不得储存密码。但我们并不能确保客户端是否储存了密码,所以该模式仅适用于受信任的客户端。否则会发生密码泄露的危险。该模式不推荐使用。
5.3. Authorization Code
授权码模式是一种混合模式,是目前功能最完整、流程最严密的授权模式。它主要分为两大步骤:认证和授权。
其流程为:
用户访问客户端,客户端将用户导向Identity Server。
用户填写凭证信息向客户端授权,认证服务器根据客户端指定的重定向URI,并返回一个【Authorization Code】给客户端。
客户端根据【Authorization Code】向Identity Server申请【Access Token】
5.4. Implicit
简化模式是相对于授权码模式而言的。其不再需要【Client】的参与,所有的认证和授权都是通过浏览器来完成的。
6. IdentityServer4 集成
通过以上知识点的梳理,我们对OpenId Connect 和OAuth2.0的一些相关概念有了大致认识。而IdentityServer4是为ASP.NET CORE量身定制的实现了OpenId Connect和OAuth2.0协议的认证授权中间件。
所以自然而然我们对IdentityServer4有了基础的认识。下面就来介绍如何集成IdentityServer4。其主要分为三步:
IdentityServer如何配置和启用IdentityServer中间件
Resources如何配置和启用认证授权中间件
Client如何认证和授权
6.1. Identity Server 中间件的配置和启用
作为一个独立的Identity Server,它必须知道哪些资源需要保护,必须知道哪些客户端能够允许访问,这是配置的基础。
所以IdentityServer中间件的配置的核心就是:
配置受保护的资源列表
配置允许验证的Client
public class Startup{ public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() //配置身份资源 .AddInMemoryIdentityResources(Config.GetIdentityResources()) //配置API资源 .AddInMemoryApiResources(Config.GetApiResources()) //预置允许验证的Client .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()); services.AddAuthentication() //添加Google第三方身份认证服务(按需添加) .AddGoogle("Google", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = "434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com"; options.ClientSecret = "3gcoTrEDPPJ0ukn_aYYT6PWo"; }) //如果当前IdentityServer不提供身份认证服务,还可以添加其他身份认证服 务提供商 .AddOpenIdConnect("oidc", "OpenID Connect", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.SignOutScheme = IdentityServerConstants.SignoutScheme; options.Authority = "https://demo.identityserver.io/"; options.ClientId = "implicit"; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加IdentityServer中间件到Pipeline app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
配置完,添加IdentityServer到Pipeline即可。
如果要支持第三方登录服务或自己实现的OpenId Connect服务,则需要额外配置下身份认证中间件。
6.2. Resources的保护配置
配置完Identity Server,接下来我们该思考如何来保护Resources,以及如何将所有的认证和授权请求导流到Identity Server呢?
在此之前,我们还是要梳理下Client访问Resources的请求顺序:
Client请求资源,资源如果需要进行身份认证和授权,则将请求导流到Identity Server。
Identity Server根据Client配置的授权类型,返回【Token】。
Client要能够验证【Token】的正确性。
所以针对要保护的资源,我们需要以下配置:
指定资源是否需要保护;
指定IdentityServer用来进行认证和授权跳转;
Client携带【Token】请求资源。
受保护的资源服务器要能够验证【Token】的正确性。
代码示例:
//使用[Authorize]特性,来显式指定受保护的资源[Route("[controller]")] [Authorize]public class IdentityController : ControllerBase{ [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
public class Startup{ public void ConfigureServices(IServiceCollection services) { services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); //指定认证方案 services.AddAuthentication("Bearer") //添加Token验证服务到DI .AddIdentityServerAuthentication(options => { //指定授权地址 options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; }); } public void Configure(IApplicationBuilder app) { //添加认证中间件到Pipeline app.UseAuthentication(); app.UseMvc(); } }
6.3. Client的请求配置
资源和认证服务器都配置完毕,接下来客户端就可以直接访问了。
如果针对控制台客户端应用,三步走就可以访问Api:
使用DiscoverClient发现Token Endpoint
使用TokenClient请求Access Token
使用HttpClient访问Api
代码示例如下:
// discover endpoints from metadatavar disco = await DiscoveryClient.GetAsync("http://localhost:5000");// request token(使用的是ClientCredentials授权类型)var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1")if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json); Console.WriteLine("\n\n");// call apivar client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken);
如果针对ASP.NET Web控制台客户端,我们先来回答一个问题:
如果Web应用是否需要登录?
如果需要登录,就需要进行身份认证。
身份认证成功后,也就需要会话状态的维持。
回答完上面的问题,我们也就梳理出了配置要点:
添加身份认证中间件
启用Cookie进行会话保持
添加OIDC,使用我们自己定义的IdentityServer提供的认证服务
public void ConfigureServices(IServiceCollection services){ services.AddMvc(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ClientId = "mvc"; options.SaveTokens = true; }); }public void Configure(IApplicationBuilder app, IHostingEnvironment env { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
7. 最后
本文通过介绍IdentityServer4涉及到的术语和相关概念,再结合官方实例,梳理了集成IdentityServer4的大致思路。而关于如何与ASP.NET Identity、EF Core集成,本文并未涉及,详参官方文档。
Identity Server 官方文档
dentityServer4 中文文档与实战
ASP.NET Core 认证与授权[4]:JwtBearer认证
Bearer Authentication
JSON Web Token
理解OAuth 2.0
Identity Server 授权类型
欢迎加群讨论技术,群:677373950(满了,可以加,但通过不了),2群:656732739