關于 OAuth 2.0 的相關內容,點擊查看:ASP.NET WebApi OWIN 實現 OAuth 2.0
OpenID 是一個去中心化的網上身份認證系統。對于支持 OpenID 的網站,用戶不需要記住像用戶名和密碼這樣的傳統驗證標記。取而代之的是,他們只需要預先在一個作為 OpenID 身份提供者(identity provider, IdP)的網站上注冊。OpenID 是去中心化的,任何網站都可以使用 OpenID 來作為用戶登錄的一種方式,任何網站也都可以作為 OpenID 身份提供者。OpenID 既解決了問題而又不需要依賴于中心性的網站來確認數字身份。
OpenID 相關基本術語:
- 最終用戶(End User):想要向某個網站表明身份的人。
- 標識(Identifier):最終用戶用以標識其身份的 URL 或 XRI。
- 身份提供者(Identity Provider, IdP):提供 OpenID URL 或 XRI 注冊和驗證服務的服務提供者。
- 依賴方(Relying Party, RP):想要對最終用戶的標識進行驗證的網站。
以上概念來自:https://zh.wikipedia.org/wiki/OpenID
針對 .NET Core 跨平臺,微軟官方并沒有針對 OAuth 2.0 的實現(Microsoft.AspNetCore.Authentication.OAuth
組件,僅限客戶端),IdentityServer4 實現了 ASP.NET Core 下的 OpenID Connect 和 OAuth 2.0,IdentityServer4 也是微軟基金會成員。
閱讀目錄:
- OpenID 和 OAuth 的區別
- 客戶端模式(Client Credentials)
- 密碼模式(resource owner password credentials)
- 簡化模式-With OpenID(implicit grant type)
- 簡化模式-With OpenID & OAuth(JS 客戶端調用)
- 混合模式-With OpenID & OAuth(Hybrid Flow)
- ASP.NET Core Identity and Using EntityFramework Core for configuration data
開源地址:https://github.com/yuezhongxin/IdentityServer4.Demo
1. OpenID 和 OAuth 的區別
簡單概括:
- OpenID:authentication(認證),用戶是誰?
- OAuth:authorization(授權),用戶能做什么?
其實,OAuth 的密碼授權模式和 OpenID 有些類似,但也不相同,比如用戶登錄落網選擇微博快捷登錄方式,大致的區別:
- OAuth:用戶在微博授權頁面輸入微博的賬號和密碼,微博驗證成功之后,返回 access_token,然后落網拿到 access_token 之后,再去請求微博的用戶 API,微博授權中心驗證 access_token,如果驗證通過,則返回用戶 API 的請求數據給落網。
- OpenID:落網可以沒有用戶的任何實現,落網需要確認一個 URL 標識(可以是多個),然后用戶登錄的時候,選擇一個 URL 進行登錄(比如微博),跳轉到微博 OpenID 登錄頁面,用戶輸入微博的賬號和密碼,微博驗證成功之后,按照用戶的選擇,返回用戶的一些信息。
可以看到,OAuth 首先需要拿到一個授權(access_token),然后再通過這個授權,去資源服務器(具體的 API),獲取想要的一些數據,上面示例中,用戶 API 只是資源服務器的一種(可以是視頻 API、文章 API 等等),在這個過程中,OAuth 最重要的就是獲取授權(四種模式),獲取到授權之后,你就可以通過這個授權,做授權范圍之類的任何事了。
而對于 OpenID 來說,授權和它沒任何關系,它只關心的是用戶,比如落網,可以不進行用戶的任何實現(具體體現就是數據庫沒有 User 表),然后使用支持 OpenID 的服務(比如微博),通過特定的 URL 標識(可以看作是 OpenID 標識),然后輸入提供服務的賬號和密碼,返回具體的用戶信息,對于落網來說,它關心的是用戶信息,僅此而已。
上面其實是 OAuth 的授權,所以會有“獲得以下權限”提示,如果是 OpenID 的話,“權限”應該改為“用戶信息”。
支持 OpenID 的服務列表:http://openid.net/get-an-openid/
OpenID 流程圖(來自 Using OpenID):
2. 客戶端模式(Client Credentials)
簡單概述:客戶端提供 ClientId 和 ClientSecret 給認證授權服務,驗證如果成功,返回 access_token,客戶端拿到 access_token,訪問 API 資源服務。
2.1 認證授權服務配置
創建 ASP.NET Core 站點,Startup 配置修改如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource("api1", "My API")
})
.AddInMemoryClients(new List<Client>
{
// client credentials client
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
IdentityServer4 中AddInMemory
的相關配置,都是 Mock 的(代碼配置),也可以把這些配置存儲在數據庫中,這個后面再講。
AddInMemoryApiResources
增加的 API 資源服務(List 集合),也就此認證授權服務所管轄的 API 資源,比如上面配置的 api1,這個會在客戶端調用的時候用到,如果不一致,是不允許訪問的,另外,Clinet 中配置的AllowedScopes = { "api1" }
,表示此種授權模式允許的 API 資源集合(前提是需要添加ApiResource
)。
配置很簡單,我們也可以訪問http://localhost:5000/.well-known/openid-configuration
,查看具體的配置信息:
2.2 API 資源服務配置
API 資源服務站點,需要添加程序包:
"IdentityServer4.AccessTokenValidation": "1.0.1"
添加一個ValuesController
:
[Route("[controller]")]
[Authorize]
public class ValuesController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Content("hello world");
}
}
2.3 單元測試
需要添加程序包:
"IdentityModel": "2.0.0"
單元測試代碼:
[Fact]
public async Task ClientCredentials_Test()
{
// request token
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
Assert.False(tokenResponse.IsError);
Console.WriteLine(tokenResponse.Json);
// call api
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var response = await client.GetAsync("http://localhost:5010/values");
Assert.True(response.IsSuccessStatusCode);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
很簡單,和我們之前用 ASP.NET WebApi OWIN 實現 OAuth 2.0 一樣,只不過配置和調用簡化了很多,因為 IdentityServer4 替我們做了很多工作。
3. 密碼模式(resource owner password credentials)
簡單概述:客戶端提供 UserName 和 Password 給認證授權服務,驗證如果成功,返回 access_token,客戶端拿到 access_token,訪問 API 資源服務。
3.1 認證授權服務配置
創建 ASP.NET Core 站點,Startup 配置修改如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource("api1", "My API")
})
.AddInMemoryClients(new List<Client>
{
// resource owner password grant client
new Client
{
ClientId = "ro.client",
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "api1" }
}
})
.AddTestUsers(new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "xishuai",
Password = "123"
}
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
和客戶端模式不同的是,AllowedGrantTypes
授權模式改為了ResourceOwnerPassword
,然后增加了測試用戶(用來驗證用戶名和密碼),也可以存儲在數據庫中。
3.2 API 資源服務配置
API 資源服務站點,需要添加程序包:
"IdentityServer4.AccessTokenValidation": "1.0.1"
添加一個IdentityController
:
[Route("[controller]")]
[Authorize]
public class IdentityController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
3.3 單元測試
需要添加程序包:
"IdentityModel": "2.0.0"
單元測試代碼:
[Fact]
public async Task ResourceOwnerPassword_Test()
{
// request token
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret");
var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("xishuai", "123", "api1");
Assert.False(tokenResponse.IsError);
Console.WriteLine(tokenResponse.Json);
// call api
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var response = await client.GetAsync("http://localhost:5010/identity");
Assert.True(response.IsSuccessStatusCode);
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
4. 簡化模式-With OpenID(implicit grant type)
簡化模式在 IdentityServer4 中的實現,就是 OpenID Connect。
簡單概述:客戶端確定 URL(用戶認證服務),登錄在用戶認證服務,驗證成功,返回客戶端想要的用戶數據,并使此用戶為登錄狀態,可以在客戶端進行注銷用戶。
4.1 認證授權服務配置
創建 ASP.NET Core 站點,Startup 配置修改如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
})
.AddInMemoryClients(new List<Client>
{
// OpenID Connect implicit flow client (MVC)
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Implicit,
RedirectUris = { "http://localhost:5020/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5020" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
}
}
})
.AddTestUsers(new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "xishuai",
Password = "123",
Claims = new List<Claim>
{
new Claim("name", "xishuai"),
new Claim("website", "http://xishuai.cnblogs.com")
}
}
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
AddInMemoryIdentityResources
和AllowedScopes
所配置的,是客戶端允許訪問的用戶信息,具體查看:Requesting Claims using Scope Values
ClientId 很重要,必須和客戶端一一對應,所以想要使用 OpenID 認證服務的客戶端,需要向提供 OpenID 認證服務的機構,申請一個 ClientId,OpenID 認證服務會統一發放一個用戶登錄的 URL。
TestUser
中的Claims
配置,其實就是IdentityServerConstants.StandardScopes.Profile
。
另外,還有用戶登錄的一些操作代碼,這邊就不貼了,可以查看具體的實現:ImplicitServer.Web
4.2 客戶端服務配置
創建 ASP.NET Core 站點,添加程序包:
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.*",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.*"
Startup 配置修改如下:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5001",
RequireHttpsMetadata = false,
ClientId = "mvc",
SaveTokens = true
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
UseOpenIdConnectAuthentication
配置中的Authority
,就是 OpenID 認證服務的 URL。
添加一個HomeController
:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult Secure()
{
ViewData["Message"] = "Secure page.";
return View();
}
public async Task Logout()
{
await HttpContext.Authentication.SignOutAsync("Cookies");
await HttpContext.Authentication.SignOutAsync("oidc");
}
public IActionResult Error()
{
return View();
}
}
訪問 Secure 頁面,跳轉到認證服務地址,進行賬號密碼登錄,Logout 用于用戶的注銷操作。
4.3 Web 測試
5. 簡化模式-With OpenID & OAuth(JS 客戶端調用)
簡單概述:客戶端確定 URL(用戶認證服務),登錄在用戶認證服務,驗證成功,返回客戶端想要的用戶數據 和 access_token,并使此用戶為登錄狀態,可以在客戶端進行注銷用戶,客戶端可以拿到 access_token,去訪問授權范圍之內的 API 資源。
需要注意的是:因為簡化模式,所以 access_token 是作為 URL 參數返回的。
5.1 認證授權服務配置
創建 ASP.NET Core 站點,Startup 配置修改如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
})
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource("api1", "My API")
})
.AddInMemoryClients(new List<Client>
{
// OpenID Connect implicit flow client (MVC)
new Client
{
ClientId = "js",
ClientName = "JavaScript Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { "http://localhost:5022/callback.html" },
PostLogoutRedirectUris = { "http://localhost:5022/index.html" },
AllowedCorsOrigins = { "http://localhost:5022" },
RequireConsent = false, //禁用 consent 頁面確認 https://github.com/IdentityServer/IdentityServer3/issues/863
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
}
}
})
.AddTestUsers(new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "xishuai",
Password = "123",
Claims = new List<Claim>
{
new Claim("name", "xishuai"),
new Claim("website", "http://xishuai.cnblogs.com")
}
}
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
因為涉及到訪問 API 資源操作,需要需要添加AddInMemoryApiResources
配置,AllowedScopes
也需要添加對應的 API 資源名稱,AllowAccessTokensViaBrowser = true
的配置的作用就是,可以在瀏覽器地址中訪問 access_token。
更多實現代碼,點擊查看:ImplicitServerWithJS.Web
5.2 API 資源服務配置
API 資源服務站點,需要添加程序包:
"IdentityServer4.AccessTokenValidation": "1.0.1",
"Microsoft.AspNetCore.Cors": "1.1.0"
Startup 配置修改如下:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("http://localhost:5022")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseCors("default");
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5003",
RequireHttpsMetadata = false,
ApiName = "api1"
});
app.UseMvc();
}
因為 JS 需要跨域訪問 API 資源服務,所以需要增加 CORS 配置。
添加一個IdentityController
:
[Route("[controller]")]
[Authorize]
public class IdentityController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
5.3 JS Web 站點測試
創建一個 ASP.NET Core 站點,添加oidc-client.js
前端組件,測試 JS 代碼:
/// <reference path="oidc-client.js" />
function log() {
document.getElementById('results').innerText = '';
Array.prototype.forEach.call(arguments, function (msg) {
if (msg instanceof Error) {
msg = "Error: " + msg.message;
}
else if (typeof msg !== 'string') {
msg = JSON.stringify(msg, null, 2);
}
document.getElementById('results').innerHTML += msg + '\r\n';
});
}
document.getElementById("login").addEventListener("click", login, false);
document.getElementById("api").addEventListener("click", api, false);
document.getElementById("logout").addEventListener("click", logout, false);
var config = {
authority: "http://localhost:5003",
client_id: "js",
redirect_uri: "http://localhost:5022/callback.html",
response_type: "id_token token",
scope:"openid profile api1",
post_logout_redirect_uri: "http://localhost:5022/index.html",
};
var mgr = new Oidc.UserManager(config);
mgr.getUser().then(function (user) {
if (user) {
log("User logged in", user.profile);
}
else {
log("User not logged in");
}
});
function login() {
mgr.signinRedirect();
}
function api() {
mgr.getUser().then(function (user) {
var url = "http://localhost:5012/identity";
var xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onload = function () {
log(xhr.status, JSON.parse(xhr.responseText));
}
xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
xhr.send();
});
}
function logout() {
mgr.signoutRedirect();
}
測試過程(注意下 URL 中的參數):
6. 混合模式-With OpenID & OAuth(Hybrid Flow)
混合模式(Hybrid Flow)是一種新的模式,是簡化模式(implicit flow)和驗證碼模式(authorization code flow)的混合。
簡單概述:客戶端確定 URL(用戶認證服務),登錄在用戶認證服務,驗證成功,返回客戶端想要的用戶數據 和 access_token,并使此用戶為登錄狀態,可以在客戶端進行注銷用戶,客戶端可以拿到 access_token,去訪問授權范圍之內的 API 資源。
和上面的簡化模式流程差不多,不過 access_token 不是通過瀏覽器獲取的,而是通過后臺服務獲取。
6.1 認證授權服務配置
創建 ASP.NET Core 站點,Startup 配置修改如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
})
.AddInMemoryApiResources(new List<ApiResource>
{
new ApiResource("api1", "My API")
})
.AddInMemoryClients(new List<Client>
{
// OpenID Connect implicit flow client (MVC)
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
RedirectUris = { "http://localhost:5021/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:5021" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api1"
},
AllowOfflineAccess = true
}
})
.AddTestUsers(new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "xishuai",
Password = "123",
Claims = new List<Claim>
{
new Claim("name", "xishuai"),
new Claim("website", "http://xishuai.cnblogs.com")
}
}
});
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(LogLevel.Debug);
app.UseDeveloperExceptionPage();
app.UseIdentityServer();
}
}
AllowedGrantTypes
配置改為HybridAndClientCredentials
,AllowOfflineAccess
需要設置為true
。
更多實現代碼,點擊查看:HybridServer.Web
6.2 API 資源服務配置
API 資源服務站點,需要添加程序包:
"IdentityServer4.AccessTokenValidation": "1.0.1"
Startup 配置修改如下:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
}
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:5002",
RequireHttpsMetadata = false,
ApiName = "api1"
});
app.UseMvc();
}
添加一個IdentityController
:
[Route("[controller]")]
[Authorize]
public class IdentityController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
6.3 客戶端服務配置
創建 ASP.NET Core 站點,添加程序包:
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.*",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.*",
"IdentityModel": "2.0.0"
Startup 配置修改如下:
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5002",
RequireHttpsMetadata = false,
ClientId = "mvc",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "api1", "offline_access" },
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
});
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
}
添加一個HomeController
:
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public IActionResult Secure()
{
ViewData["Message"] = "Secure page.";
return View();
}
public async Task Logout()
{
await HttpContext.Authentication.SignOutAsync("Cookies");
await HttpContext.Authentication.SignOutAsync("oidc");
}
public IActionResult Error()
{
return View();
}
public async Task<IActionResult> CallApiUsingClientCredentials()
{
var tokenClient = new TokenClient("http://localhost:5002/connect/token", "mvc", "secret");
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1");
var client = new HttpClient();
client.SetBearerToken(tokenResponse.AccessToken);
var content = await client.GetStringAsync("http://localhost:5011/identity");
ViewBag.Json = JArray.Parse(content).ToString();
return View("json");
}
public async Task<IActionResult> CallApiUsingUserAccessToken()
{
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
var content = await client.GetStringAsync("http://localhost:5011/identity");
ViewBag.Json = JArray.Parse(content).ToString();
return Content("json");
}
}
CallApiUsingClientCredentials
是通過客戶端模式獲取 access_token,CallApiUsingUserAccessToken
是通過上下文獲取保存的 access_token,其實和瀏覽器 URL 中獲取是一樣的意思,但需要配置SaveTokens = true
。
6.4 Web 測試
7. ASP.NET Core Identity and Using EntityFramework Core for configuration data
使用 ASP.NET Core Identity,就是用戶管理不由 OpenID 認證服務進行提供,ASP.NET Core Identity 就相當于用戶的一個管理者,比如用戶的存儲等。
我沒做這一塊的示例,配置比較簡單:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.AddTransient<IEmailSender, AuthMessageSender>();
services.AddTransient<ISmsSender, AuthMessageSender>();
// Adds IdentityServer
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
}
詳細使用:Using ASP.NET Core Identity
關于 IdentityServer4 的配置信息,可以使用 EntityFramework Core 進行存儲,配置如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var connectionString = @"server=(localdb)\mssqllocaldb;database=IdentityServer4.Quickstart;trusted_connection=yes";
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
// configure identity server with in-memory users, but EF stores for clients and resources
services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddTestUsers(Config.GetUsers())
.AddConfigurationStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)))
.AddOperationalStore(builder =>
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly)));
}
詳細使用:Using EntityFramework Core for configuration data
最后,簡要總結下使用 IdentityServer4 的幾種應用場景:
- 客戶端模式(Client Credentials):和用戶無關,用于應用程序與 API 資源的直接交互場景。
- 密碼模式(resource owner password credentials):和用戶有關,一般用于第三方登錄。
- 簡化模式-With OpenID(implicit grant type):僅限 OpenID 認證服務,用于第三方用戶登錄及獲取用戶信息,不包含授權。
- 簡化模式-With OpenID & OAuth(JS 客戶端調用):包含 OpenID 認證服務和 OAuth 授權,但只針對 JS 調用(URL 參數獲取),一般用于前端或無線端。
- 混合模式-With OpenID & OAuth(Hybrid Flow):推薦使用,包含 OpenID 認證服務和 OAuth 授權,但針對的是后端服務調用。
開源地址:https://github.com/yuezhongxin/IdentityServer4.Demo
參考資料:
文章列表