Asp.Net Core 2.0 中的自定义身份验证和授权






4.31/5 (22投票s)
在本文中,我将解释如何在 Web、WebApi 以及其他客户端中实现身份验证和授权。
引言
身份验证和授权是任何 Web 应用程序中的两个重要概念。现在我们将通过使用 oAuth2.0 和 OpenID 标准来实现这一点,以及如何创建一个支持 Web、移动、WebApi 等多个应用程序的集中式身份服务器。
需要理解的概念
如果您不熟悉以下主题,请尝试了解它们。如果您已经熟悉这些内容,可以跳过此部分。
基于令牌的访问:令牌只不过是在访问某物之前,我们需要获取一个令牌。
示例:如果我们尝试与现实生活相关联,会更容易理解。假设我们计划去看电影。首先,我们会去某个电影网站并预订我们计划好的电影票。预订完门票后,我们将收到一个如下所示的条形码。
那个条形码只不过是令牌(秘密密码),到了电影院,他们会扫描这个条形码,如果它是有效的令牌,我们就可以观看电影。还有一点需要记住的是,这个令牌仅对本次放映有效,下次来看另一场电影时,需要携带新的有效令牌。在这里,无需将我们预订电影票网站的用户名和密码提供给保安人员,只需出示条形码即可完成工作。现在我们对令牌的工作原理有了一些了解。
身份服务器:身份服务器是一个独立的服务器,它将维护用户的相关信息,例如用户名、密码、角色或用户的声明等。当 Web 客户端(MVC、WebAPI 等)请求身份验证时,用户将被重定向到此身份服务器。身份服务器验证用户后,如果用户有效,身份服务器将发放令牌并重定向回请求的客户端。在这里,客户端不知道用户凭据,因为在身份服务器中输入用户凭据时,不会有任何信息泄露到客户端,因为它是一个完全隔离的服务器。
示例:当我们打开一些网站时,会有一个选项,可以使用 Google 或 Facebook 登录。一旦我们选择 Google,我们将被重定向到 Google 身份服务器。在我们提供 Google 凭据后,它将向请求的网站发放令牌,表明他是我们身份服务器中的有效用户,Google 会向客户端提供一些信息。因此,我们不必向每个人提供我们的用户名和密码,而是提供一个令牌来验证我们的身份。
OAuth:(授权的开放标准):OAuth 2.0 是授权的行业标准协议。OAuth 2.0 侧重于客户端开发人员的简便性,同时为 Web 应用程序、桌面应用程序、手机和客厅设备提供特定的授权流程。它通过 Http 工作,因此与 Java、Csharp、PHP、Python 等编程语言无关,任何处理 Http 的客户端都可以使用这些身份服务器功能。OAuth 告诉我们如何处理令牌、如何获取令牌、令牌中可以包含哪些信息等等。请访问此链接了解更多关于 OAuth 的信息:https://oauth.ac.cn/2/
OpenID:OpenID Connect 1.0 是 OAuth 2.0 协议之上的一层简单身份。它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,并以可互操作和类 REST 的方式获取有关最终用户的基本配置文件信息。OpenID Connect 允许所有类型的客户端,包括基于 Web、移动和 JavaScript 的客户端,请求和接收有关已验证会话和最终用户的信息。规范套件是可扩展的,允许参与者在需要时使用可选功能,例如加密身份数据、发现 OpenID 提供者以及会话管理。请访问此链接了解更多关于 OpeinID 的信息:http://openid.net/connect/
让我们开始实现。关于我们将要构建的内容的简要介绍。我们将构建一个独立的身份服务器,一旦它启动并运行,我们将构建以下客户端,它们将使用身份服务器进行身份验证。稍后在 Web 应用程序中,我们将看到如何添加授权。
1. WebAPI
2. 使用控制台的客户端应用程序
3. MVC Web 应用程序
身份服务器
先决条件:安装了 .net core 2.0 的 Visual Studio 2017
开发与实现
打开 VS2017,选择 .NET Core 模板,选择 ASP.NET Core Web 应用程序项目类型,然后单击“确定”。
选择“Empty”模板,从下拉列表中选择 ASP.NET Core 2.0 作为框架,然后单击“OK”。 这将创建一个空的 .net core 项目。
解决方案资源管理器将只包含 Startup.cs 和 Program.cs 文件。
右键单击 IdentityServer 项目,然后转到“管理 NuGet 包”。
转到 Browse,在搜索框中键入 IdenityServer4 并按 Enter 键,它将显示 IdentityServer4 相关的包。选择 Brock Allen、Dominick 提供的突出显示的包。
单击右侧的安装
阅读并接受许可协议,单击“I Accept”按钮。
在输出窗口中将显示成功安装的消息。
现在转到 Startup.cs 页面在 Configure 部分添加 IdentityServer 的中间件。
app.UseIdentityServer();
在 ConfigureServices 部分添加服务以解析依赖项。
services.AddIdentityServer().AddDeveloperSigningCredential();
这样,IdentityServer 的初始设置就完成了。为确保一切正常,只需将以下代码粘贴到 Startup.cs 页面。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; namespace IdentityServer { 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(); } // 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(); } app.UseIdentityServer(); } } }
现在右键单击 Identity server,转到 debug start new instance。
我们将收到以下错误。
单击“View Details”查看完整的异常。“No storage mechanism for clients specified. Use the 'AddInMemoryClients' extension method to register a development version.”(未指定客户端的存储机制。使用 'AddInMemoryClients' 扩展方法注册开发版本。)
所以它正在寻找客户端,让我们添加 InMemoryClients。
我们将创建一个新类来定义我们的客户端。右键单击,转到 Add,然后单击 Class。 将其命名为 Config.cs,然后单击“Add”
将以下代码添加到 Config.cs 文件中。
using IdentityServer4.Models; using System.Collections.Generic; namespace IdentityServer { public class Config { public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); //Client1 clients.Add(new Client() { ClientId = "Client1", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) } }); return clients; } } }
ConfigureService 方法如下。
public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer().AddDeveloperSigningCredential() .AddInMemoryClients(Config.GetClients()); }
现在我们已经添加了 InMemoryClients,现在运行应用程序。
我们将收到以下异常,单击“View Details”查看完整的异常。“No storage mechanism for resources specified. Use the 'AddInMemoryIdentityResources' or 'AddInMemoryApiResources' extension method to register a development version.”(未指定资源的存储机制。使用 'AddInMemoryIdentityResources' 或 'AddInMemoryApiResources' 扩展方法注册开发版本。)
现在它正在查找 API 资源,让我们将其添加到 Config.cs 类中。将 Config.cs 替换为以下代码。
using IdentityServer4.Models; using System.Collections.Generic; namespace IdentityServer { public class Config { //Defining the InMemory Clients public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); //Client1 clients.Add(new Client() { ClientId = "Client1", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) } }); return clients; } //Defining the InMemory API's public static IEnumerable<ApiResource> GetApiResources() { List<ApiResource> apiResources = new List<ApiResource>(); apiResources.Add(new ApiResource("api1","First API")); return apiResources; } } }
现在打开 Startup.cs 页面,并将此 apiService 添加到 Identity service。
public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer().AddDeveloperSigningCredential() .AddInMemoryClients(Config.GetClients()) .AddInMemoryApiResources(Config.GetApiResources()); }
现在让我们再次运行应用程序。
现在我们看不到任何错误。由于我们没有实现 IdentityServer 的 UI,它将在浏览器中显示一个空白页面。
有一个 URL,我们可以实际查看与 IdentityServer 配置相关的信息,其中定义了用于 token、authorize、userinfo 等的终结点。
只需在我们的 localhost URL 后面添加“/.well-known/openid-configuration”,我这里的端口号是 49381,所以我的更新 URL 将是
https://:49381/.well-known/openid-configuration
一旦我们粘贴此 URL 并按 Enter 键,我们将看到我们的 IdentityServer 详细信息。我们可以看到 issuer url、token end point,有哪些 api 可用等等。
理想情况下,我们会将此 URL 与我们的客户端共享,它们将知道应该使用哪个 URL 取决于它们的要求,它们还将了解已注册哪些 API。
现在 IdentityServer 已准备就绪。请记住 IdentityServer 地址以备将来参考。
IdentityServer 地址:https://:49381
现在让我们构建 API。
1. WebAPI 实现
现在转到 Visual Studio 2017 并创建一个新项目,我将其命名为 WebAPI。
选择 WebAPI 模板,单击“OK”。
解决方案资源管理器将默认具有上述文件。
从 Controllers 文件夹打开 ValuesController.cs 文件。我们的 API 不安全。只需运行 API 项目。
我们可以在没有任何授权的情况下看到控制器方法中的数据。请记住 API URL https://:52037/api/values (端口号可能因系统而异)。
让我们为这个 api 控制器添加授权,用 [Authorize] 属性装饰 API 控制器。
namespace WebAPI.Controllers { [Route("api/[controller]")] [Authorize] public class ValuesController : Controller { // GET api/values [HttpGet] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
现在我们的 API 将只允许已注册的客户端访问,这意味着它们将发送 Token,API 将在身份服务器上进行验证,一旦验证通过,它将授予对 API 控制器的访问权限。
客户端最初从 IdentityServer 获取令牌,然后将其发送到 API,因此 API 必须通过将令牌传递给身份服务器 (https://:49381) 来验证令牌,
所以 API 需要一个包 (IdentityServer4.AccessTokenValidation) 来完成这个任务。
右键单击 API 项目,然后转到“管理 NuGet 包”,在搜索栏中键入 IdentityServer4.AccessTokenValidation 并按 Enter 键。 接受许可协议。
安装完成后,打开 Startup.cs 页面并粘贴以下代码。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace WebAPI { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvcCore().AddAuthorization().AddJsonFormatters(); services.AddAuthentication("Bearer") // it is a Bearer token .AddIdentityServerAuthentication(options => { options.Authority = "https://:49381"; //Identity Server URL options.RequireHttpsMetadata = false; // make it false since we are not using https options.ApiName = "api1"; //api name which should be registered in IdentityServer }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication(); // add the Authentication middleware app.UseMvc(); } } }
需要注意的事项
Authority:这里我们必须提供 Identityserver 地址。
ApiName:这里我们必须提供与 Config.cs 文件(IdentityServer 项目)中的 GetApiResources() 方法中相同的名称。
现在运行 API 查看,它将给出“401 Unauthorized”错误。
所以我们的 WebAPI 是安全的,它将只允许拥有有效令牌的客户端访问。
现在 WebAPI 实现已完成。让我们构建客户端应用程序。
2. 客户端应用程序实现
我们要构建什么
让我们创建一个简单的控制台应用程序,它将从 IdentityServer (https://:49381) 请求令牌,并通过传递该令牌来调用 WebAPI (https://:52037/api/values)。
打开 Visual Studio 2017,选择“Console App”,单击“OK”。
解决方案资源管理器将只包含 Program.cs 页面。让我们打开它。
首先,我们的客户端需要测试 IdentityServer (https://:49381) 是否可访问,如果可访问,则将其存储在一个对象中以发现令牌的终结点。为了发现 IdentityServer,我们需要安装一个名为“IdentityModel”的包。
转到 NuGet 包管理器,在搜索框中键入 IdentityModel,查找下面的突出显示的包并进行安装。 接受协议。
首先,我们将使用“DiscoveryClient”类调用 IdentityServer,如下所示。
var identityServer = await DiscoveryClient.GetAsync("https://:49381");
如果出现任何错误,我们将把错误写入控制台,并停止执行。
if (identityServer.IsError) { Console.Write(identityServer.Error); return; }
一旦我们有了 identityserver 对象,下一步就是获取令牌。
首先,我们必须使用我们的客户端详细信息定义令牌客户端。
注意:客户端详细信息可在 IdentityServer 项目的 Config.cs 页面的 GetClients() 方法中找到。
var tokenClient = new TokenClient(identityServer.TokenEndpoint, "Client1", "secret");
使用 withtokenClient 对象,我们将调用 IdentityServer 来接收特定 API 的令牌。
var tokenResponse = tokenClient.RequestClientCredentialsAsync("api1");
我们必须提供与我们将传递此令牌相同的 API 名称。
因此,在“tokenResponse”对象中,我们有令牌。现在我们可以调用 API 了,让我们通过传递此令牌来调用 API。我们在 API 中定义了令牌是“Bearer”类型,所以我们将此令牌添加到 HttpClient 方法 SetBearerToken 方法中。
HttpClient client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken);
现在调用 API 并将结果显示在控制台中。
var response = await client.GetAsync("https://:52037/api/values"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); Console.ReadKey();
所以最终代码应该是这样的,将其粘贴到 Program.cs 页面。
using IdentityModel.Client; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Threading.Tasks; namespace Client { class Program { static void Main(string[] args) => MainAsync().GetAwaiter().GetResult(); private static async Task MainAsync() { var identityServer = await DiscoveryClient.GetAsync("https://:49381"); //discover the IdentityServer if (identityServer.IsError) { Console.Write(identityServer.Error); return; } //Get the token var tokenClient = new TokenClient(identityServer.TokenEndpoint, "Client1", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); //Call the API HttpClient client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var response = await client.GetAsync("https://:52037/api/values"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); Console.ReadKey(); } } }
现在我们已经实现了我们的客户端。在运行此之前,IdentityServer 和 API 应该已启动并正在运行。
首先运行 IdentityServer 项目。https://:49381/.well-known/openid-configuration
接下来运行 WebAPI 项目。
所以两者都已启动并运行,现在让我们运行我们的客户端应用程序。在获取令牌时,我们将收到以下错误。
它显示无效的范围,这意味着我们的客户端“api1”不可访问。
要解决此问题,请转到 IdentityServer 项目中的 Config.cs 文件。为我们的客户端添加范围。
AllowedScopes = {"api1"}
将 GetClients 方法更新如下。
//Defining the InMemory Clients public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); //Client1 clients.Add(new Client() { ClientId = "Client1", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = {"api1"} }); return clients; }
现在我们已经添加了范围,让我们现在运行,在运行我们的客户端之前,IdentityServer 和 WebAPI 应该已经运行。
现在我们的客户端已成功与 IdentityServer 和 WebAPI 通信。
这里有一点需要注意,我们是通过客户端凭据与 IdentityServer 通信的。 现在我们将通过引入密码来提高安全性。
转到 IdentityServer 项目中的 Config.cs 文件。
目前客户端的“AllowedGrantTypes”是 ClientCredentials。让我们更新它以使用密码。
//Defining the InMemory Clients public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); //Client1 clients.Add(new Client() { ClientId = "Client1", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = {"api1"} }); return clients; }
在 Config.cs 页面定义一些测试用户。
添加命名空间 using IdentityServer4.Test; 将以下方法复制粘贴到 Config.cs 页面中,
//Defining the InMemory User's public static List<TestUser> GetUsers() { List<TestUser> testUsers = new List<TestUser>(); testUsers.Add(new TestUser() { SubjectId="1", Username="satya", Password="password" }); return testUsers; }
在 Startup.cs 页面,在 ConfigureServices 方法中添加这些 GetUsers 方法。
public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer().AddDeveloperSigningCredential() .AddInMemoryClients(Config.GetClients()) .AddInMemoryApiResources(Config.GetApiResources()) .AddTestUsers(Config.GetUsers()); }
现在转到 Client Application 并使用此用户凭据获取令牌。
在 Client 中打开 Program.cs 页面,替换为以下代码。
using IdentityModel.Client; using Newtonsoft.Json.Linq; using System; using System.Net.Http; using System.Threading.Tasks; namespace Client { class Program { static void Main(string[] args) => MainAsync().GetAwaiter().GetResult(); private static async Task MainAsync() { var identityServer = await DiscoveryClient.GetAsync("https://:49381"); //discover the IdentityServer if (identityServer.IsError) { Console.Write(identityServer.Error); return; } //Get the token var tokenClient = new TokenClient(identityServer.TokenEndpoint, "Client1", "secret"); //var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("satya", "password", "api1"); //Call the API HttpClient client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var response = await client.GetAsync("https://:52037/api/values"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); Console.ReadKey(); } } }
我们只修改了 tokenResponse 类型。
一旦 identityserver 和 webapi 运行并启动,就运行客户端应用程序。
我们可以看到以下输出。
目前 IdentityServer 没有登录、注销等 UI。IdentityServer4 提供了一个基于 MVC 的示例 UI,我们可以用它作为起点。
转到以下 URL https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/tree/release
单击“Download ZIP”
如下所示解压 zip 文件。
复制 Quickstart、Views 和 wwwroot 文件夹,并将其粘贴到 IdentityServer 根文件夹中,如下所示。
由于这是 MVC,我们需要在 Startup.cs 页面中添加中间件和服务。
将 Startup.cs 页面替换为以下代码。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; namespace IdentityServer { 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.AddMvc(); services.AddIdentityServer().AddDeveloperSigningCredential() .AddInMemoryClients(Config.GetClients()) .AddInMemoryApiResources(Config.GetApiResources()) .AddTestUsers(Config.GetUsers()); } // 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(); } app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); } } }
现在运行“IdentityServer”。我们可以看到漂亮的默认 IdentityServer4。单击第二行(管理您的已存储授权)。
这是一个已开发的登录页面,具有所有必需的功能,它将包含验证用户名、密码、注销等的验证、登录机制。
尝试在用户名、密码中输入错误的数值,然后单击登录页面。 它已通过我们的 GetUsers() 进行验证,并显示错误消息。
现在提供正确的凭据。 现在我们可以看到用户名和下面的注销按钮。
因此,样板代码已准备就绪,我们可以将其集成到任何其他应用程序中。我们甚至可以根据项目需求修改 CSS、HTML 和功能。
3. MVC Web 应用程序
现在,让我们创建一个 MVC Web 应用程序,用于身份验证和授权,我们将使用这个集中的 IdentityServer。
我们将解决以下几点。
1. 当 MVC 应用程序请求身份验证时,将用户重定向到 IdentityServer。
2. 在 IdentityServer 中成功身份验证后,IdentityServer 将使用所需详细信息将用户重定向到 MVC 应用程序。
打开 Visual Studio 新实例,转到 New-> Project
选择“ASP.NET Core Web Application”项目。将其命名为 MVCWebApplication,然后单击“OK”。
选择以下模板,单击“OK”。解决方案资源管理器将具有以下文件。
为了使用 IdentityServer 进行身份验证,我们需要更新 Startup.cs 页面。将以下代码粘贴到 Startup.cs 页面。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace MVCWebApplication { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "https://:49381/"; options.RequireHttpsMetadata = false; options.ClientId = "mvc"; options.SaveTokens = true; }); } // 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(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication(); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } }
注意:options.Authority = "https://:49381/"; 它应该是 IdentityServer 地址。
现在运行应用程序并获取 MVC Web 应用程序的 URL。我得到了 https://:62024
记下 URL。 现在我们需要对 IdentityServer 进行一些更改才能使用此 MVC 应用程序。
打开 Config.cs 文件并替换为以下代码。
using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Test; using System.Collections.Generic; namespace IdentityServer { public class Config { //Defining the InMemory Clients public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); //Client1 clients.Add(new Client() { ClientId = "Client1", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } }); //Client for MVC clients.Add(new Client() { ClientId = "mvc", ClientName = "MVC", AllowedGrantTypes = GrantTypes.Implicit, // where to redirect to after login RedirectUris = { "https://:62024/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://:62024/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } }); return clients; } //Defining the InMemory API's public static IEnumerable<ApiResource> GetApiResources() { List<ApiResource> apiResources = new List<ApiResource>(); apiResources.Add(new ApiResource("api1", "First API")); return apiResources; } //Defining the InMemory User's public static List<TestUser> GetUsers() { List<TestUser> testUsers = new List<TestUser>(); testUsers.Add(new TestUser() { SubjectId = "1", Username = "satya", Password = "password" }); return testUsers; } //Support for OpenId connectivity scopes public static IEnumerable<IdentityResource> GetIdentityResources() { List<IdentityResource> resources = new List<IdentityResource>(); resources.Add(new IdentityResources.OpenId()); // Support for the OpenId resources.Add(new IdentityResources.Profile()); // To get the profile information return resources; } } }
我们添加了一个新的方法 GetIdentityResources() 来支持与 mvc 应用程序的 OpenId。
我们为 mvc 应用程序添加了一个新的客户端。
注意:将此 URL https://:62024/ 替换为你的 mvc URL。
现在我们在 Startup.cs 页面中注册了这个方法。
将 Startup.cs 页面替换为以下代码。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; namespace IdentityServer { 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.AddMvc(); services.AddIdentityServer().AddDeveloperSigningCredential() .AddInMemoryClients(Config.GetClients()) .AddInMemoryApiResources(Config.GetApiResources()) .AddTestUsers(Config.GetUsers()) .AddInMemoryIdentityResources(Config.GetIdentityResources()); } // 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(); } app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); } } }
现在运行此 IdentityServer。 在 MVC 应用程序中转到 HomeController.cs 页面,并为 Controller 添加 Authorize 属性。
[Authorize] public class HomeController : Controller { public IActionResult Index() { return View(); }
现在运行 MVC 应用程序。如果 IdentityServer 未运行,我们将收到以下错误页面。
如果 IdentityServer 可用,我们将看到如下登录屏幕。
现在输入正确的用户凭据,单击登录,它将询问您是否要与 MVC 应用程序共享详细信息。单击“Yes”。
现在我们可以看到我们的 HomeController Index 方法。
现在我们已成功测试了身份验证。
Authorization
让我们通过添加一些代码来测试授权。
在 identity server 中,我们必须创建声明来授权用户角色。在 MVC Web 应用程序中,我们必须创建策略来获取这些声明。因此,使用策略,我们将向 MVC 应用程序中的特定资源授予访问权限。
IdentityServer:在 IdentityServer 中添加“roles” Identity Resoruce new IdentityResource("roles",new[] {"role"} )
让我们有两个用户,一个是管理员,另一个是编辑。
new TestUser() { SubjectId="1", Username="admin", Password="password", Claims = new List<Claim> { new Claim("role", "admin"), } }, new TestUser() { SubjectId="2", Username="editor", Password="password", Claims = new List<Claim> { new Claim("role", "editor"), } } In MVC Client add “roles” in AllowedScopes AllowedScopes=new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "roles" }, AlwaysIncludeUserClaimsInIdToken = true
将 Config.cs 替换为以下代码。
using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Test; using System.Collections.Generic; using System.Security.Claims; namespace IdentityServer { public class Config { //Defining the InMemory Clients public static IEnumerable<Client> GetClients() { List<Client> clients = new List<Client>(); //Client1 clients.Add(new Client() { ClientId = "Client1", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } }); //Client for MVC clients.Add(new Client() { ClientId = "mvc", ClientName = "MVC", AllowedGrantTypes = GrantTypes.Implicit, // where to redirect to after login RedirectUris = { "https://:62024/signin-oidc" }, // where to redirect to after logout PostLogoutRedirectUris = { "https://:62024/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "roles" }, AlwaysIncludeUserClaimsInIdToken = true //It should be true to send Claims with token }); return clients; } //Defining the InMemory API's public static IEnumerable<ApiResource> GetApiResources() { List<ApiResource> apiResources = new List<ApiResource>(); apiResources.Add(new ApiResource("api1", "First API")); return apiResources; } //Defining the InMemory User's public static List<TestUser> GetUsers() { return new List<TestUser>() { new TestUser() { SubjectId="1", Username="admin", Password="password", Claims = new List<Claim> { new Claim("role", "admin"), } }, new TestUser() { SubjectId="2", Username="editor", Password="password", Claims = new List<Claim> { new Claim("role", "editor"), } } }; } //Support for OpenId connectivity scopes public static IEnumerable<IdentityResource> GetIdentityResources() { List<IdentityResource> resources = new List<IdentityResource>(); resources.Add(new IdentityResources.OpenId()); // Support for the OpenId resources.Add(new IdentityResources.Profile()); // To get the profile information resources.Add(new IdentityResource("roles", new[] { "role" }));//To Support Roles return resources; } } }
IdentityServer 已准备好发送角色。
现在转到 MVC 应用程序 Startup.cs 页面,为这些角色创建策略。
services.AddAuthorization(options => { options.AddPolicy("All", policy => policy.RequireRole("admin", "editor")); options.AddPolicy("Admin", policy => policy.RequireRole("admin")); });
这里我们创建了两个策略。
1. All - 适用于管理员和编辑。
2. Admin - 仅适用于管理员角色。
将以下代码替换到 MVC Web 应用程序的 Startup.cs 页面中。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace MVCWebApplication { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //Admin role can access both admin as well as editor resources //Editor role can access only ediotr resources, admin resources are not available for editor services.AddAuthorization(options => { options.AddPolicy("All", policy => policy.RequireRole("admin", "editor")); options.AddPolicy("Admin", policy => policy.RequireRole("admin")); }); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "https://:49381/"; options.RequireHttpsMetadata = false; options.ClientId = "mvc"; options.Scope.Add("roles"); options.SaveTokens = true; }); } // 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(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication(); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } } }
现在转到 HomeController.cs 页面,添加这些策略。
[Authorize(Policy = "All")] public IActionResult Index() { return View(); } [Authorize(Policy = "Admin")] public IActionResult About() { ViewData["Message"] = "Your application description page."; return View(); }
Index - 角色(管理员、编辑)
About – 角色(管理员)
Index 方法可由管理员或编辑访问,但 About 方法只能由管理员方法访问。
由于我们正在使用 cookie,因此我们还必须实现注销功能来测试不同的角色。
将以下方法添加到 HomeController.cs 页面。
public async Task Logout() { await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); }
转到 _Layout.cshtml 页面。
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
将上面一行更新为如下。
<li><a asp-area="" asp-controller="Home" asp-action="Logout">Logout</a></li>
首先,IdentityServer 必须已启动并运行。
现在运行 MVC Web 应用程序。
首先,我们将测试编辑功能。
用户名:editor
密码:password
他只能访问 Index,但不能访问 About 方法。让我们测试一下。
输入凭据,单击登录按钮。
单击“Yes” 他已成功通过身份验证,并授权访问 Index 方法。
现在单击 About 链接。 我们可以看到“AccessDenied”消息。
现在单击注销链接。
现在我们将测试管理员角色。
用户名:admin
密码:password
他可以访问 Index 和 About。
让我们测试一下。
输入管理员凭据并单击登录。
我们可以看到 Index 方法。现在单击 About 链接。
他还可以访问 About。
我们涵盖了身份验证和授权。