identity4 系列————啟航篇[二]
- 2022 年 8 月 14 日
- 筆記
- .net core(web)
前言
開始identity的介紹了。
正文
前文介紹了一些概念,如果概念不清的話,可以去前文查看。
//www.cnblogs.com/aoximin/p/13475444.html 對一些理論概念的初步介紹一下。
那麼什麼是identityserver4 呢?
IdentityServer is a free, open source OpenID Connect and OAuth 2.0 framework for ASP.NET Core.
Founded and maintained by Dominick Baier and Brock Allen, IdentityServer4 incorporates all the protocol implementations and extensibility points needed to integrate token-based authentication, single-sign-on and API access control in your applications. IdentityServer4 is officially certified by the OpenID Foundation and thus spec-compliant and interoperable. It is part of the .NET Foundation, and operates under their code of conduct. It is licensed under Apache 2 (an OSI approved license).
上面說identityserver 是免費開源的的OpenId Connect 和 oauth 2.0 框架。
這裡有人可能會有疑問openid connect 不是基於oauth 2.0嗎,這個是的呢。 只是openId connect 基於oauth 2.0 是為了實現身份相關的,而oauth 2.0 是授權其實和身份沒有關係。有些不需要身份的,依然可以使用identityservice 提供的oauth 2.0的實現。
簡單點說就是identityservice 實現了oauth 2.0,在oauth 2.0 的基礎上又實現了openid connect,你還可以利用oauth 2.0 實現其他的。
下面也介紹了他們兩個的關係:
OpenID Connect and OAuth 2.0 are very similar – in fact OpenID Connect is an extension on top of OAuth 2.0. The two fundamental security concerns, authentication and API access, are combined into a single protocol - often with a single round trip to the security token service.
We believe that the combination of OpenID Connect and OAuth 2.0 is the best approach to secure modern applications for the foreseeable future. IdentityServer4 is an implementation of these two protocols and is highly optimized to solve the typical security problems of today』s mobile, native and web applications.
現在就來實踐一下吧。
學習一個框架,先看下他的quick start 吧。
//identityserver4.readthedocs.io/en/latest/quickstarts/0_overview.html
這裡有個preparation 讓我們先安裝模板。
dotnet new -i IdentityServer4.Templates
可以看到這樣模板就安裝完成了。
然後隨便找一個目錄,進行通過模板創建。
先創建一個空的吧。
dotnet new is4empty -n IdentityServer
官方也介紹了這幾個文件是啥了。
IdentityServer.csproj - the project file and a Properties\launchSettings.json file
Program.cs and Startup.cs - the main application entry point
Config.cs - IdentityServer resources and clients configuration file
使用identity 也很簡單了。
第一步就是注入服務:
var builder = services.AddIdentityServer(options =>
{
// see //identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients);
public static class Config
{
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId()
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{ };
public static IEnumerable<Client> Clients =>
new Client[]
{ };
}
第二步就是注入中間件:
app.UseIdentityServer();
這裡就不直接看源碼了,identity4的源碼篇在後面,後面會有identity4的源碼篇, 後面的更新速度也會加快。
那麼來看看這幾個配置是什麼吧。
第一個是identityResource, 這個是什麼呢? 這個是用戶的身份資源。
比如郵箱地址,OpenId、 地址啊、電話號碼頭像之類的。
這些是用戶的身份資源,表示用戶的一些信息。
然後apiscopes 是什麼東西呢?
An API is a resource in your system that you want to protect. Resource definitions can be loaded in many ways, the template you used to create the project above shows how to use a 「code as configuration」 approach.
The Config.cs is already created for you. Open it, update the code to look like this:
public static class Config
{
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
}
官方也介紹了這個是幹什麼的, 這個就是受保護的api 資源。
為什麼要創建這個呢?
If you will be using this in production it is important to give your API a logical name.
Developers will be using this to connect to your api though your Identity server.
It should describe your api in simple terms to both developers and users.
上面是這個api的作用。
最後一個介紹的是客戶端, 這裡的客戶端並不是我們提到的前端。
我們知道identityServer 表示的是身份服務,那麼和其對接需要身份認證授權的就是客戶端了。
這一點需要理解,而不是說客戶端就是用戶展示層,客戶端和服務端都是相對而言的。
客戶端的註冊方式如下:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
這樣我們就註冊了一個客戶端, 這個客戶端獲取到access_token 能獲得的授權是api1。
當我們配置好這些,啟動程序後,我們將可以訪問一個url:
//localhost:5001/.well-known/openid-configuration
裏面是什麼東西呢?我們指定well-known 的意思是眾所周知的意思,那麼這個應該是ids 公開的信息,看一下。
裏面是ids 的一些配置,比如說grant_types_supported 支持哪些授權方式。
scopes_supported 表示支持哪些資源的授權。
claims_supported 表示的是支持哪些用戶身份信息。
我修改配置後:
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Address()
};
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
那麼我打開配置後是這樣的。
好了,這裡ids 就配置完成了,也就是identityServer 就配置完了。
那麼上面我們註冊了受保護的api。
public static IEnumerable<ApiScope> ApiScopes =>
new List<ApiScope>
{
new ApiScope("api1", "My API")
};
那麼我們如果建立一個api1,那麼我們該怎麼寫呢?
官網給了我們例子,那麼可以看一下吧。
//github.com/IdentityServer/IdentityServer4/tree/main/samples/Quickstarts/1_ClientCredentials
其實就是我們的api1, 這個怎麼寫的問題。
打開項目後,看到了這個Api,這個就是官方給了我們例子。
其實也是很簡單的, 在Startup中, 看下怎麼配置的。
// accepts any access token issued by identity server
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "//localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
// adds an authorization policy to make sure the token is for scope 'api1'
services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("scope", "api1");
});
});
上面配置了access_token的驗證方式。
然後增加了授權策略,scope 裏面必須有api1。
最後再管道中注入策略, 這樣就不用每個api 都加上策略了。
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization("ApiScope");
});
授權策略其實就是驗證scope 中帶有api1。
那麼現在就知道了,apiResouces 是什麼了, 且知道了api 怎麼進行驗證了。
那麼看下客戶端吧。
前文我們在ids註冊了客戶端為:
public static IEnumerable<Client> Clients =>
new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
那麼我們怎麼請求token呢?
// discover endpoints from metadata
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("//localhost:5001");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// request token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client",
ClientSecret = "secret",
Scope = "api1"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
Console.WriteLine("\n\n");
這裏面使用了IdentityModel, 裏面為我們封裝了對identityService的請求。
上面client.GetDiscoveryDocumentAsync(“//localhost:5001“); 其實就是請求:
//localhost:5001/.well-known/openid-configuration
通過這個獲取到TokenEndpoint。
然後通過tokenEndpoint 獲取到token。
裏面傳的也就是我們在identityServer 裏面註冊的客戶端信息了。
來看一下獲取的access_token 是怎麼樣的。
"eyJhbGciOiJSUzI1NiIsImtpZCI6IkM3OTEyQjMyMTREMTc1MTg3NzgyNDc1ODk4OEY1MjUxIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE2NjA0NjIyMjgsImV4cCI6MTY2MDQ2NTgyOCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImNsaWVudF9pZCI6ImNsaWVudCIsImp0aSI6IkMzRjEyMTQwMTdGQTRCRjMyNTQ1QzJEQTA1MTlFREVDIiwiaWF0IjoxNjYwNDYyMjI4LCJzY29wZSI6WyJhcGkxIl19.u_KqoiIsdu33Tl79CbMLeVSM3Avr2tRqeyrFe62_oPlCjD34AzIa0JI1afhneRxkZ4iftENlYapSzoa2QPfvc8G4AF4HyZN-MS1CjU2nNT_ASxg84nXWUR9k6e8D7PIc-gBlPNqje-zObJjiEb_2qDTkEgmBbx8gd8zjGrClwGXVo85yhYE2efF64ymP0FV9XE34Zi9OELtVL41xGYghw-BKEL-NAXFJGBqlkmqZ-EPccsNlaBq-EG0OwqTIVTg7JNGd5LRjLLIFmY79bsns5v02qJGCVluOF8dEKCG_sRqbw7VvyBC5vP5xUElx10TsPDCn9TxyAeMGObLYninnvQ"
然後通過這個token,我們用jwt解析一下。
可以看到這個token的訪問區域就是api1了。
然後通過這個token 去訪問api1吧。
api1 中有一個action為:
[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
訪問方式為:
// call api
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("//localhost:6001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
查看結果:
上面介紹了一個quick start。 但是僅僅這個是不滿足我們的業務需求的,我們還需要實現單點登錄。下一章介紹。
結
簡單整理一下identityServer4的使用,後面把那幾個例子解釋一下,然後就介紹實際開發中的問題,包括數據遷移、無法訪問等、跳轉慢等等等問題。