IdentityServer4-快速入门
九、使用EntityFramework core进行配置和操作数据
1,使用 dotnet new mvc 创建一个mvc项目
2,nuget IdentityServer4
3,修改Startup
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddIdentityServer()//在DI中注册IdentityServer服务,它还为运行时状态注册一个内存存储 .AddDeveloperSigningCredential();//设置临时签名凭证 } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); } }
在这个场景中,我们将定义一个API和一个希望访问它的客户端。客户端将在IdentityServer中请求访问令牌,并使用它获得对API的访问。
定义API
创建一个Config,cs文件,在该文件中定义您希望保护的系统中的资源,例如api。
public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("api1", "My API") }; }
定义Client
定义访问这个api的client
对于此场景,客户端将没有交互式用户,并将使用所谓的client secret与IdentityServer进行身份验证。将以下代码添加到配置中。cs文件:
public static IEnumerable<Client> GetClient() { return new List<Client>() { new Client(){ ClientId="client", //没有交互式用户,使用clientid/secret进行身份验证 AllowedGrantTypes=GrantTypes.ClientCredentials, //secret认证 ClientSecrets={new Secret("secret".Sha256())}, //客户端有权访问的范围 AllowedScopes={"api"} } }; }
配置IdentityServer
在ConfigureServices添加AddInMemoryApiResources与AddInMemoryClients方法
public void ConfigureServices(IServiceCollection services) { // configure identity server with in-memory stores, keys, clients and resources services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()); }
打开 http://localhost:5000/.well-known/openid-configuration 你应该看看所谓的发现文件。您的客户端和api将使用它来下载必要的配置数据。
添加一个API项目
添加一个api项目,将url设置为 http://localhost:5001
1,添加controller
[Route("identity")] [Authorize] public class IdentityController : ControllerBase { [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
2,配置
最后一步是将身份验证服务添加到DI,将身份验证中间件添加到管道。这些将会:
- 验证传入的令牌以确保它来自受信任的颁发者
- 验证该令牌是否适用于此api(又名scope)
nuget IdentityServer4.AccessTokenValidation
更新Startup如下
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); //AddAuthentication将认证服务添加到DI,并将“Bearer”配置为默认方案。 services.AddAuthentication("Bearer") //将IdentityServer访问令牌验证处理程序添加到DI中以供验证服务使用 .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; }); } public void Configure(IApplicationBuilder app) { //UseAuthentication将认证中间件添加到管道,因此每次调用主机时都会自动执行认证。 app.UseAuthentication(); app.UseMvc(); } }
如果使用浏览器导航到控制器(http://localhost:5001/identity),则应该得到401状态码作为回报。这意味着您的API需要凭据。
就是这样,API现在由IdentityServer保护。
创建client
添加一个console项目
IdentityServer中的令牌端点实现OAuth 2.0协议,您可以使用原始HTTP访问它。但是,我们有一个名为IdentityModel的客户端库,它将协议交互封装在一个易于使用的API中。
nuget IdentityModel
IdentityModel包含一个用于发现端点的客户端库。 这样您只需要知道IdentityServer的基地址 - 可以从元数据中读取实际的端点地址:
// 从元数据发现端点 var disco = await DiscoveryClient.GetAsync("http://localhost:5000"); if (disco.IsError) { Console.WriteLine(disco.Error); return; }
接下来,您可以使用TokenClient类来请求令牌。 要创建实例,您需要传入令牌端点地址,client ID和secret。
接下来,您可以使用requestcredenticlientalsasync方法为您的API请求一个令牌:
// request token 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);//将access token赋值到https://jwt.io/可以查看详细信息
最后一步是调用API。
要将访问令牌发送到API,通常使用HTTP授权头。这是使用SetBearerToken扩展方法完成的:
// call api var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var response = await client.GetAsync("http://localhost:5001/identity"); if (!response.IsSuccessStatusCode) { Console.WriteLine(response.StatusCode); } else { var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content)); }
案例下载:https://pan.baidu.com/s/1xYxymmnn6giMUphnnCGaaQ
客户端输入用户名和密码获取访问令牌,通过访问令牌访问资源,该规范建议仅将“资源所有者密码授予”用于“可信”应用程序
添加用户
TestUser类表示测试用户。 让我们通过将以下代码添加到我们的配置类来创建几个用户:
首先将以下using语句添加到Config.cs文件中:
using IdentityServer4.Test; 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" } }; }
然后向IdentityServer注册测试用户:
public void ConfigureServices(IServiceCollection services) { // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()); }
AddTestUsers扩展方法在底层做了几件事情
- 增加了对资源所有者密码授权的支持
- 增加了对登录UI通常使用的用户相关服务的支持
- 基于测试用户添加了对配置文件服务的支持
配置一个支持密码授权的客户端
public static IEnumerable<Client> GetClients() { return new List<Client> { // other clients omitted... // resource owner password grant client new Client { ClientId = "ro.client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } } }; }
使用密码授予请求令牌
客户端看起来非常类似于我们为客户端凭据授权所做的操作。 主要区别在于客户端会以某种方式收集用户的密码,并在令牌请求期间将其发送给令牌服务。
// request token 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");
案例下载:https://pan.baidu.com/s/1q-UYw_34hX00MqxiZp524g
添加UI
OpenID Connect所需的所有协议支持都已内置到IdentityServer中。 您需要为登录,注销,同意和错误提供必要的UI部分。
虽然每个IdentityServer实现的外观和感觉以及确切的工作流程可能总是不同,但我们提供了一个基于MVC的示例UI,您可以将其用作起点。
这个用户界面可以在Quickstart UI仓库中找到。 您可以克隆或下载此repo,并将控制器,视图,模型和CSS放入您的IdentityServer Web应用程序。
创建一个MVC应用,然后将 Quickstart UI复制到项目中,nuget IdentityServer4 。并配置Startup:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()); }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
创建一个MVC客户端
要将OpenID Connect身份验证支持添加到MVC应用程序,请将以下内容添加到启动时的ConfigureServices中:
//AddAuthentication将认证服务添加到DI services.AddAuthentication(options=>{ options.DefaultScheme="Cookies";//设置Cookies为主要认证手段 options.DefaultChallengeScheme="oidc";//当需要登录时使用OpenID Connect方案 }) .AddCookie("Cookies")//使用AddCookie添加可以处理cookie的处理程序。 .AddOpenIdConnect("oidc",options=>{ options.SignInScheme="Cookies"; options.Authority="http://localhost:5000"; options.RequireHttpsMetadata=false; options.ClientId="client"; options.SaveTokens=true; });
AddOpenIdConnect用于配置执行OpenID连接协议的处理程序。授权表明我们信任身份服务器。然后我们通过ClientId识别这个客户端。一旦OpenID连接协议完成,SignInScheme将使用cookie处理程序发出cookie。
savetoken用于将标识符从IdentityServer中持久化到cookie中(稍后需要它们)。
此外,我们关闭了JWT声明类型映射,以允许众所周知的声明(例如'sub'和'idp')通过非混淆的声明:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
然后为了确保验证服务在每个请求上执行,请添加UseAuthentication以在启动时进行配置:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseAuthentication();//认证中间件应该在管道中的MVC之前添加。 app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
添加对OpenID Connect Identity Scopes的支持
与OAuth 2.0类似,OpenID Connect也使用了作用域概念。同样,作用域表示您希望保护和客户端希望访问的内容。与OAuth相反,OIDC中的作用域并不表示api,而是用户id、名称或电子邮件地址等标识数据。
在Config.cs中创建一个IdentityResource对象集合,添加对标准openid(subject id)和profile(名字,姓氏等)作用域的支持
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; }
修改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()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()); }
为OpenID Connect隐式流添加客户端
最后一步是将MVC客户端的新配置条目添加到IdentityServer。
基于OpenID Connect的客户端与我们迄今添加的OAuth 2.0客户端非常相似。 但由于OIDC中的流程始终是交互式的,因此我们需要将一些重定向URL添加到我们的配置中。
将以下内容添加到客户端配置中:
public static IEnumerable<Client> GetClients() { return new List<Client> { // 其他客户被忽略... // OpenID Connect隐式流程客户端(MVC) new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.Implicit, // 登录后重定向到哪里 RedirectUris = { "http://localhost:5001/signin-oidc" }, // 注销后重定向到哪里 PostLogoutRedirectUris = { "http://localhost:5001/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } }; }
测试客户端
现在,最终所有的东西都应该适用于新的MVC客户端。
通过导航到受保护的控制器操作来触发身份验证握手。 您应该看到重定向到IdentityServer的登录页面。
案例下载:https://pan.baidu.com/s/1XHL4eRzp3oqIK3ubMF-NLQ
接下来我们将添加对外部认证的支持。 这非常简单,因为您真正需要的只是一个兼容ASP.NET Core的身份验证处理程序。
ASP.NET Core本身也支持Google,Facebook,Twitter,Microsoft帐户和OpenID Connect。 另外你可以在这里找到许多其他认证提供者的实现。
添加Google支持
要能够使用谷歌进行身份验证,首先需要向它们注册。这是在开发人员控制台完成的。创建一个新项目,启用谷歌+ API,并通过将/signin-google路径添加到基本地址(例如http://localhost:5000/signin- Google)来配置本地标识服务器的回调地址。
如果您正在端口5000上运行——您可以使用下面代码片段中的客户端id/secret,因为这是我们预先注册的。
首先添加Google身份验证处理程序到DI。 这是通过将此片段添加到启动时的ConfigureServices中完成的:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()); services.AddAuthentication() .AddGoogle("Google", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = "434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com"; options.ClientSecret = "3gcoTrEDPPJ0ukn_aYYT6PWo"; }); }
默认情况下,IdentityServer专门为外部认证的结果配置cookie处理程序(使用基于常量IdentityServerConstants.ExternalCookieAuthenticationScheme的方案)。 Google处理程序的配置然后使用该cookie处理程序。 为了更好地理解这是如何完成的,请参阅Quickstart文件夹下的AccountController类。
进一步实验
您可以添加一个额外的外部提供者。 我们有一个云托管的IdentityServer4演示版本,您可以使用OpenID Connect进行集成。
将OpenId Connect处理程序添加到DI中:
services.AddAuthentication() .AddGoogle("Google", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = "434483408261-55tc8n0cs4ff1fe21ea8df2o443v2iuc.apps.googleusercontent.com"; options.ClientSecret = "3gcoTrEDPPJ0ukn_aYYT6PWo"; }) .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" }; });
在前面的快速启动中,我们研究了API访问和用户身份验证。现在我们要把这两个部分结合起来。
OpenID Connect & OAuth 2.0组合的美妙之处在于,您可以通过一个协议和一个与令牌服务的交换来实现。
在前面的quickstart中,我们使用了OpenID连接隐式流。在隐式流中,所有令牌都通过浏览器传输,这对于身份令牌来说是完全没问题的。现在我们还想请求一个访问令牌。
访问令牌比身份令牌更加敏感,如果不需要,我们不希望将它们暴露于“外部”世界。 OpenID Connect包含一个名为“混合流”的流程,它可以让我们两全其美,身份令牌通过浏览器通道传输,因此客户可以在做更多工作之前验证它。 如果验证成功,客户端会打开令牌服务的后端通道来检索访问令牌。
修改client配置
没有多少必要的修改。首先,我们希望允许客户端使用混合流,此外,我们还希望客户端允许执行服务器到服务器的API调用,这些调用不在用户的上下文中(这与我们的客户端凭证quickstart非常相似)。使用AllowedGrantTypes属性表示。
接下来,我们需要添加一个客户端secret。这将用于检索后通道上的访问令牌。
最后,我们还为客户端提供了对offline_access范围的访问权限——这允许为长期存在的API访问请求刷新令牌:
new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" }, AllowOfflineAccess = true };
修改MVC客户端
MVC客户端的修改也是最小的——ASP.NET核心的OpenID连接处理器对混合流有内置的支持,所以我们只需要更改一些配置值。
我们配置 ClientSecret 以匹配IdentityServer上的secret。添加 offline_access 和 api1 作用域,并将 ResponseType 设置为 code id_token (这基本上意味着“使用混合流”)
.AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ClientId = "mvc"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("api1"); options.Scope.Add("offline_access"); });
当您运行MVC客户端时,除了同意屏幕现在要求您提供额外的API和 offline access scope外,没有太大区别。
使用访问令牌
OpenID连接中间件为您自动保存令牌(标识、访问和刷新)。这就是savetoken设置的作用。
技术上,令牌存储在cookie的属性部分。 访问它们的最简单方法是使用Microsoft.AspNetCore.Authentication命名空间中的扩展方法。
例如在您的声明视图上:
<dt>access token</dt> <dd>@await ViewContext.HttpContext.GetTokenAsync("access_token")</dd> <dt>refresh token</dt> <dd>@await ViewContext.HttpContext.GetTokenAsync("refresh_token")</dd>
要使用访问令牌访问API,您只需检索令牌并将其设置在您的HttpClient上:
public async Task<IActionResult> CallApiUsingUserAccessToken() { var accessToken = await HttpContext.GetTokenAsync("access_token"); var client = new HttpClient(); client.SetBearerToken(accessToken); var content = await client.GetStringAsync("http://localhost:5001/identity"); ViewBag.Json = JArray.Parse(content).ToString(); return View("json"); }
IdentityServer旨在提供灵活性,其中的一部分允许您为用户及其数据使用任何数据库(包括密码)。 如果你从一个新的用户数据库开始,那么ASP.NET Identity是你可以选择的一个选项。 本快速入门介绍了如何将Identity Identity与Identity Identity一起使用。
这个快速入门假设你已经完成了所有的快速入门。 快速入门使用ASP.NET Identity的方法是从Visual Studio中的ASP.NET Identity模板创建一个新项目。 这个新项目将取代之前在快速入门中从头开始构建的IdentityServer项目。 此解决方案中的所有其他项目(针对客户端和API)将保持不变。
新建ASP.NET Identity项目
第一步是为您的解决方案添加一个ASP.NET Identity的新项目。 鉴于ASP.NET身份需要大量代码,因此使用Visual Studio中的模板是有意义的。 您最终将删除IdentityServer的旧项目(假设您正在关注其他快速入门),但您需要迁移几个项目(或按照之前的快速入门中所述从头开始重写)。
nuget dotnet new mvc --auth Individual
修改hosting
不要忘记修改主机(如此处所述)以在端口5000上运行。这非常重要,因此现有客户端和API项目将继续运行。
添加IdentityServer包
添加IdentityServer4.AspNetIdentity NuGet包。 这取决于IdentityServer4包,因此会自动添加为传递依赖项。
Scopes和Clients配置
尽管这是IdentityServer的一个新项目,但我们仍需要与之前的快速入门相同的范围和客户端配置。 将用于之前快速入门的配置类(在Config.cs中)复制到此新项目中。
new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, RequireConsent = false, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" }, AllowOfflineAccess = true }
配置IdentityServer
与前面一样,需要在Configure reservices中和Startup.cs中配置IdentityServer。
ConfigureServices
public void ConfigureServices(IServiceCollection services) { services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); // Add application services. services.AddTransient<IEmailSender, EmailSender>(); services.AddMvc(); // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryPersistedGrants() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddAspNetIdentity<ApplicationUser>(); }
Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); app.UseDatabaseErrorPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); // app.UseAuthentication(); // not needed, since UseIdentityServer adds the authentication middleware app.UseIdentityServer(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
创建用户数据库
鉴于这是一个新的ASP.NET Identity项目,您将需要创建数据库。 您可以通过从项目目录运行命令提示符并运行 dotnet ef database update -c ApplicationDbContext 来执行此操作
案例下载:https://pan.baidu.com/s/1ZxVHNYApxyZMGcDg-XlTkg
这个快速入门将展示如何构建JavaScript客户端应用程序。 用户将登录到IdentityServer,使用IdentityServer发出的访问令牌调用Web API,并注销IdentityServer。
添加一个JavaScript的客户端项目
添加一个空项目
添加静态文件中间件
鉴于此项目主要用于运行客户端,我们需要ASP.NET Core来提供构成我们应用程序的静态HTML和JavaScript文件。 静态文件中间件旨在执行此操作。
在Configure方法中的Startup.cs中注册静态文件中间件:
public void Configure(IApplicationBuilder app) { app.UseDefaultFiles(); app.UseStaticFiles(); }
这个中间件现在将从应用程序的〜/ wwwroot文件夹中提供静态文件。 这是我们将放置HTML和JavaScript文件的地方。
引用oidc-client
在MVC项目中,我们使用一个库来处理OpenID Connect协议。 在这个项目中,我们需要一个类似的库,除了一个在JavaScript中工作并且被设计为在浏览器中运行的库。 oidc-client库就是这样一个库。 它可以通过NPM,Bower,以及从github直接下载。
NPM
如果您想使用NPM下载oidc-client,请按照以下步骤操作:
将一个新的NPM包文件添加到您的项目中,并将其命名为package.json。
在package.json中添加一个依赖到oidc-client:
"dependencies": { "oidc-client": "1.4.1" }
一旦保存了该文件,Visual Studio应该自动将这些软件包恢复到名为node_modules的文件夹中。
在〜/node_modules/oidc-client/dist文件夹中找到名为oidc-client.js的文件,并将其复制到应用程序的〜/wwwroot文件夹中。 有更复杂的方法将您的NPM软件包复制到〜/wwwroot中,但这些技术不在本快速入门的范围之内。
添加您的HTML和JavaScript文件
在〜/wwwroot中,添加一个名为index.html和callback.html的HTML文件,并添加一个名为app.js的JavaScript文件。
index.html
这将是我们应用程序的主页面。 它将包含用于登录,注销并调用Web API的按钮的HTML。 它还将包含<script>标签以包含我们的两个JavaScript文件。 它还将包含用于向用户显示消息的<pre>。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <button id="login">Login</button> <button id="api">Call API</button> <button id="logout">Logout</button> <pre id="results"></pre> <script src="oidc-client.js"></script> <script src="app.js"></script> </body> </html>
app.js
这将包含我们的应用程序的主要代码。 首先要添加一个辅助函数来将消息记录到<pre>:
function log() { document.getElementById('results').innerText = ''; Array.prototype.forEach.call(arguments, function (msg) { if (msg instanceof Error) { msg = "Error: " + msg.message; } else if (typeof msg !== 'string') { msg = JSON.stringify(msg, null, 2); } document.getElementById('results').innerHTML += msg + '\r\n'; }); }
接下来,添加代码将“click”事件处理程序注册到三个按钮:
document.getElementById("login").addEventListener("click", login, false); document.getElementById("api").addEventListener("click", api, false); document.getElementById("logout").addEventListener("click", logout, false);
接下来,我们可以使用oidc-client库中的UserManager类来管理OpenID Connect协议。 它需要类似的配置,这在MVC客户端中是必需的(虽然值不同)。 添加此代码以配置和实例化UserManager:
var config = { authority: "http://localhost:5000", client_id: "js", redirect_uri: "http://localhost:5003/callback.html", response_type: "id_token token", scope:"openid profile api1", post_logout_redirect_uri : "http://localhost:5003/index.html", }; var mgr = new Oidc.UserManager(config);
接下来,UserManager提供一个getUser API来知道用户是否登录到JavaScript应用程序。 它使用JavaScript Promise异步返回结果。 返回的用户对象具有包含用户声明的配置文件属性。 添加此代码以检测用户是否已登录JavaScript应用程序:
mgr.getUser().then(function (user) { if (user) { log("User logged in", user.profile); } else { log("User not logged in"); } });
接下来,我们要实现登录,api和注销功能。 UserManager提供signinRedirect来登录用户,并提供signoutRedirect登录用户。 我们在上面的代码中获得的用户对象也具有access_token属性,可用于通过Web API进行身份验证。 access_token将通过Bearer方案的Authorization头传递给Web API。 添加此代码以在我们的应用程序中实现这三个功能:
function login() { mgr.signinRedirect(); } function api() { mgr.getUser().then(function (user) { var url = "http://localhost:5001/identity"; var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function () { log(xhr.status, JSON.parse(xhr.responseText)); } xhr.setRequestHeader("Authorization", "Bearer " + user.access_token); xhr.send(); }); } function logout() { mgr.signoutRedirect(); }
callback.html
一旦用户登录到IdentityServer,该HTML文件就是指定的redirect_uri页面。 它将完成与IdentityServer的OpenID Connect协议登录握手。 这个代码全部由我们之前使用的UserManager类提供。 登录完成后,我们可以将用户重定向回主index.html页面。 添加此代码以完成登录过程:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <script src="oidc-client.js"></script> <script> new Oidc.UserManager().signinRedirectCallback().then(function () { window.location = "index.html"; }).catch(function (e) { console.error(e); }); </script> </body> </html>
为JavaScript客户端添加客户端注册到IdentityServer
现在客户端应用程序已准备就绪,我们需要在IdentityServer中为这个新的JavaScript客户端定义配置条目。 在IdentityServer项目中找到客户端配置(在Config.cs中)。 为我们的新JavaScript应用程序添加一个新客户端到列表中。 它应该具有下面列出的配置:
// JavaScript Client new Client { ClientId = "js", ClientName = "JavaScript Client", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, RedirectUris = { "http://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "http://localhost:5003/index.html" }, AllowedCorsOrigins = { "http://localhost:5003" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" } }
允许使用CORS对Web API进行Ajax调用
需要配置的最后一点是在Web API项目中配置CORS。 这将允许Ajax调用从http://localhost:5003到http://localhost:5001。
Configure CORS
将CORS服务添加到Startup.cs中ConfigureServices中的依赖注入系统:
public void ConfigureServices(IServiceCollection services) { services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api1"; }); services.AddCors(options => { // this defines a CORS policy called "default" options.AddPolicy("default", policy => { policy.WithOrigins("http://localhost:5003") .AllowAnyHeader() .AllowAnyMethod(); }); }); }
在配置中将CORS中间件添加到管道中:
public void Configure(IApplicationBuilder app) { app.UseCors("default"); app.UseAuthentication(); app.UseMvc(); }
案例下载:https://pan.baidu.com/s/1wFDdwjmjWIhvMIPIoZQwBw
九、使用EntityFramework核心进行配置和操作数据
IdentityServer专为扩展性而设计,其中一个可扩展点是用于IdentityServer所需数据的存储机制。 本快速入门说明如何配置IdentityServer以使用EntityFramework(EF)作为此数据的存储机制(而不是使用我们迄今为止使用的内存中实现)。
IdentityServer4.EntityFramework
我们正在向数据库移动两种类型的数据。 首先是配置数据(resources和client)。 第二个是IdentityServer在使用时生成的操作数据(tokens,codes和consents)。 这些商店使用接口建模,我们在 IdentityServer4.EntityFramework Nuget包中提供这些接口的EF实现。
通过添加对 IdentityServer4.EntityFramework Nuget包的IdentityServer项目的引用开始。
使用SqlServer
鉴于EF的灵活性,您可以使用任何EF支持的数据库。 对于这个快速入门,我们将使用Visual Studio附带的SqlServer的LocalDb版本。
数据库模式更改和使用EF迁移
IdentityServer4.EntityFramework软件包包含从IdentityServer模型映射的实体类。 随着IdentityServer的模型更改,IdentityServer4.EntityFramework中的实体类也会更改。 在使用IdentityServer4.EntityFramework并随着时间的推移升级时,随着实体类的更改,您将负责自己的数据库模式以及对该模式所必需的更改。 管理这些变化的一种方法是使用EF迁移,这个快速入门将显示如何完成。 如果迁移不是您的偏好,那么您可以以任何您认为合适的方式管理架构更改。
用于迁移的EF工具
除了使用EF迁移跟踪模式更改外,我们还将使用它来创建数据库中的初始模式。 这需要使用EF Core工具(更多细节在这里)。 我们现在将添加这些内容,不幸的是,这必须通过手工编辑您的.csproj文件来完成。 通过右键单击项目并选择“编辑projectname.csproj”来编辑.csproj:
<ItemGroup> <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.0" /> </ItemGroup>
配置 stores
下一步是将当前调用替换为Startup.cs中ConfigureServices方法中的AddInMemoryClients,AddInMemoryIdentityResources和AddInMemoryApiResources。 我们将用下面的代码替换它们:
const string connectionString = @"Data Source=(LocalDb)\MSSQLLocalDB;database=IdentityServer4.Quickstart.EntityFramework-2.0.0;trusted_connection=yes;"; var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() .AddTestUsers(Config.GetUsers()) // this adds the config data from DB (clients, resources) .AddConfigurationStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) // this adds the operational data from DB (codes, tokens, consents) .AddOperationalStore(options => { options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); // this enables automatic token cleanup. this is optional. options.EnableTokenCleanup = true; options.TokenCleanupInterval = 30; });
您可能需要将这些命名空间添加到文件中:
using Microsoft.EntityFrameworkCore; using System.Reflection;
上面的代码是对连接字符串进行硬编码的,如果你愿意,你可以随意更改。 此外,对AddConfigurationStore和AddOperationalStore的调用正在注册EF支持的存储实现。
添加迁移
要创建迁移,请在IdentityServer项目目录中打开命令提示符。 在命令提示符下运行这两个命令:
dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
您现在应该在项目中看到一个〜/Data/Migrations/IdentityServer文件夹。 这包含新创建的迁移的代码。
初始化数据库
现在我们已经进行了迁移,我们可以编写代码来从迁移中创建数据库。 我们还将使用我们在之前的快速入门中定义的内存配置数据对数据库进行种子处理。
在Startup.cs中添加此方法以帮助初始化数据库:
private void InitializeDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope()) { serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate(); var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>(); context.Database.Migrate(); if (!context.Clients.Any()) { foreach (var client in Config.GetClients()) { context.Clients.Add(client.ToEntity()); } context.SaveChanges(); } if (!context.IdentityResources.Any()) { foreach (var resource in Config.GetIdentityResources()) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (!context.ApiResources.Any()) { foreach (var resource in Config.GetApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); } } }
然后我们可以从Configure方法调用它::
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // this will do the initial DB population InitializeDatabase(app); // the rest of the code that was already here // ... }
上面的InitializeDatabase帮助程序API可以方便地为数据库创建种子,但这种方法并不适合每次运行应用程序时执行。 一旦你的数据库被填充,考虑删除对API的调用。
运行客户端应用程序
您现在应该能够运行任何现有的客户端应用程序并登录,获取令牌并调用API - 全部基于数据库配置。
案例下载:https://pan.baidu.com/s/1zVRXIEB9VMbbbCBf3kokTA
各种ASP.NET core安全示例
https://github.com/leastprivilege/AspNetCoreSecuritySamples
IdentityServer4 EF 和 ASP.NET Identity
这个样本结合了EF和ASP.NET身份快速入门(#6和#8)。
共同托管IdentityServer4和一个Web API
此示例显示如何在保护API的IdentityServer所在的主机上托管API。
https://github.com/brockallen/IdentityServerAndApi
用于MongoDB的IdentityServer4案例
- IdentityServer4-mongo:与快速入门#8 EntityFramework配置类似,但使用MongoDB作为配置数据。
- IdentityServer4-mongo-AspIdentity:更详细的示例基于使用ASP.NET身份进行身份管理,该身份管理使用MongoDB作为配置数据
https://github.com/souzartn/IdentityServer4.Samples.Mongo
从Facebook,Google和Twitter交换外部令牌
演示如何使用扩展授权将外部身份验证令牌交换到身份服务器访问令牌
https://github.com/waqaskhan540/IdentityServerExternalAuth
IdentityServer4快速入门UI的ASP.NET Core MVC RazorPages模板
Razor Pages based QuickStart sample by Martin Fletcher.
.NET core和ASP.NET core“平台”方案
显示受信任的“内部”应用程序与“外部”应用程序与.NET Core 2.0和ASP.NET Core 2.0应用程序的交互
https://github.com/BenjaminAbt/Samples.AspNetCore-IdentityServer4
使用JWKS使用来自IdentityServer4的令牌保护Node API
- 演示如何使用IdentityServer4的JWKS端点和RS256算法来保护Node(Express)API。
- 使用更高质量的生产准备模块为IdentityServer4.Samples中的NodeJsApi样本提供备选方案。