免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
Angular SPA基于Ocelot API網(wǎng)關(guān)與IdentityServer4的身份認(rèn)證與授權(quán)(一)

好吧,這個(gè)題目我也想了很久,不知道如何用最簡單的幾個(gè)字來概括這篇文章,原本打算取名《Angular單頁面應(yīng)用基于Ocelot API網(wǎng)關(guān)與IdentityServer4+ASP.NET Identity實(shí)現(xiàn)身份認(rèn)證與授權(quán)》,然而如你所見,這樣的名字實(shí)在是太長了。所以,我不得不縮寫“單頁面應(yīng)用”幾個(gè)字,然后去掉ASP.NET Identity的描述,最后形成目前的標(biāo)題。

不過,這也就意味著這篇文章會(huì)涵蓋很多內(nèi)容和技術(shù),我會(huì)利用這些技術(shù)來走通一個(gè)完整的流程,這個(gè)流程也代表著在微服務(wù)架構(gòu)中單點(diǎn)登錄的一種實(shí)現(xiàn)模式。在此過程中,我們會(huì)使用到如下技術(shù)或框架:

  • Angular 8

  • Ocelot API Gateway

  • IdentityServer4

  • ASP.NET Identity

  • Entity Framework Core

  • SQL Server

本文假設(shè)讀者具有上述技術(shù)框架的基礎(chǔ)知識(shí)。由于內(nèi)容比較多,我還是將這篇文章分幾個(gè)部分進(jìn)行講解和討論。

場景描述

在微服務(wù)架構(gòu)下的一種比較流行的設(shè)計(jì),就是基于前后端分離,前端只做呈現(xiàn)和用戶操作流的管理,后端服務(wù)由API網(wǎng)關(guān)同一協(xié)調(diào),以從業(yè)務(wù)層面為前端提供各種服務(wù)。大致可以用下圖表示:

在這個(gè)結(jié)構(gòu)中,我沒有將Identity Service放在API Gateway后端,因?yàn)榭紤]到Identity Service本身并沒有承擔(dān)任何業(yè)務(wù)功能。從它所能提供的端點(diǎn)(Endpoint)的角度,它也需要做負(fù)載均衡、熔斷等保護(hù),但我們暫時(shí)不討論這些內(nèi)容。

流程上其實(shí)也比較簡單,在上圖的數(shù)字標(biāo)識(shí)中:

  1. Client向Identity Service發(fā)送認(rèn)證請(qǐng)求,通常可以是用戶名密碼

  2. 如果驗(yàn)證通過,Identity Service會(huì)向Client返回認(rèn)證的Token

  3. Client使用Token向API Gateway發(fā)送API調(diào)用請(qǐng)求

  4. API Gateway將Client發(fā)送過來的Token發(fā)送給Identity Service,以驗(yàn)證Token的有效性

  5. 如果驗(yàn)證成功,Identity Service會(huì)告知API Gateway認(rèn)證成功

  6. API Gateway轉(zhuǎn)發(fā)Client的請(qǐng)求到后端API Service

  7. API Service將結(jié)果返回給API Gateway

  8. API Gateway將API Service返回的結(jié)果轉(zhuǎn)發(fā)到Client

只是在這些步驟中,我們有很多技術(shù)選擇,比如Identity Service的實(shí)現(xiàn)方式、認(rèn)證方式等等。接下來,我就在ASP.NET Core的基礎(chǔ)上使用IdentityServer4、Entity Framework Core和Ocelot來完成這一流程。在完成整個(gè)流程的演練之前,需要確保機(jī)器滿足以下條件:

  • 安裝Visual Studio 2019 Community Edition。使用Visual Studio Code也是可以的,根據(jù)自己的需要選擇

  • 安裝Visual Studio Code

  • 安裝Angular 8

IdentityServer4結(jié)合ASP.NET Identity實(shí)現(xiàn)Identity Service

創(chuàng)建新項(xiàng)目

首先第一步就是實(shí)現(xiàn)Identity Service。在Visual Studio 2019 Community Edition中,新建一個(gè)ASP.NET Core Web Application,模板選擇Web Application (Model-View-Controller),然后點(diǎn)擊Authentication下的Change按鈕,再選擇Individual User Accounts選項(xiàng),以便將ASP.NET Identity的依賴包都加入項(xiàng)目,并且自動(dòng)完成基礎(chǔ)代碼的搭建。

