ASP.NET Core的身份认证框架IdentityServer4--(3)令牌服务配置访问控制跟UI(可自定义路由)添加
使用密码保护API
OAuth 2.0 资源所有者密码授权允许一个客户端发送用户名和密码到IdentityServer
并获得一个表示该用户的可以用于访问api的Token。
该规范建议仅对“受信任”应用程序使用资源所有者密码授权。 一般来说,当您要验证用户并请求访问令牌时,通常使用交互式OpenID Connect流会更好。
不过,这个授权类型允许我们在IdentityServer
快速入门中引入 用户 的概念,这是我们要展示它的原因。
添加测试用户
就像基于内存存储的资源和客户端一样,对于用户也可以这样做。
在IdentityServer
项目中的的Config.cs
文件中添加如下代码:
/// <summary> /// 测试用户信息 /// </summary> /// <returns></returns> public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { SubjectId = "1", Username = "alice", Password = "password" }, new TestUser { SubjectId = "2", Username = "bob", Password = "password" } }; }
修改Startup.cs
代码如下:
public void ConfigureServices(IServiceCollection services) { // 使用内存存储,密钥,客户端和资源来配置身份服务器。 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources())//添加api资源 .AddInMemoryClients(Config.GetClients())//添加客户端 .AddTestUsers(Config.GetUsers()); //添加测试用户 }
该AddTestUsers
扩展方法做了几件事情引擎盖下
- 增加了对资源所有者密码授权的支持
- 增加了对登录用户界面通常使用的用户相关服务的支持(我们将在下面使用这些信息)
- 添加对基于测试用户的配置文件服务的支持(我们将在下面使用这些信息)
为资源所有者密码授予添加一个客户端
您可以通过更改AllowedGrantTypes
属性,简单地将授予类型的支持添加到我们现有的客户端 。如果您需要您的客户端能够使用绝对支持的两种授权类型。
通常,您希望为资源所有者用例创建单独的客户端,请将以下内容添加到Config.cs
的GetClients
方法中:
/// <summary> /// 客服端配置 /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "client", // 没有交互性用户,使用 clientid/secret 实现认证。 AllowedGrantTypes = GrantTypes.ClientCredentials, // 用于认证的密码 ClientSecrets = { new Secret("secret".Sha256()) }, // 客户端有权访问的范围(Scopes) AllowedScopes = { "api1" } }, // resource owner password grant client new Client { ClientId = "ro.client", //使用用户名密码访问 AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } } };
}
使用密码授权请求一个令牌
客户端看起来跟之前客户端证书授权的客户端是相似的。主要差别在于现在的客户端将会以某种方式收集用户密码,然后在令牌请求期间发送到令牌服务。
IdentityModel
的TokenClient
在这里再次为我们提了供帮助:
//请求令牌 var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret"); var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password", "api1"); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json); Console.WriteLine("\n\n");
当您将令牌发送给令牌端点时,您会注意到与客户端凭据授权相比有一点小但重要的区别。访问令牌现在将包含sub
唯一标识用户的声明。这个“子”声明可以通过在调用API之后检查内容变量来看到,并且也将由控制台应用程序显示在屏幕上。
声明的存在(或不存在)sub
使API能够区分代表客户的呼叫和代表用户的调用。
使用OpenID Connect添加用户验证
OpenID Connect 1.0是OAuth 2.0协议之上的一个简单的身份层。 它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似REST的方式获取关于最终用户的基本配置文件信息。
OpenID Connect允许所有类型的客户端(包括基于Web的移动和JavaScript客户端)请求和接收关于认证会话和最终用户的信息。 规范套件是可扩展的,允许参与者使用可选功能,例如身份数据的加密,OpenID提供商的发现和会话管理。
添加UI
OpenID Connect所需的所有协议支持已经内置到IdentityServer
中。您需要提供登录,注销,同意和错误所需的UI部分。
虽然每个IdentityServer
实现的外观跟工作流程可能总是不同,但我们提供了一个基于MVC的示例UI,您可以使用它作为起始页。
这个用户界面可以在Quickstart UI仓库中找到。下载解压后把Quickstart
、Views
、wwwroot
这三个文件夹放入您的IdentityServer
应用程序。
注:如需使用UI资源,您还需要将MVC添加到主机应用程序中,无论是在DI系统还是管道中。ConfigureServices
使用AddMvc
扩展方法添加MVC 。
修改Startup.cs
中的ConfigureServices
和Configure
,完整代码如下:
public class Startup { // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { // 使用内存存储,密钥,客户端和资源来配置身份服务器。 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources())//添加api资源 .AddInMemoryClients(Config.GetClients())//添加客户端 .AddTestUsers(Config.GetUsers()); //添加测试用户 //注册mvc服务 services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } //添加到HTTP管道中。 app.UseIdentityServer(); //添加静态资源访问 app.UseStaticFiles(); //添加mvc到管道中 app.UseMvcWithDefaultRoute(); }
配置完成后启动项目,访问http://localhost:5000/Account/login
,展示如下:
WEB端配置访问控制
接下来,将向web端解决方案添加一个MVC应用程序。使用ASP.NET核心“Web应用程序”(即MVC)模板。不要在向导中配置“身份验证”设置 - 您将在此快速入门中手动执行此操作。创建项目后,将应用程序配置为使用端口5002(有关如何执行此操作的说明,请参阅概述部分)。
要将OpenID Connect身份验证的支持添加到MVC应用程序,请将以下内容添加到Startup.cs
的ConfigureServices
中
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; }); }
AddAuthentication
将认证服务添加到DI。作为主装置来验证用户(通过我们使用一个cookie "Cookies"
为DefaultScheme
)。我们设置为DefaultChallengeScheme
,"oidc"
因为当我们需要用户登录时,我们将使用OpenID Connect方案。
然后我们使用AddCookie
添加可以处理cookie的处理程序。
最后,AddOpenIdConnect
用于配置执行OpenID Connect协议的处理程序。在Authority
表明我们信任IdentityServer
。然后,我们通过身份认证这个客户ClientId
。 SignInScheme
用于在OpenID Connect协议完成后使用cookie处理程序发出cookie。并SaveTokens
用于在cookie中保存来自IdentityServer
的令牌(因为它们将在稍后需要)。
另外,我们关闭了JWT声明类型映射,以允许众所周知的声明(例如'sub'和'idp')通过非混淆的声明:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
然后要确保认证服务执行每个请求,在Startup.cs
中加入UseAuthentication
到Configure
中:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
认证中间件应该在管道中的MVC之前添加。
最后是触发认证握手。为此,转到控制器并添加[Authorize]
。还要修改该操作的视图以显示用户的声明,例如:
<dl> @foreach (var claim in User.Claims) { <dt>@claim.Type</dt> <dd>@claim.Value</dd> } </dl>
如果您现在使用浏览器导航到该控制器,将尝试重定向到IdentityServer
- 这将导致错误,因为MVC客户端尚未注册。
添加对OpenID Connect Identity Scopes的支持
与OAuth 2.0类似,OpenID Connect也使用范围概念。同样,范围表示您想要保护的以及客户想要访问的内容。与OAuth相比,OIDC中的范围不代表API,而是用户标识,名称或电子邮件地址等身份数据。
通过添加一个新的帮助器来添加对标准openid
和profile
的范围支持,在Config.cs类中
创建一个IdentityResource
对象集合:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; }
注意:所有的标准范围和相应的权利要求都可以在OpenID Connect 规范中找到
然后,您需要将这些身份资源添加到您的IdentityServer
中的Startup.cs
。将AddInMemoryIdentityResources
添加到使用扩展方法AddIdentityServer()
中:
public void ConfigureServices(IServiceCollection services) { // 使用内存存储,密钥,客户端和资源来配置身份服务器。 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources())//添加api资源 .AddInMemoryClients(Config.GetClients())//添加客户端 .AddTestUsers(Config.GetUsers()) //添加测试用户 .AddInMemoryIdentityResources(Config.GetIdentityResources());//添加对OpenID Connect的支持 //注册mvc服务 services.AddMvc(); }
为OpenID Connect隐式流添加客户端
最后是将MVC客户端的新配置条目添加到IdentityServer
。
基于OpenID Connect的客户端与我们迄今添加的OAuth 2.0客户端非常相似。但是由于OIDC中的流程始终是交互式的,我们需要在配置中添加一些重定向URL。
修改Config.cs
的GetClients
代码如下:
public static IEnumerable<Client> GetClients() { return new List<Client> {//定义mvc客服端凭证 new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.Implicit, //关闭是否返回身份信息界面 RequireConsent=false, // 登录成功后重定向地址 RedirectUris = { "http://localhost:5002/signin-oidc" }, //注销成功后的重定向地址 PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } }; }
测试客户端
现在,所有的东西都应该在MVC客户端上。
通过导航到受保护的控制器操作来触发身份验证握手。您应该看到重定向到IdentityServer
的登录页面。
成功登录后,用户将看到同意界面。在这里,用户可以决定是否要将他的身份信息发布到客户端应用程序。
注意:可以使用客户端对象上的RequireConsent
属性以每个客户端为基础关闭如下界面,这里可以通过修改Config.cs
中的GetClients
所定义的客服端信息中关闭跳转到该界面。
最后浏览器重定向到客户端应用程序,显示用户的声明
注意:在开发过程中,您有时可能会看到一个异常,说明令牌无法验证。这是因为签名密钥材料是即时创建的,并且只保存在内存中。当客户端和IdentityServer
不同步时,会发生此异常。只需在客户端重复操作,下次元数据已经追上,一切都应该正常工作。
添加注销
向MVC客户端添加注销。使用IdentityServer
等身份验证服务,仅清除本地应用程序Cookie是不够的。此外,您还需要往身份服务器往返,以清除中央单一登录会话。
确切的协议步骤在OpenID Connect中间件内实现,只需将以下代码添加到某个控制器即可触发注销:
public async Task Logout() { await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); }
这将清除本地cookie,然后重定向到IdentityServer
。IdentityServer
将清除它的cookie,然后给用户一个链接返回到MVC应用程序。
添加自定义返回身份信息
如上所述,OpenID Connect中间件默认要求配置文件范围。这个范围还包括像名字或网站这样的声明。
让我们将这些声明添加到用户,以便IdentityServer
可以将它们放入身份令牌中,这里我们修改下之前添加的测试用户信息,修改如下:
public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { SubjectId = "1", Username = "alice", Password = "password", //自定义返回数据 Claims = new [] { new Claim("name", "Alice"), new Claim("website", "https://alice.com") } }, new TestUser { SubjectId = "2", Username = "bob", Password = "password", //自定义返回数据 Claims = new [] { new Claim("name", "Bob"), new Claim("website", "https://bob.com") } } }; }
下一次进行身份验证时,您的声明页面现在将显示额外的声明信息。
随意添加更多的声明 - 也更多的范围。ScopeOpenID Connect
中间件的属性是您配置哪些范围将在身份验证期间发送到IdentityServer
的位置。
值得注意的是,检索令牌的索赔是一个可扩展性点IProfileService
。由于我们正在使用AddTestUsers
,TestUserProfileService
默认情况下使用。你可以在这里 查看源代码,看看它是如何工作的。
自定义UI(不使用官方UI)
修ConfigureServices如下,自己定义路由地址,一般情况下建议使用官方ui修改。代码如下:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddIdentityServer(options => { //用户交互配置 主要涉及到入口地址参数等 options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions { LoginUrl = "/Account/Login", LogoutUrl = "/Account/Logout", ConsentUrl = "/Account/Consent", ErrorUrl = "/Account/Error", LoginReturnUrlParameter = "ReturnUrl", LogoutIdParameter = "logoutId", ConsentReturnUrlParameter = "ReturnUrl", ErrorIdParameter = "errorId", CustomRedirectReturnUrlParameter = "ReturnUrl", CookieMessageThreshold = 5 }; }).AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources())//添加api资源 .AddInMemoryClients(Config.GetClients());//添加客户端; }
本章节官方文档地址:点一下