什么是身份認證
身份認證是指當客戶端訪問服務端資源時,驗證客戶端是否合法的一種機制
什么是授權
為什么要使用身份驗證和授權
為了保證服務端資源的安全,我們要理解必須從真實項目中去理解
身份認證和授權方式有哪些
1、Base認證
Base64編號認證 == https
2、Digest認證
MD5消息摘要認證 == https
3、Bearer認證
就是基於token(電子身份證)認證,講用戶信息等其他信息轉換成為token,然后以token進行認證
token認證是一種無狀態的認證方式,能夠無限擴展,特別適合做單點登錄
3.1 OAuth 2.0 ==== 授權方式的認證
3.2 JWT的也是一種身份認證
token 分兩種類型
引用類型token == OAuth 2.0
無用戶相關信息
自包含token
有用戶相關信息 JWT === 地址,電話,id,等
在實際分布式項目中,大部分都是使用Bearer來進行身份認證和身份授權
在分布式項目或者微服務項目中,為了保證系統統一登錄(SSO登錄),
使用OpenID協議標准來規范身份認證功能 === 規范
同時使用OAuth 2.0協議標准來規范權限訪問 === 規范
為了將身份認證(單點登錄)和授權結合起來,所以出現了OpenID Connect協議標准 === 接口
OpenID Connect = OpenID + OAuth 2.0
SSO+OAuth 2.0可以實現單點登錄和授權
IdentityServer4 也可以實現單點登錄和授權
然后綜合實現了這些框架就是今天要講的IdentityServer4 身份認證服務器
如何在項目中使用IdentityServer4
IdentityServer4 是什么
IdentityServer是基於OpenID Connect協議標准的身份認證和授權程序,它實現了OpenID Connect 和 OAuth 2.0 協議。
IdentityServer4 功能
保護你的資源 使用本地帳戶或通過外部身份提供程序對用戶進行身份驗證 提供會話管理和單點登錄 管理和驗證客戶機 向客戶發出標識和訪問令牌 驗證令牌
IdentityServer4 內部概念
下面對以下幾個轉用術語進行解釋
用戶(User) 用戶是使用已注冊的客戶端(指在id4中已經注冊)訪問資源的人。
客戶端(Client)
客戶端就是從identityserver請求令牌的軟件(你可以理解為一個app即可),既可以通過身份認證令牌來驗證識別用戶身份,又可以通過授權令牌來訪問服務端的資源。但是客戶端首先必須在申請令牌前已經在identityserver服務中注冊過。
實際客戶端不僅可以是Web應用程序,app或桌面應用程序(你就理解為pc端的軟件即可),SPA,服務器進程等。
資源(Resources)
資源就是你想用identityserver保護的東東,可以是用戶的身份數據或者api資源。 每一個資源都有一個唯一的名稱,客戶端使用這個唯一的名稱來確定想訪問哪一個資源(在訪問之前,實際identityserver服務端已經配置好了哪個客戶端可以訪問哪個資源,所以你不必理解為客戶端只要指定名稱他們就可以隨便訪問任何一個資源)。
用戶的身份信息實際由一組claim組成,例如姓名或者郵件都會包含在身份信息中(將來通過identityserver校驗后都會返回給被調用的客戶端)。
API資源就是客戶端想要調用的功能(通常以json或xml的格式返回給客戶端,例如webapi,wcf,webservice),通常通過webapi來建立模型,但是不一定是webapi,我剛才已經強調可以使其他類型的格式,這個要看具體的使用場景了。
身份令牌(顧名思義用於做身份認證,例如sso其實主要就是用於身份認證) id_token jwt 一個身份令牌指的就是對認證過程的描述。它至少要標識某個用戶(Called the sub aka subject claim)的主身份信息,和該用戶的認證時間和認證方式。但是身份令牌可以包含額外的身份數據,具體開發者可以自行設定,但是一般情況為了確保數據傳輸的效率,開發者一般不做過多額外的設置,大家也可以根據使用場景自行決定。
訪問令牌(用於做客戶端訪問授權) access_token oauth 2.0 訪問令牌允許客戶端訪問某個 API 資源。客戶端請求到訪問令牌,然后使用這個令牌來訪問 API資源。訪問令牌包含了客戶端和用戶(如果有的話,這取決於業務是否需要,但通常不必要)的相關信息,API通過這些令牌信息來授予客戶端的數據訪問權限。
IdentityServer4 角色邏輯關系圖
見圖部分
微服務中使用IdentityServer4
IdentityServer4 官網地址
中文地址:http://www.identityserver.com.cn/Home/Detail/Zhengtizongshu
英文地址:https://identityserver4.readthedocs.io/en/3.1.0/
客戶端認證模式
條件
1、IdentityServer4 授權服務器(IdentityServer)
2、客戶端(Client)
3、微服務系統(Resources)
4、用戶
步驟
1、NutGet下載IdentityServer4 在資源服務器中
1.1、在IOC容器中配置IdentityServer4
// 1、ioc容器中添加IdentityServer4
services.AddIdentityServer()
.AddDeveloperSigningCredential()// 1、用戶登錄配置
.AddInMemoryApiResources(Config.GetApiResources()) // 2、存儲Api資源
.AddInMemoryClients(Config.GetClients()) // 3、存儲客戶端(模式)
1.2、使用IdentityServer4
// 1、使用IdentityServe4
app.UseIdentityServer();
//2、添加靜態資源訪問
app.UseStaticFiles();
1.3、配置測試用戶Config類
/// <summary>
/// 1、Identity測試使用
/// </summary>
public class Config
{
/// <summary>
/// 1、微服務API資源
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("TeamService", "TeamService api需要被保護")
};
}
/// <summary>
/// 2、客戶端
/// </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 = { "TeamService" }
}
};
}
}
2、微服務配置
2.1 Nuget下載IdentityServer4.AccessTokenValidation
2.2 配置身份和授權認證(配置認證資源,讓資源服務器進行保護)
// 6、校驗AccessToken,從身份校驗中心進行校驗
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options => {
options.Authority = "http://localhost:5005"; // 1、授權中心地址
options.ApiName = "TeamService"; // 2、api名稱(項目具體名稱)
options.RequireHttpsMetadata = false; // 3、https元數據,不需要
});
2.3 開啟身份認證
app.UseAuthentication(); // 1、使用身份驗證
3、客戶端配置
3.1 Nuget下載IdentityModel
3.2 調用代碼即可
以上默認客戶端認證模式
客戶端密碼認證模式
條件
1、用戶Test
步驟
1、在Config中添加新客戶端
new Client
{
ClientId = "client-password",
// 使用用戶名密碼交互式驗證
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
// 用於認證的密碼
ClientSecrets =
{
new Secret("secret".Sha256())
},
// 客戶端有權訪問的范圍(Scopes)
AllowedScopes = { "TeamService" }
},
2、在config中添加test用戶
/// <summary>
/// 客戶端下面的用戶
/// </summary>
/// <returns></returns>
public static List<TestUser> GetUsers()
{
return new List<TestUser>()
{
new TestUser
{
SubjectId="1",
Username="tony",
Password="123456"
}
};
}
3、在startup中添加配置
// 1、ioc容器中添加IdentityServer4
services.AddIdentityServer()
.AddDeveloperSigningCredential()// 1、用戶登錄配置
.AddInMemoryApiResources(Config.GetApiResources()) // 2、存儲Api資源
.AddInMemoryClients(Config.GetClients()) // 3、存儲客戶端(模式)
.AddTestUsers(Config.GetUsers())// 4、添加登錄用戶(模式)
4、 開始調用
在代碼中已經寫好調用過程
授權碼模式認證(通過code獲取assess_token)
條件
1、OpenIdConnect
步驟
1、Nuget下載Microsoft.AspNetCore.Authentication.OpenIdConnect
2、認證服務器配置
2.1 在config中添加openid身份資源聲明
public static IEnumerable<IdentityResource> Ids => new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() };
2.2 在config中添加客戶端
// openid客戶端 new Client { ClientId="client-code", ClientSecrets={new Secret("secret".Sha256())}, AllowedGrantTypes=GrantTypes.Code, RequireConsent=false, RequirePkce=true, RedirectUris={ "https://localhost:5006/signin-oidc"}, PostLogoutRedirectUris={ "https://localhost:5006/signout-callback-oidc"}, AllowedScopes=new List<string>{ IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "TeamService" // 啟用對刷新令牌的支持 }, // 增加授權訪問 AllowOfflineAccess=true }
2.3 添加openidtest用戶
// openid 身份驗證 new TestUser{SubjectId = "818727", Username = "張三", Password = "123456", Claims = { new Claim(JwtClaimTypes.Name, "張三"), new Claim(JwtClaimTypes.GivenName, "三"), new Claim(JwtClaimTypes.FamilyName, "張"), new Claim(JwtClaimTypes.Email, "zhangsan@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://zhangsan.com"), // new Claim(JwtClaimTypes.Address, @"{ '城市': '杭州', '郵政編碼': '310000' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) } }
2.4、在startup中添加配置
// 1、ioc容器中添加IdentityServer4 services.AddIdentityServer() .AddDeveloperSigningCredential()// 1、用戶登錄配置 .AddInMemoryApiResources(Config.GetApiResources()) // 2、存儲Api資源 .AddInMemoryClients(Config.GetClients()) // 3、存儲客戶端(模式) .AddTestUsers(Config.GetUsers())// 4、添加登錄用戶(模式) .AddInMemoryIdentityResources(Config.Ids); // 4、使用openid模式
2.5 准備登陸ui頁面
代碼中已經准備好
3、客戶端配置
3.1 startup.cs文件中配置
// 1、添加身份驗證 // 我們使用cookie來本地登錄用戶(通過“Cookies”作為DefaultScheme),並且將DefaultChallengeScheme設置為oidc。因為當我們需要用戶登錄時,我們將使用OpenID Connect協議。 services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) // 添加可以處理cookie的處理程序 .AddCookie("Cookies") // 用於配置執行OpenID Connect協議的處理程序 .AddOpenIdConnect("oidc", options => { options.Authority = "http://localhost:5005"; // 受信任令牌服務地址 options.RequireHttpsMetadata = false; options.ClientId = "client-code"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.SaveTokens = true; // 用於將來自IdentityServer的令牌保留在cookie中 // 1、添加授權訪問api的支持 options.Scope.Add("TeamService"); options.Scope.Add("offline_access"); });
3.2 使用配置
// 1、使用靜態頁面 app.UseStaticFiles(); // 2、開啟身份驗證 app.UseAuthentication();
IdentityServer4持久化
主要是將資源數據,用戶數據,身份數據全部進行放在數據庫進行存儲。
條件
1、ConfigurationDbContext
用於配置數據,如clients,resources和scopes。
2、PersistedGrantDbContext
用於臨時操作數據,如授權代碼和刷新令牌
步驟
1、Nuge安裝IdentityServer4.EntityFramework
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
2、重新配置Start.cs文件
// 2、資源客戶端支持化操作 var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; var connectionString = Configuration.GetConnectionString("DefaultConnection"); services.AddIdentityServer() .AddConfigurationStore(options => { options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)); }; }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)); }; }) .AddTestUsers(Config.GetUsers()) .AddDeveloperSigningCredential();
3、appsettings.json中的配置
{ "ConnectionStrings": { "DefaultConnection": "Data Source=.;Initial Catalog=IndentityServer4;Persist Security Info=True;User ID=sa;Password=tony" } }
4、執行遷移腳本
dotnet ef migrations add PersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb dotnet ef migrations add ConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
5、示例數據存儲
// 1、將config中數據存儲起來 private void InitializeDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) { serviceScope.ServiceProvider.GetService<PersistedGrantDbContext>().Database.Migrate(); var context = serviceScope.ServiceProvider.GetService<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.Ids) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (!context.ApiResources.Any()) { foreach (var resource in Config.GetApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); } } }
6、客戶端進行訪問
IdentityServer4 用戶進行持久化(數據存儲)
條件
1、Identity
步驟
1、Nuget安裝Microsoft.AspNetCore.Identity.EntityFrameworkCore
2、添加上下文類
public class IdentityServerUserDbContext : IdentityDbContext<IdentityUser> { public IdentityServerUserDbContext(DbContextOptions<IdentityServerUserDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } }
3、startup.cs文件配置
services.AddDbContext<IdentityServerUserDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); }); // 1.1 添加用戶 services.AddIdentity<IdentityUser, IdentityRole>(options => { // 1.2 密碼復雜度配置 options.Password.RequireDigit = true; options.Password.RequiredLength = 6; options.Password.RequiredUniqueChars = 1; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; }) .AddEntityFrameworkStores<IdentityServerUserDbContext>() .AddDefaultTokenProviders();
4、執行遷移腳本
dotnet ef migrations add IdentityServerUserDbMigration -c IdentityServerUserDbContext -o Data/Migrations/IdentityServer/IdentityServerUserDb
5、示例數據存儲
// 2、將用戶中數據存儲起來 private void InitializeUserDatabase(IApplicationBuilder app) { using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) { var context = serviceScope.ServiceProvider.GetService<IdentityServerUserDbContext>(); context.Database.Migrate(); var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); var idnetityUser = userManager.FindByNameAsync("tony").Result; if (idnetityUser == null) { idnetityUser = new IdentityUser { UserName = "zhangsan", Email = "zhangsan@email.com" }; var result = userManager.CreateAsync(idnetityUser, "123456").Result; if (!result.Succeeded) { throw new Exception(result.Errors.First().Description); } result = userManager.AddClaimsAsync(idnetityUser, new Claim[] { new Claim(JwtClaimTypes.Name, "tony"), new Claim(JwtClaimTypes.GivenName, "tony"), new Claim(JwtClaimTypes.FamilyName, "tony"), new Claim(JwtClaimTypes.Email, "tony@email.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://tony.com") }).Result; if (!result.Succeeded) { throw new Exception(result.Errors.First().Description); } } } }
6、AccountController修改Login代碼
public class AccountController : Controller { private UserManager<ApplicationUser> _userManager; private SignInManager<ApplicationUser> _signInManager; private IIdentityServerInteractionService _interaction; public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IIdentityServerInteractionService interaction) { _userManager = userManager; _signInManager = signInManager; _interaction = interaction; } public IActionResult Login(string returnUrl) { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl) { if (!ModelState.IsValid) { return View(); } var user = await _userManager.FindByEmailAsync(loginViewModel.Email); if (user == null) { ModelState.AddModelError(nameof(loginViewModel.Email), $"Email {loginViewModel.Email} not exists"); } else { if (await _userManager.CheckPasswordAsync(user, loginViewModel.Password)) { AuthenticationProperties props = null; if (loginViewModel.RememberMe) { props = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30)) }; } await _signInManager.SignInAsync(user, props); if (_interaction.IsValidReturnUrl(returnUrl)) { return Redirect(returnUrl); } return Redirect("~/"); } ModelState.AddModelError(nameof(loginViewModel.Password), "Wrong password"); } return View(loginViewModel); } }
ocelot集成 IdentityServer4
條件
1、IdentityServer4
步驟
1、Nuget安裝IdentityServer4.AccessTokenValidation
2、在startup中文件配置IndentityServer4
// 1、配置IdentityServer var authenticationProviderKey = "OcelotKey"; services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5005"; // 1、授權中心地址 options.ApiName = "TeamService"; // 2、api名稱(項目具體名稱) options.RequireHttpsMetadata = false; // 3、https元數據,不需要 options.SupportedTokens = SupportedTokens.Both; });
3、 在ocelot.json中需要加入驗證的ReRoute中
"ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 1001 }, { "Host": "localhost", "Port": 1002 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" }, "AuthenticationOptions": { "AuthenticationProviderKey": "OcelotKey", "AllowedScopes": [] } } ]
ocelot動態路由
{ "DynamicReRoutes": [ { "ServiceName": "TeamService", "RateLimitRule": { "ClientWhitelist": [], "EnableRateLimiting": true, "Period": "1s", "PeriodTimespan": 3000, "Limit": 3 } } ], "GlobalConfiguration": { "RequestIdKey": null, "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "Consul" }, "RateLimitOptions": { "ClientIdHeader": "ClientId", "QuotaExceededMessage": "", "RateLimitCounterPrefix": "", "DisableRateLimitHeaders": false, "HttpStatusCode": 428 }, "DownstreamScheme": "http" } }