本文中的IdentityServer4基於上節的jenkins 進行docker自動化部署。
使用了MariaDB,EF Core,AspNetIdentity,Dockerhtml
Demo地址:https://sso.neverc.cn
Demo源碼:https://github.com/NeverCL/Geek.IdentityServer4git
OpenID Connect :經常使用的認證協議有SAML2p, WS-Federation and OpenID Connect – SAML2p。OpenID Connect是其中最新的協議。github
OAuth 2.0 :OAuth 2.0 是一種受權協議。經過Access Token能夠訪問受保護的API接口。web
OpenID Connect和OAuth 2.0很是類似,實際上,OpenID Connect是OAuth 2.0之上的一個擴展。
身份認證和API訪問這兩個基本的安全問題被合併爲一個協議 - 每每只需一次往返安全令牌服務。docker
IdentityServer4基於ASP.NET Core 2對這兩種協議的實現。shell
支持規範:https://identityserver4.readthedocs.io/en/release/intro/specs.htmljson
IdentityServer:提供OpenID Connect and OAuth 2.0 protocols.c#
User:IdentityServer中的用戶api
Client:第三方應用,包括web applications, native mobile or desktop applications, SPAs etc.瀏覽器
Resource:包含Identity data 和 APIs。這是認證受權中的標識。
Identity Token:標識認證信息,至少包含user的sub claim。
Access Token:標識受權信息,能夠包含Client 和 user的claim信息。
Client Credentials是最簡單的一種受權方式。
步驟:
dotnet new web -o Geek.IdentityServer4 && dotnet add Geek.IdentityServer4 package IdentityServer4
Startup:
services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()); ... app.UseIdentityServer();
Config:
public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("api1") }; } public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, Claims = { new Claim("name","名稱") }, AllowedScopes = { "api1" } }, } }
dotnet new web -o Geek.Api && dotnet add Geek.Api package IdentityServer4.AccessTokenValidation
Startup:
services.AddMvc(); services.AddAuthentication("Bearer")//AddIdentityServerAuthentication 默認SchemeName:Bearer .AddIdentityServerAuthentication(opt => { opt.ApiName = "api1"; opt.Authority = "https://sso.neverc.cn"; }); ... app.UseAuthentication(); app.UseMvc();
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 }); } }
Client:
dotnet new web -o Geek.Client && dotnet add Geek.Client package IdentityServer4.IdentityModel
Program:
var disco = await DiscoveryClient.GetAsync("https://sso.neverc.cn"); var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var response = await client.GetAsync("http://localhost:5001/identity"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content));
這種認證方式須要User提供用戶名和密碼,因此Client爲很是可信的應用纔可能使用這種方式。
步驟:
Config:
public static IEnumerable<Client> GetClients() { ... new Client { ClientId = "ro.client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } } } public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { SubjectId = "1", Username = "alice", Password = "password", } } }
Startup:
services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers());
var disco = await DiscoveryClient.GetAsync("https://sso.neverc.cn"); var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret"); var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password", "api1"); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var response = await client.GetAsync("http://localhost:5001/identity"); var content = await response.Content.ReadAsStringAsync(); Console.WriteLine(JArray.Parse(content));
區分Client Credentials 和 ResourceOwnerPassword 可經過 sub claim來區分
Implicit爲隱式模式,經過瀏覽器端直接傳輸id_token
步驟:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } ... new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.Implicit, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, } }
services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()) .AddInMemoryIdentityResources(Config.GetIdentityResources());
添加MvcUI:
在IdentityServer項目中powershell執行:
iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/release/get.ps1'))
public void ConfigureServices(IServiceCollection services) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddMvc(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "https://sso.neverc.cn"; options.ClientId = "mvc"; options.SaveTokens = true; }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication(); app.UseMvcWithDefaultRoute(); }
public class HomeController : ControllerBase { [Authorize] public ActionResult Index() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
在Implicit方式中,id_token在瀏覽器中傳輸是適用的,可是access_token不該該暴露在瀏覽器中。
Hybrid模式則是在Implicit的基礎上,再傳輸code,適用code模式來獲取access_token。
步驟:
Config:
new Client { ClientId = "hybrid", AllowedGrantTypes = GrantTypes.Hybrid, 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" }, };
Startup:
.AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "https://sso.neverc.cn"; options.ClientId = "mvc"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.SaveTokens = true; options.Scope.Add("api1"); });
Controller:
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"); }
在登陸完成後,便可經過認證獲得的access_token調用CallApiUsingUserAccessToken來調用API服務。
本文爲IdentityServer4作了基本的介紹。 實際上IdentityServer4還能夠很是靈活的與ASP.NET Identity 以及 EF Core等組合使用。 另外基於ASP.NET Core,因此IdentityServer4也支持跨平臺。