潍坊外贸网站制作,长沙专业网站设计服务,郑州小程序制作流程及费用,ipv6改造 网站怎么做好吧#xff0c;这个题目我也想了很久#xff0c;不知道如何用最简单的几个字来概括这篇文章#xff0c;原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ASP.NET Identity实现身份认证与授权》#xff0c;然而如你所见#xff0c;这样的名字实在是太… 好吧这个题目我也想了很久不知道如何用最简单的几个字来概括这篇文章原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ASP.NET Identity实现身份认证与授权》然而如你所见这样的名字实在是太长了。所以我不得不缩写“单页面应用”几个字然后去掉ASP.NET Identity的描述最后形成目前的标题。不过这也就意味着这篇文章会涵盖很多内容和技术我会利用这些技术来走通一个完整的流程这个流程也代表着在微服务架构中单点登录的一种实现模式。在此过程中我们会使用到如下技术或框架Angular 8Ocelot API GatewayIdentityServer4ASP.NET IdentityEntity Framework CoreSQL Server本文假设读者具有上述技术框架的基础知识。由于内容比较多我还是将这篇文章分几个部分进行讲解和讨论。场景描述在微服务架构下的一种比较流行的设计就是基于前后端分离前端只做呈现和用户操作流的管理后端服务由API网关同一协调以从业务层面为前端提供各种服务。大致可以用下图表示在这个结构中我没有将Identity Service放在API Gateway后端因为考虑到Identity Service本身并没有承担任何业务功能。从它所能提供的端点Endpoint的角度它也需要做负载均衡、熔断等保护但我们暂时不讨论这些内容。流程上其实也比较简单在上图的数字标识中Client向Identity Service发送认证请求通常可以是用户名密码如果验证通过Identity Service会向Client返回认证的TokenClient使用Token向API Gateway发送API调用请求API Gateway将Client发送过来的Token发送给Identity Service以验证Token的有效性如果验证成功Identity Service会告知API Gateway认证成功API Gateway转发Client的请求到后端API ServiceAPI Service将结果返回给API GatewayAPI Gateway将API Service返回的结果转发到Client只是在这些步骤中我们有很多技术选择比如Identity Service的实现方式、认证方式等等。接下来我就在ASP.NET Core的基础上使用IdentityServer4、Entity Framework Core和Ocelot来完成这一流程。在完成整个流程的演练之前需要确保机器满足以下条件安装Visual Studio 2019 Community Edition。使用Visual Studio Code也是可以的根据自己的需要选择安装Visual Studio Code安装Angular 8IdentityServer4结合ASP.NET Identity实现Identity Service创建新项目首先第一步就是实现Identity Service。在Visual Studio 2019 Community Edition中新建一个ASP.NET Core Web Application模板选择Web Application (Model-View-Controller)然后点击Authentication下的Change按钮再选择Individual User Accounts选项以便将ASP.NET Identity的依赖包都加入项目并且自动完成基础代码的搭建。然后通过NuGet添加IdentityServer4.AspNetIdentity以及IdentityServer4.EntityFramework的引用IdentityServer4也随之会被添加进来。接下来在该项目的目录下执行以下命令安装IdentityServer4的模板并将IdentityServer4的GUI加入到当前项目12dotnet new -i identityserver4.templatesdotnet new is4ui --force然后调整一下项目结构将原本的Controllers目录删除同时删除Models目录下的ErrorViewModel类然后将Quickstart目录重命名为Controllers编译代码代码应该可以编译通过接下来就是实现我们自己的Identity。定制Identity Service为了能够展现一个标准的应用场景我自己定义了User和Role对象它们分别继承于IdentityUser和IdentityRole类123456789public class AppUser : IdentityUser{ public string DisplayName { get; set; }} public class AppRole : IdentityRole{ public string Description { get; set; }}当然Data目录下的ApplicationDbContext也要做相应调整它应该继承于IdentityDbContextAppUser, AppRole, string类这是因为我们使用了自定义的IdentityUser和IdentityRole的实现1234567public class ApplicationDbContext : IdentityDbContextAppUser, AppRole, string{ public ApplicationDbContext(DbContextOptionsApplicationDbContext options) : base(options) { }}之后修改Startup.cs里的ConfigureServices方法通过调用AddIdentity、AddIdentityServer以及AddDbContext将ASP.NET Identity、IdentityServer4以及存储认证数据所使用的Entity Framework Core的依赖全部注册进来。为了测试方便目前我们还是使用Developer Signing Credential对于Identity Resource、API Resource以及Clients我们也是暂时先写死hard codepublic void ConfigureServices(IServiceCollection services){ services.AddDbContextApplicationDbContext(options options.UseSqlServer( Configuration.GetConnectionString(DefaultConnection))); services.AddIdentityAppUser, AppRole() .AddEntityFrameworkStoresApplicationDbContext() .AddDefaultTokenProviders(); services.AddIdentityServer().AddDeveloperSigningCredential() .AddOperationalStore(options { options.ConfigureDbContext builder builder.UseSqlServer(Configuration.GetConnectionString(DefaultConnection), sqlServerDbContextOptionsBuilder sqlServerDbContextOptionsBuilder.MigrationsAssembly(typeof(Startup).Assembly.GetName().Name)); options.EnableTokenCleanup true; options.TokenCleanupInterval 30; // interval in seconds }) .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddAspNetIdentityAppUser(); services.AddCors(options options.AddPolicy(AllowAll, p p.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader())); services.AddControllersWithViews(); services.AddRazorPages(); services.AddControllers();}然后调整Configure方法的实现将IdentityServer加入进来同时配置CORS使得站点能够被跨域访问public void Configure(IApplicationBuilder app, IWebHostEnvironment env){ if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); } else { app.UseExceptionHandler(/Home/Error); app.UseHsts(); } app.UseCors(AllowAll); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseIdentityServer(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints { endpoints.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?}); endpoints.MapRazorPages(); });}完成这部分代码调整后编译是通不过的因为我们还没有定义IdentityServer4的IdentityResource、API Resource和Clients。在项目中新建一个Config类代码如下public static class Config{ public static IEnumerableIdentityResource GetIdentityResources() new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Email(), new IdentityResources.Profile() }; public static IEnumerableApiResource GetApiResources() new[] { new ApiResource(api.weather, Weather API) { Scopes { new Scope(api.weather.full_access, Full access to Weather API) }, UserClaims { ClaimTypes.NameIdentifier, ClaimTypes.Name, ClaimTypes.Email, ClaimTypes.Role } } }; public static IEnumerableClient GetClients() new[] { new Client { RequireConsent false, ClientId angular, ClientName Angular SPA, AllowedGrantTypes GrantTypes.Implicit, AllowedScopes { openid, profile, email, api.weather.full_access }, RedirectUris {http://localhost:4200/auth-callback}, PostLogoutRedirectUris {http://localhost:4200/}, AllowedCorsOrigins {http://localhost:4200}, AllowAccessTokensViaBrowser true, AccessTokenLifetime 3600 }, new Client { ClientId webapi, AllowedGrantTypes GrantTypes.ResourceOwnerPassword, ClientSecrets { new Secret(mysecret.Sha256()) }, AlwaysSendClientClaims true, AllowedScopes { api.weather.full_access } } };}大致说明一下上面的代码。通俗地讲IdentityResource是指允许应用程序访问用户的哪些身份认证资源比如用户的电子邮件或者其它用户账户信息在Open ID Connect规范中这些信息会被转换成Claims保存在User Identity的对象里ApiResource用来指定被IdentityServer4所保护的资源比如这里新建了一个ApiResource用来保护Weather API它定义了自己的Scope和UserClaims。Scope其实是一种关联关系它关联着Client与ApiResource用来表示什么样的Client对于什么样的ApiResource具有怎样的访问权限比如在这里我定义了两个Clientangular和webapi它们对Weather API都可以访问UserClaims定义了当认证通过之后IdentityServer4应该向请求方返回哪些Claim。至于Client就比较容易理解了它定义了客户端能够以哪几种方式来向IdentityServer4提交请求。至此我们的源代码就可以编译通过了成功编译之后还需要使用Entity Framework Core所提供的命令行工具或者Powershell Cmdlet来初始化数据库。我这里选择使用Visual Studio 2019 Community中的Package Manager Console在执行数据库更新之前确保appsettings.json文件里设置了正确的SQL Server连接字符串。当然你也可以选择使用其它类型的数据库只要对ConfigureServices方法做些相应的修改即可。在Package Manager Console中依次执行下面的命令1234Add-Migration ModifiedUserAndRole -Context ApplicationDbContextAdd-Migration ModifiedUserAndRole –Context PersistedGrantDbContextUpdate-Database -Context ApplicationDbContextUpdate-Database -Context PersistedGrantDbContext效果如下打开SQL Server Management Studio看到数据表都已成功创建由于IdentityServer4的模板所产生的代码使用的是mock user也就是IdentityServer4里默认的TestUser因此相关部分的代码需要被替换掉最主要的部分就是AccountController的Login方法将该方法中的相关代码替换为if (ModelState.IsValid){ var user await _userManager.FindByNameAsync(model.Username); if (user ! null await _userManager.CheckPasswordAsync(user, model.Password)) { await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.DisplayName)); // only set explicit expiration here if user chooses remember me. // otherwise we rely upon expiration configured in cookie middleware. AuthenticationProperties props null; if (AccountOptions.AllowRememberLogin model.RememberLogin) { props new AuthenticationProperties { IsPersistent true, ExpiresUtc DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; }; // issue authentication cookie with subject ID and username await HttpContext.SignInAsync(user.Id, user.UserName, props); if (context ! null) { if (await _clientStore.IsPkceClientAsync(context.ClientId)) { // if the client is PKCE then we assume its native, so this change in how to // return the response is for better UX for the end user. return View(Redirect, new RedirectViewModel { RedirectUrl model.ReturnUrl }); } // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null return Redirect(model.ReturnUrl); } // request for a local page if (Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } else if (string.IsNullOrEmpty(model.ReturnUrl)) { return Redirect(~/); } else { // user might have clicked on a malicious link - should be logged throw new Exception(invalid return URL); } } await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, invalid credentials, clientId: context?.ClientId)); ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);}这样才能通过注入的userManager和EntityFramework Core来访问SQL Server以完成登录逻辑。新用户注册API由IdentityServer4所提供的默认UI模板中没有包括新用户注册的页面开发者可以根据自己的需要向Identity Service中增加View来提供注册界面。不过为了快速演示我打算先增加两个API然后使用curl来新建一些用于测试的角色Role和用户User。下面的代码为客户端提供了注册角色和注册用户的API18119public class RegisterRoleRequestViewModel{ [Required] public string Name { get; set; } public string Description { get; set; }} public class RegisterRoleResponseViewModel{ public RegisterRoleResponseViewModel(AppRole role) { Id role.Id; Name role.Name; Description role.Description; } public string Id { get; } public string Name { get; } public string Description { get; }} public class RegisterUserRequestViewModel{ [Required] [StringLength(50, ErrorMessage The {0} must be at least {2} and at max {1} characters long., MinimumLength 2)] [Display(Name DisplayName)] public string DisplayName { get; set; } public string Email { get; set; } [Required] [StringLength(100, ErrorMessage The {0} must be at least {2} and at max {1} characters long., MinimumLength 6)] [DataType(DataType.Password)] [Display(Name Password)] public string Password { get; set; } [Required] [StringLength(20)] [Display(Name UserName)] public string UserName { get; set; } public Liststring RoleNames { get; set; }} public class RegisterUserResponseViewModel{ public string Id { get; set; } public string UserName { get; set; } public string DisplayName { get; set; } public string Email { get; set; } public RegisterUserResponseViewModel(AppUser user) { Id user.Id; UserName user.UserName; DisplayName user.DisplayName; Email user.Email; }} // Controllers\Account\AccountController.cs[HttpPost][Route(api/[controller]/register-account)]public async TaskIActionResult RegisterAccount([FromBody] RegisterUserRequestViewModel model){ if (!ModelState.IsValid) { return BadRequest(ModelState); } var user new AppUser { UserName model.UserName, DisplayName model.DisplayName, Email model.Email }; var result await _userManager.CreateAsync(user, model.Password); if (!result.Succeeded) return BadRequest(result.Errors); await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.NameIdentifier, user.UserName)); await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Name, user.DisplayName)); await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, user.Email)); if (model.RoleNames?.Count 0) { var validRoleNames new Liststring(); foreach(var roleName in model.RoleNames) { var trimmedRoleName roleName.Trim(); if (await _roleManager.RoleExistsAsync(trimmedRoleName)) { validRoleNames.Add(trimmedRoleName); await _userManager.AddToRoleAsync(user, trimmedRoleName); } } await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Role, string.Join(,, validRoleNames))); } return Ok(new RegisterUserResponseViewModel(user));} // Controllers\Account\AccountController.cs[HttpPost][Route(api/[controller]/register-role)]public async TaskIActionResult RegisterRole([FromBody] RegisterRoleRequestViewModel model){ if (!ModelState.IsValid) { return BadRequest(ModelState); } var appRole new AppRole { Name model.Name, Description model.Description }; var result await _roleManager.CreateAsync(appRole); if (!result.Succeeded) return BadRequest(result.Errors); return Ok(new RegisterRoleResponseViewModel(appRole));}在上面的代码中值得关注的就是register-account API中的几行AddClaimAsync调用我们将一些用户信息数据加入到User Identity的Claims中比如将用户的角色信息通过逗号分隔的字符串保存为Claim在后续进行用户授权的时候会用到这些数据。创建一些基础数据运行我们已经搭建好的Identity Service然后使用下面的curl命令创建一些基础数据123456789curl -X POST https://localhost:7890/api/account/register-role \ -d {name:admin,description:Administrator} \ -H Content-Type:application/json --insecurecurl -X POST https://localhost:7890/api/account/register-account \ -d {userName:daxnet,password:Pssw0rd123,displayName:Sunny Chen,email:daxnet163.com,roleNames:[admin]} \ -H Content-Type:application/json --insecurecurl -X POST https://localhost:7890/api/account/register-account \ -d {userName:acqy,password:Pssw0rd123,displayName:Qingyang Chen,email:qychen163.com} \ -H Content-Type:application/json --insecure完成这些命令后系统中会创建一个admin的角色并且会创建daxnet和acqy两个用户daxnet具有admin角色而acqy则没有该角色。使用浏览器访问https://localhost:7890点击主页的链接进入登录界面用已创建的用户名和密码登录可以看到如下的界面表示Identity Service的开发基本完成小结一篇文章实在是写不完今天就暂且告一段落吧下一讲我将介绍Weather API和基于Ocelot的API网关整合Identity Service进行身份认证。源代码访问以下Github地址以获取源代码https://github.com/daxnet/identity-demo