然后,通過NuGet添加IdentityServer4.AspNetIdentity以及IdentityServer4.EntityFramework的引用,IdentityServer4也隨之會(huì)被添加進(jìn)來。接下來,在該項(xiàng)目的目錄下,執(zhí)行以下命令安裝IdentityServer4的模板,并將IdentityServer4的GUI加入到當(dāng)前項(xiàng)目:

dotnet new -i identityserver4.templatesdotnet new is4ui --force

然后調(diào)整一下項(xiàng)目結(jié)構(gòu),將原本的Controllers目錄刪除,同時(shí)刪除Models目錄下的ErrorViewModel類,然后將Quickstart目錄重命名為Controllers,編譯代碼,代碼應(yīng)該可以編譯通過,接下來就是實(shí)現(xiàn)我們自己的Identity。

定制Identity Service

為了能夠展現(xiàn)一個(gè)標(biāo)準(zhǔn)的應(yīng)用場景,我自己定義了User和Role對(duì)象,它們分別繼承于IdentityUser和IdentityRole類:

public class AppUser : IdentityUser{    public string DisplayName { get; set; }}public class AppRole : IdentityRole{    public string Description { get; set; }}


當(dāng)然,Data目錄下的ApplicationDbContext也要做相應(yīng)調(diào)整,它應(yīng)該繼承于IdentityDbContext<AppUser, AppRole, string>類,這是因?yàn)槲覀兪褂昧俗远x的IdentityUser和IdentityRole的實(shí)現(xiàn):

public class ApplicationDbContext : IdentityDbContext<AppUser, AppRole, string>{    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)        : base(options)    {    }}


之后修改Startup.cs里的ConfigureServices方法,通過調(diào)用AddIdentity、AddIdentityServer以及AddDbContext,將ASP.NET Identity、IdentityServer4以及存儲(chǔ)認(rèn)證數(shù)據(jù)所使用的Entity Framework Core的依賴全部注冊(cè)進(jìn)來。為了測試方便,目前我們還是使用Developer Signing Credential,對(duì)于Identity Resource、API Resource以及Clients,我們也是暫時(shí)先寫死(hard code):

public void ConfigureServices(IServiceCollection services){    services.AddDbContext<ApplicationDbContext>(options =>        options.UseSqlServer(            Configuration.GetConnectionString("DefaultConnection")));    services.AddIdentity<AppUser, AppRole>()        .AddEntityFrameworkStores<ApplicationDbContext>()        .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())      .AddAspNetIdentity<AppUser>();    services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin()       .AllowAnyMethod()       .AllowAnyHeader()));    services.AddControllersWithViews();    services.AddRazorPages();    services.AddControllers();}


然后,調(diào)整Configure方法的實(shí)現(xiàn),將IdentityServer加入進(jìn)來,同時(shí)配置CORS使得站點(diǎn)能夠被跨域訪問:

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: "{controller=Home}/{action=Index}/{id?}");        endpoints.MapRazorPages();    });}


完成這部分代碼調(diào)整后,編譯是通不過的,因?yàn)槲覀冞€沒有定義IdentityServer4的IdentityResource、API Resource和Clients。在項(xiàng)目中新建一個(gè)Config類,代碼如下:

