本文中的IdentityServer4基於上節的jenkins 進行docker自動化部署。
使用了MariaDB,EF Core,AspNetIdentity,Docker
Demo地址:https://sso.neverc.cn
Demo原始碼:https://github.com/NeverCL/Geek.IdentityServer4
簡介
OpenID Connect :常用的認證協議有SAML2p, WS-Federation and OpenID Connect – SAML2p。OpenID Connect是其中最新的協議。
OAuth 2.0 :OAuth 2.0 是一種授權協議。通過Access Token可以訪問受保護的API介面。
OpenID Connect和OAuth 2.0非常相似,實際上,OpenID Connect是OAuth 2.0之上的一個擴充套件。
身份認證和API訪問這兩個基本的安全問題被合併為一個協議 – 往往只需一次往返安全令牌服務。
IdentityServer4基於ASP.NET Core 2對這兩種協議的實現。
支援規範:https://identityserver4.readthedocs.io/en/release/intro/specs.html
關鍵詞
IdentityServer:提供OpenID Connect and OAuth 2.0 protocols.
User:IdentityServer中的使用者
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
Client Credentials是最簡單的一種授權方式。
步驟:
- 建立IdentityServer
- 定義APIs
- 定義Client
- 建立API
- 定義Authentication
- 使用Client
- 請求Token
- 使用Token
IdentityServer:
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" }
},
}
}
API:
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));
ResourceOwnerPassword
這種認證方式需要User提供使用者名稱和密碼,所以Client為非常可信的應用才可能使用這種方式。
步驟:
- 定義RO Client 和 User
- 使用Client
Identity Server
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());
Client
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
Implicit為隱式模式,通過瀏覽器端直接傳輸id_token
步驟:
- 配置IdentityServer
- 定義IdentityResources
- 定義mvc client
- 新增Mvc UI
- 建立mvc client
IdentityServer
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`))
MvcClient
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 });
}
}
Hybrid
在Implicit方式中,id_token在瀏覽器中傳輸是適用的,但是access_token不應該暴露在瀏覽器中。
Hybrid模式則是在Implicit的基礎上,再傳輸code,適用code模式來獲取access_token。
步驟:
- 定義Client
- 使用Client
IdentityServer配置
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"
},
};
MvcClient配置
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也支援跨平臺。