public static class Config{    public static IEnumerable<IdentityResource> GetIdentityResources() =>         new IdentityResource[]        {            new IdentityResources.OpenId(),            new IdentityResources.Email(),            new IdentityResources.Profile()        };    public static IEnumerable<ApiResource> 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 IEnumerable<Client> 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是指允許應(yīng)用程序訪問用戶的哪些身份認(rèn)證資源,比如,用戶的電子郵件或者其它用戶賬戶信息,在Open ID Connect規(guī)范中,這些信息會(huì)被轉(zhuǎn)換成Claims,保存在User Identity的對(duì)象里;ApiResource用來指定被IdentityServer4所保護(hù)的資源,比如這里新建了一個(gè)ApiResource,用來保護(hù)Weather API,它定義了自己的Scope和UserClaims。Scope其實(shí)是一種關(guān)聯(lián)關(guān)系,它關(guān)聯(lián)著Client與ApiResource,用來表示什么樣的Client對(duì)于什么樣的ApiResource具有怎樣的訪問權(quán)限,比如在這里,我定義了兩個(gè)Client:angular和webapi,它們對(duì)Weather API都可以訪問;UserClaims定義了當(dāng)認(rèn)證通過之后,IdentityServer4應(yīng)該向請(qǐng)求方返回哪些Claim。至于Client,就比較容易理解了,它定義了客戶端能夠以哪幾種方式來向IdentityServer4提交請(qǐng)求。

至此,我們的源代碼就可以編譯通過了,成功編譯之后,還需要使用Entity Framework Core所提供的命令行工具或者Powershell Cmdlet來初始化數(shù)據(jù)庫。我這里選擇使用Visual Studio 2019 Community中的Package Manager Console,在執(zhí)行數(shù)據(jù)庫更新之前,確保appsettings.json文件里設(shè)置了正確的SQL Server連接字符串。當(dāng)然,你也可以選擇使用其它類型的數(shù)據(jù)庫,只要對(duì)ConfigureServices方法做些相應(yīng)的修改即可。在Package Manager Console中,依次執(zhí)行下面的命令:

Add-Migration ModifiedUserAndRole -Context ApplicationDbContextAdd-Migration ModifiedUserAndRole –Context PersistedGrantDbContextUpdate-Database -Context ApplicationDbContextUpdate-Database -Context PersistedGrantDbContext

效果如下:

打開SQL Server Management Studio,看到數(shù)據(jù)表都已成功創(chuàng)建:

由于IdentityServer4的模板所產(chǎn)生的代碼使用的是mock user,也就是IdentityServer4里默認(rèn)的TestUser,因此,相關(guān)部分的代碼需要被替換掉,最主要的部分就是AccountController的Login方法,將該方法中的相關(guān)代碼替換為:

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 it's 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,以完成登錄邏輯。

新用戶注冊(cè)API

由IdentityServer4所提供的默認(rèn)UI模板中沒有包括新用戶注冊(cè)的頁面,開發(fā)者可以根據(jù)自己的需要向Identity Service中增加View來提供注冊(cè)界面。不過為了快速演示,我打算先增加兩個(gè)API,然后使用curl來新建一些用于測試的角色(Role)和用戶(User)。下面的代碼為客戶端提供了注冊(cè)角色和注冊(cè)用戶的API:

public 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 List<string> 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 Task<IActionResult> 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 List<string>();        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 Task<IActionResult> 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));}

在上面的代碼中,值得關(guān)注的就是register-account API中的幾行AddClaimAsync調(diào)用,我們將一些用戶信息數(shù)據(jù)加入到User Identity的Claims中,比如,將用戶的角色信息,通過逗號(hào)分隔的字符串保存為Claim,在后續(xù)進(jìn)行用戶授權(quán)的時(shí)候,會(huì)用到這些數(shù)據(jù)。

創(chuàng)建一些基礎(chǔ)數(shù)據(jù)

運(yùn)行我們已經(jīng)搭建好的Identity Service,然后使用下面的curl命令創(chuàng)建一些基礎(chǔ)數(shù)據(jù):

curl -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":"P@ssw0rd123","displayName":"Sunny Chen","email":"daxnet@163.com","roleNames":["admin"]}'   -H 'Content-Type:application/json' --insecurecurl -X POST https://localhost:7890/api/account/register-account   -d '{"userName":"acqy","password":"P@ssw0rd123","displayName":"Qingyang Chen","email":"qychen@163.com"}'   -H 'Content-Type:application/json' --insecure

完成這些命令后,系統(tǒng)中會(huì)創(chuàng)建一個(gè)admin的角色,并且會(huì)創(chuàng)建daxnet和acqy兩個(gè)用戶,daxnet具有admin角色,而acqy則沒有該角色。

使用瀏覽器訪問https://localhost:7890,點(diǎn)擊主頁的鏈接進(jìn)入登錄界面,用已創(chuàng)建的用戶名和密碼登錄,可以看到如下的界面,表示Identity Service的開發(fā)基本完成:

小結(jié)

一篇文章實(shí)在是寫不完,今天就暫且告一段落吧,下一講我將介紹Weather API和基于Ocelot的API網(wǎng)關(guān),整合Identity Service進(jìn)行身份認(rèn)證。

源代碼

訪問以下Github地址以獲取源代碼:

https://github.com/daxnet/identity-demo

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
微服務(wù)框架Spring Cloud介紹 Part3: Mysteam項(xiàng)目結(jié)構(gòu)與開發(fā)用戶注冊(cè)服務(wù)
Asp.Net Core 中IdentityServer4 授權(quán)中心之應(yīng)用實(shí)戰(zhàn)
淺析C#中單點(diǎn)登錄的原理和使用
datagrid +combox
ASP.NET Core MVC學(xué)習(xí)筆記
基于令牌的身份驗(yàn)證使用的ASP.NET Web API 2,Owin和身份
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服