IdentityServer4實現原理

OAuth&OpenIDConnect是什麼?
最近由於工做的緣由,大概有兩個月時間沒寫博客了,原本今年給本身的目標是每月寫一篇,或許記錄工做中踩過的一些坑,或許學習一些新的技術框架。說實話IDS4的源碼我是在幾天前纔開始看的,由於工做須要實現一個認證受權服務中心,臨時抱佛腳研究了幾天源碼和查了一些資料,我我的的習慣是三方的開源框架,若是不瞭解源碼實現,通常狀況下不敢貿然使用。如下內容若是有說的不對的地方,但願你們及時指出。接下來我就結合OAuth&OIDC以及IDS4的源碼跟你們剖析一下Identityserver4(如下簡稱IDS4)的實現原理。
在介紹IDS4以前,咱們先來看下OAuth和OpenIDConnect(如下簡稱OIDC)協議的一些概念,由於只有在理解這兩個協議規範的基礎之上才能理解IDS4。
 
OAuth是什麼?最直接的方式,咱們能夠經過查看OAuth2的官方文檔,地址是 https://oauth.net/2/。官方文檔是英文版的,建議你們在閱讀IDS4的源碼以前多少看看這個文檔,我我的的理解是OAuth是一個開放式受權委託協議,是一種規範,它可讓資源全部者委託應用軟件(注意是委託),從IDP(IDP是一種服務提供商,NETCORE平臺下的實現是IDS4)獲取受權AccessToken訪問資源全部者受保護的資源。接下來咱們一塊兒看看OAuth的受權流程。
 
OAuth2的受權流程
在官方文檔裏邊大概有這麼一張圖片,描述了OAuth2的整個受權流程。
在介紹受權流程以前,我想先簡單解釋如下OAuth2受權裏邊涉及的這麼4個角色,我用紅圈標註了。1.Client咱們能夠把它理解爲客戶端應用;2.ResourceOwner也就是前面描述的資源全部者;3.AuthorizationServer就是受權服務提供商簡稱IDP,在本篇文章裏面就是IDS4;4.ResourceServer資源服務器,資源全部者受保護的資源就在這裏。下邊咱們一塊兒看看受權流程。
咱們以WEB-ASPNETCORE應用爲例,ResourceOwner(用戶)操做Client(WEB應用)去訪問ResourceServer(資源服務器)上面的某個受保護的資源,這時候由於ASPNETCORE內置的認證系統會重定向到AuthorizationServer(受權服務器),固然在受權以前還須要身份認證,OAuth2它無論認證,只管受權,通常狀況下在認證經過以後,會跳轉到一個受權頁面,裏面通常包含了Scope也就是你的權限範圍,最後完成受權。關於整個受權流程裏面涉及的參數以及返回值我在後續實例會詳細說。下面咱們來看下OAuth2裏邊支持哪些受權類型?
 
OAuth2的受權類型(模式)
從官方文檔的定義來看,OAuth2的受權類型主要包含6種,可是咱們常見的只有4種。1.AuthorizationCode受權碼類型,也是咱們經常使用的受權模式,通常是經過受權服務器中介完成,典型的場景就是我上面的例子WEB-ASPNETCORE應用,該模式主要是經過受權碼獲取AccessToken訪問令牌,並且受權碼是一次性的;2.Implicit簡化受權類型,有些朋友稱之爲隱式受權類型,它應該是code類型的簡化版本,沒有受權碼這個過程,受權服務器直接返回AccessToken到瀏覽器,安全相通常,可是使用方便,它的應用場景主要是瀏覽器內的客戶端應用,好比Angular等;3.ResourceOwnerPasswordCredentials密碼受權類型,名字比較長,安全係數不高,須要用戶與應用具備高度的信任的環境;4.ClientCredentials客戶端受權類型,通常應用場景就是資源不屬於某個全部者可是客戶端又須要獲取。受權類型概念也差很少簡單介紹完了,下面咱們再看看OAuth2端點這個概念。
 
OAuth2端點(EndPoint)
OAuth2端點主要分爲3個AuthorizationEndPoint(受權端點)和TokenPoint(令牌端點)以及RedirectionEndpoint(重定向端點)。受權端點通常是在瀏覽器裏面進行交互和處理,通俗一點說就是資源全部者委託客戶端從受權服務器那登陸以及受權,可使用Post|Get請求,必須支持Get請求。令牌端點通常是客戶端應用與受權服務器進行交互,通俗來講就是客戶端和受權服務器交換令牌token的過程都是須要tokenendpoint參與完成,必須使用Post請求。以上介紹的4種受權類型只有Implicit簡化受權類型沒有采用令牌端點,其餘均訪問了令牌端點。OAuth2受權協議就介紹到這吧,下邊咱們看看OIDC。
 
什麼是OIDC?
OIDC它是基於OAuth2的協議之上構建的一個簡單身份層,通俗的說就是身份認證協議,簡單理解就是OIDC = Authentication + Authorization + OAuth2。在OAuth2 in Action 這本書裏面把受權比喻成巧克力,這個巧克力不是最終的產品仍是一種原料,身份認證被比喻成軟糖,按照這本書的理解,咱們要製做巧克力軟糖,也就是須要一個基於OAuth2的身份認證協議,而OpenIDConnect就是這樣協議規範,它能夠工做在不一樣的身份認證供應商之間。關於OIDC和OAuth2角色映射本身查下資料吧,就是叫法不一樣,沒什麼暗箱操做,OIDC協議須要注意的是它定義了3個流程,AuthorizationCodeFlow、HybridFlow、ImplicitFlow,其實這3個流程對應的就是OAuth2的幾種受權類型,好比HybridFlow它叫混合流程,包含OAuth2受權類型裏面的code & implicit,概念就到這吧。
 
什麼是IDS4?
IdentityServer4 是ASPNETCore2.X系列量身打造的一款基於OpenIDConnect和OAuth2認證框架,也就是OIDC認證服務提供商。它實現了OpenIDConnect和OAuth2的大部分協議,同時遵循Apache2的這麼一個開源協議,github地址是 https://github.com/IdentityServer/IdentityServer4,你們若是下載建議先Star如下。上面說了這麼多,還不如一張圖來的實在。
這張圖不是我畫的,是從IDS4官網扣的。引用官方的描述IDS4大概能夠爲咱們作這麼幾件事。
1.保護你的資源
2.使用本地賬戶或經過外部身份提供程序對用戶進行身份驗證
3.提供會話管理和單點登陸
4.管理和驗證客戶機
5.向客戶發出標識和訪問令牌
6.驗證令牌
接下來咱們具體看看IDS4是如何實現OAuth2和OIDC完成咱們的統一身份認證和受權的,我會從OAuth2常見的受權類型裏面抽取兩個受權類型實例加以講解,而且結合IDS4框架源碼加以分析,咱們先從簡單的開始吧。
 
ClientCredentials
實例代碼是IDS4源碼裏面Samples文件下的Demo工程,你們能夠自行下載,GITHUB地址我在上面已經貼出來了。
ClientCredentials實例解決方案下面有3個工程文件夾,分別是API對應咱們OAuth2裏面的受保護資源,Client對應咱們OAuth2裏面的客戶端應用,在這裏是控制檯程序,IdentityServer不用多說就是IDS4,認證受權服務。
 
請求流程
ClientCredentials受權類型的請求流程很簡單,本身看吧。下面咱們一塊兒看看實例代碼。
 
Client端代碼
 1 private static async Task Main()
 2         {
 3             // 其餘代碼...
 4             var client = new HttpClient();
 5             // 沒啥說的,OIDC協議規範定義的端點發現機制,固然你也能夠繞過這步,感興趣的能夠看看這個方法的實現
 6             var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
 7           
 8             // 經過clientid secret tokenendpoint等信息獲取accesstoken訪問令牌
 9             var tokenResponse = await client.RequestClientCredentialsTokenAsync(new  ClientCredentialsTokenRequest
10             {
11                 Address = disco.TokenEndpoint,
12                 ClientId = "client",
13                 ClientSecret = "secret",
14                 Scope = "api1"
15             });
16             Console.WriteLine(tokenResponse.Json);
17             // 經過accesstoken 調用 受保護的api
18             var apiClient = new HttpClient();
19             apiClient.SetBearerToken(tokenResponse.AccessToken);
20             var response = await apiClient.GetAsync("http://localhost:5001/identity");
21             if (!response.IsSuccessStatusCode)
22             {
23                 Console.WriteLine(response.StatusCode);
24             }
25             else
26             {
27                 var content = await response.Content.ReadAsStringAsync();
28                 Console.WriteLine(JArray.Parse(content));
29             }
30         }
client代碼很簡單,我也大概作了註釋和刪掉一些驗證的代碼,相信你們一看就明白了。接下來咱們繼續看IDS4這邊的代碼。
 
IdentityServer代碼
 1 // 注入服務
 2         public void ConfigureServices(IServiceCollection services)
 3         {
 4             // 注入IDS4的基礎服務
 5             var builder = services.AddIdentityServer()
 6                 // 注入resource資源到內存,該資源咱們能夠理解爲ids4本地資源,好比用戶信息等
 7                 .AddInMemoryIdentityResources(Config.GetIdentityResources())
 8                 // 注入api資源,也就是那些受保護的api
 9                 .AddInMemoryApiResources(Config.GetApis())
10                 // 注入client客戶端資源
11                 .AddInMemoryClients(Config.GetClients());
12             if (Environment.IsDevelopment())
13             {
14                 // 臨時開發環境下的證書
15                 builder.AddDeveloperSigningCredential();
16             }
17         }
18         // 注入中間件
19         public void Configure(IApplicationBuilder app)
20         {
21             // 註冊ids4中間件
22             app.UseIdentityServer();
23         }
以上代碼註釋的比較詳細,就不加以說明了,須要注意的一個地方就是這些資源都是從config這個類裏面加載出來的,咱們下面看看資源具體是什麼樣。題外話,上邊這兩方法來源於Starpup啓動類的兩個方法,關於Starpup的加載執行機制能夠看我上一篇文章。
 
Config代碼
 1 public static class Config
 2     {
 3         public static IEnumerable<IdentityResource> GetIdentityResources()
 4         {
 5             return new IdentityResource[]
 6             {
 7                 new IdentityResources.OpenId()
 8             };
 9         }
10         public static IEnumerable<ApiResource> GetApis()
11         {
12             return new List<ApiResource>
13             {
14                 // 注意api1這個名稱,須要跟api資源Audience的名稱同樣,包括下邊的scope裏面的名稱
15                 new ApiResource("api1", "My API")
16             };
17         }
18         public static IEnumerable<Client> GetClients()
19         {
20             return new List<Client>
21             {
22                 new Client
23                 {
24                     // 客戶端id
25                     ClientId = "client",
26                     // 受權類型
27                     AllowedGrantTypes = GrantTypes.ClientCredentials,
28                     // secret
29                     ClientSecrets =
30                     {
31                         new Secret("secret".Sha256())
32                     },
33                     // scope受權範圍
34                     AllowedScopes = { "api1" }
35                 }
36             };
37         }
38     }
因爲是示例代碼,因此資源都是以硬編碼的方式寫死在了config類裏邊,代碼簡單很少說,生產環境下建議用數據庫或者redis緩存,關於efcore的版本github上面有開源代碼,有興趣的朋友能夠自行查看。最後咱們一塊兒看下api這個工程裏邊的代碼。
 
API代碼
 1 public void ConfigureServices(IServiceCollection services)
 2         {
 3             // 注入mvc的核心服務
 4             services.AddMvcCore()
 5                 // 注入aspnetcore認證服務
 6                 .AddAuthorization()
 7                 .AddJsonFormatters();
 8             // 注入jwtbearer基礎服務
 9             services.AddAuthentication("Bearer")
10                 .AddJwtBearer("Bearer", options =>
11                 {
12                     // ids4地址,用於驗證accesstoken
13                     options.Authority = "http://localhost:5000";
14                     // 非必須使用https
15                     options.RequireHttpsMetadata = false;
16                     // api資源名稱
17                     options.Audience = "api1";
18                 });
19         }
20         public void Configure(IApplicationBuilder app)
21         {
22             // 注入aspnetcore認證中間件
23             app.UseAuthentication();
24             app.UseMvc();
25         }
26  
27     [Route("identity")]
28     [Authorize] // 須要認證應用攜帶正確的accesstoken才能訪問
29     public class IdentityController : ControllerBase
30     {
31         public IActionResult Get()
32         {
33             return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
34         }
35     }
api代碼的風格相似ids4認證服務,我註釋的也很詳細,關於aspnetcore的認證機制能夠查看我上一篇文章。以上代碼就是IDS4的 ClientCredentials的受權,下邊咱們重點看看IDS4的源代碼實現。
 
ClientCredentials受權IDS4源碼分析
在分析ids4源碼以前,咱們先看看OAuth協議針對ClientCredentials客戶端受權的一些參數&返回值規範。
Request:
POST /token HTTP/1.1
Host: authorization-server.com
grant_type=client_credentials // 必須
&client_id=xxxxxxxxxx // 必須
&client_secret=xxxxxxxxxx // 必須

 

Response:
 1 HTTP/1.1 200 OK
 2 Content-Type: application/json
 3 Cache-Control: no-store
 4 Pragma: no-cache
 5 {
 6   "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", // 必須
 7   "token_type":"bearer", // 必須
 8   "expires_in":3600, // 
 9   "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk",
10   "scope":"create"
11 }
Request&Response我是從OAuth官方文檔上面扒下來的,可能有朋友會好奇,我爲何要貼這一部分東西?我我的以爲讀ids4的源碼和aspnetcore或者其餘開源代碼有一些區別,ids4的源代碼代碼量並很少,也沒有不少邏輯,可是它是遵循OAuth2和OIDC這兩個協議規範實現的這麼一個框架,換句話說若是你徹底不瞭解這兩個協議規範的狀況下去閱讀ids4,你會被它跳暈。即使你能看懂裏面的邏輯,你也不知道它爲何要這麼幹。下面咱們經過Fiddler工具查看 ClientCredentials受權類型的請求和響應報文。
從Fiddler上的請求監控來看,ClientCredentials受權類型一共有6個請求,其中前面4個是client發起的,後面2個是api資源向ids4發起的請求。下面咱們具體看看這些請求涉及的報文。
 
openid-configuration&openid-configuration/jwks
configuration響應報文
從響應內容來看,這個請求只是獲取ids4的端點信息和對OAuth2&OIDC協議的支持信息。
 
jwks響應報文
這個請求其實就是從ids4獲取公鑰,用戶驗證jwt的數字簽名部分。
 
connect/token
請求報文
響應報文
以上兩段報文信息內容比較簡單,這裏我想簡單說下這個accesstoken訪問令牌,它是由ids4框架裏面的tokenendpoint端點負責生成,這個令牌就是後續請求受保護資源api的這麼一個通行證。它的生成機制,在後續的ids4源碼分析裏面我會詳細介紹。
 
/identity
請求頭
攜帶了上一個請求返回的accesstoken,而且token類型是Bearer。
 
響應內容
這個沒啥特別的,就是api資源返回的內容。須要注意的地方,在此以前,也就是當前請求響應以前,api資源向ids4受權服務器發起了2個請求,也就是前面圖裏邊顯示的最後兩個請求。這兩個請求其實際是JwtBearer認證的範疇,由於咱們在api工程的startup啓動類裏邊配置了它的認證類型是JwtBearer而且identitycontroller上面配置受權特性[Authorize],熟悉aspnetcore認證受權機制的朋友應該知道,在執行action以前會作認證受權處理,由於未認證直接返回challenge,最終處理由認證handler子類JwtBearerHandler接管,在JwtBearerHandler處理邏輯中,經過調用咱們api資源服務JwtBearer的options配置的Authority地址,獲取ids4公鑰(也就是後面的jwks請求)作accesstoken簽名校驗,後面兩個請求我就不貼圖片了,跟上面的兩個請求同樣。關於aspnetcore認證機制能夠看我上一篇帖子。接下來咱們來看一個稍微複雜一點的實例。
 
Hybrid Flow & AuthorizationCode Implicit
Hybrid Flow認證流程很是適合WEB服務器應用,OIDC官方文檔描述HybridFlow流程實際上是OAuth2 Code和Implicit受權類型的組合,也是最經常使用的一種認證流程吧。 這個實例解決方案包含3個工程,client爲aspnetcoremvc,api爲webapicore,idp爲ids4,該Demo實例支持自定義策略受權並且支持ReferenceToken刷新AccessToken,登出等操做,首先咱們看下Hybrid Flow流程大體請求原理,見下圖
這副圖引用的是園子裏邊solenovex大神的,請求邏輯在這副圖上已經描述的很清楚了,我就再也不介紹,可是這裏有個地方須要注意,response_type這個參數有3種組合,圖示裏面展現的是最多的一種狀況,code(受權碼)、id_token(用戶token)、token(就是accesstoken),細心的朋友發現accesstoken、idtoken返回了兩次,其實它們的值是不一樣的,最終訪問受保護的api資源用的是下一個accesstoken。下面咱們直接上代碼吧。
 
identityserver
 1 public void ConfigureServices(IServiceCollection services)
 2         {
 3              services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1);
 4             // 其餘代碼...
 5             // 注入ids4基礎服務
 6             var builder = services.AddIdentityServer(options =>
 7             {
 8                 options.Events.RaiseErrorEvents = true;
 9                 options.Events.RaiseInformationEvents = true;
10                 options.Events.RaiseFailureEvents = true;
11                 options.Events.RaiseSuccessEvents = true;
12             })
13             // 測試用戶
14            .AddTestUsers(TestUsers.Users);
15             // 這些個相似第一個實例
16             builder.AddInMemoryIdentityResources(Config.GetIdentityResources());
17             builder.AddInMemoryApiResources(Config.GetApis());
18             builder.AddInMemoryClients(Config.GetClients());
19             // 臨時開發證書,生成環境建議使用機構頒發的證書
20             if (Environment.IsDevelopment())
21             {
22                 builder.AddDeveloperSigningCredential();
23             }
24         }
25         public void Configure(IApplicationBuilder app)
26         {
27             if (Environment.IsDevelopment())
28             {
29                 app.UseDeveloperExceptionPage();
30             }
31             // 註冊ids4中間件
32             app.UseIdentityServer();
33             app.UseStaticFiles();
34             app.UseMvcWithDefaultRoute();
35         }
startup裏面的代碼跟上一個實例的代碼幾乎同樣,這個實例裏面咱們着重看看這些資源。
 
resources
 1 public static IEnumerable<IdentityResource> GetIdentityResources()
 2         {
 3             // 身份資源,相似上一個實例
 4             return new IdentityResource[]
 5             {
 6                 new IdentityResources.OpenId(),
 7                 new IdentityResources.Profile(),
 8                 new IdentityResources.Address(),
 9                 new IdentityResources.Phone(),
10                 new IdentityResources.Email(),
11                 // 策略資源,也是身份資源的一種吧
12                 new IdentityResource("names", "姓名", new List<string> { "FamilyName" }),
13             };
14         }
15         public static IEnumerable<ApiResource> GetApis()
16         {
17             return new ApiResource[]
18             {
19                 new ApiResource("api1", "testapi", new List<string> { "FamilyName" })
20                 {
21                     // 這個secrets主要是用於api在ids4上的驗證,由於須要referencetoken刷新accesstoken
22                     ApiSecrets = { new Secret("api1 secret".Sha256()) }
23                 }
24             };
25         }
26         public static IEnumerable<Client> GetClients()
27         {
28             return new[]
29             {
30                 new Client
31                 {
32                     ClientId = "hybrid client mvc",
33                     ClientSecrets = {new Secret("hybrid secret".Sha256()) },
34                     AllowedGrantTypes = GrantTypes.Hybrid, // 混合認證流程,OIDC協議
35                     AccessTokenType = AccessTokenType.Jwt, // accesstoken類型jwt,還有referencetoken類型
36                     AllowAccessTokensViaBrowser = true, 
37                     AccessTokenLifetime = 60, // 時間單位是秒,表示accesstoken60秒後過時
38                     RedirectUris = // 跳轉登陸地址
39                     {
40                         "http://localhost:5002/signin-oidc"
41                     },
42                     PostLogoutRedirectUris = // 跳轉登出地址
43                     {
44                         "http://localhost:5002/signout-callback-oidc"
45                     },
46                     AllowOfflineAccess = true,
47                     AlwaysIncludeUserClaimsInIdToken = true,
48                     AllowedScopes =
49                     {
50                         "api1",
51                         IdentityServerConstants.StandardScopes.OpenId,
52                         IdentityServerConstants.StandardScopes.Email,
53                         IdentityServerConstants.StandardScopes.Address,
54                         IdentityServerConstants.StandardScopes.Phone,
55                         IdentityServerConstants.StandardScopes.Profile,
56                         "names" // 策略scope
57                     }
58                 }
59             };
60         }
61  
62         // 測試用戶
63         public static List<TestUser> Users = new List<TestUser>
64         {
65             new TestUser{
66                 SubjectId = "1", Username = "test", Password = "test",
67                 Claims =
68                 {
69                     new Claim(JwtClaimTypes.Name, "test"),
70                     new Claim(JwtClaimTypes.FamilyName, "test"),
71                     new Claim(JwtClaimTypes.Email, "test@email.com"),
72                     new Claim(JwtClaimTypes.EmailVerified, "true",  ClaimValueTypes.Boolean),
73                     new Claim(JwtClaimTypes.Address, @"{ '街道': '萬家麗', '地址': '長沙','country': '大中國' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), 
74                 }
75             },
76             new TestUser{
77                 SubjectId = "2", Username = "demo", Password = "demo",
78                 Claims =
79                 {
80                     new Claim(JwtClaimTypes.Name, "demo"),
81                     new Claim(JwtClaimTypes.FamilyName, "demo"),
82                     new Claim(JwtClaimTypes.Email, "demo@email.com"),
83                     new Claim(JwtClaimTypes.EmailVerified, "true",  ClaimValueTypes.Boolean),
84                     new Claim(JwtClaimTypes.Address, @"{ '街道': '五一廣場', '地址': '長沙','country': '大中國' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json),
85                     new Claim("name", "FamilyName"),  // 受權策略claim
86                 }
87             }
88         };
89     }
代碼也很簡單,跟上一個實例的資源代碼有必定的區別,註釋的地方須要多看一下。
 
Client
 1 public void ConfigureServices(IServiceCollection services)
 2         {
 3             // 其餘代碼...
 4             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
 5             JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // 清空jwt用戶信息的映射關係。
 6             services.AddAuthentication(options =>
 7                 {
 8                     // 註冊cookiesScheme,表示本地認證採用cookie認證
 9                     options.DefaultScheme =  CookieAuthenticationDefaults.AuthenticationScheme;
10                     // 註冊oidcscheme,表示遠程認證採用oidc認證
11                     options.DefaultChallengeScheme =  OpenIdConnectDefaults.AuthenticationScheme;
12                 })
13                 // 注入cookie認證處理器,
14                 .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
15                     {
16                         options.AccessDeniedPath = "/Unauthorized/AccessDenied";  // 指定未受權的頁面路由地址
17                     })
18                     // 注入oidc認證處理器
19                 .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
20                 {
21                     // 認證成功登陸到本地cookie,主要是解決本地認證的問題。
22                     options.SignInScheme =  CookieAuthenticationDefaults.AuthenticationScheme;
23                     options.Authority = "http://localhost:5000"; // ids4 base地址
24                     options.RequireHttpsMetadata = false;
25                     options.ClientId = "hybrid client mvc";
26                     options.ClientSecret = "hybrid secret";
27                     options.SaveTokens = true; // 保存token到store
28                     options.ResponseType = "code id_token token"; // responsetype有三種組合,這裏使用的是組合最多的一種,能夠看前面一張圖
29                     options.Scope.Clear();
30                     options.Scope.Add("api1");
31                     options.Scope.Add(OidcConstants.StandardScopes.OpenId);
32                     options.Scope.Add(OidcConstants.StandardScopes.Profile);
33                     options.Scope.Add(OidcConstants.StandardScopes.Email);
34                     options.Scope.Add(OidcConstants.StandardScopes.Phone);
35                     options.Scope.Add(OidcConstants.StandardScopes.Address);
36                     options.Scope.Add("names"); // ids4那邊定義的策略名稱
37                     options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess);
38                 });
39             services.AddAuthorization(options =>
40             {
41                 options.AddPolicy("FamilyNamePolicy", builder => // 注入自定義策略
42                     {
43                         builder.AddRequirements(new NameRequirement());
44                     });
45             });
46             // 注入策略處理器
47             services.AddSingleton<IAuthorizationHandler, NameHandler>();
48         }
49         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
50         {
51             if (env.IsDevelopment())
52             {
53                 app.UseDeveloperExceptionPage();
54             }
55             else
56             {
57                 app.UseExceptionHandler("/Home/Error");
58             }
59             app.UseStaticFiles();
60             // 注入認證中間件
61             app.UseAuthentication();
62             app.UseCookiePolicy();
63             app.UseMvc(routes =>
64             {
65                 routes.MapRoute(
66                     name: "default",
67                     template: "{controller=Home}/{action=Index}/{id?}");
68             });
69         }
客戶端的startup裏邊的代碼我也作了詳細註釋,須要注意的是更前面一個實例的client代碼大相徑庭,比較繞的地方主要是對aspnetcore的認證受權系統的理解,因爲篇幅問題,AddOpenidConnect這個方法裏面的源碼我就不貼了,我大概講一下源碼的實現邏輯,AddOpenidConnect方法內部其實際是注入了OIDC認證的處理器,這個處理器是OpenIdConnectHandler,這個handler跟jwthandler或者cookiehandler在aspnetcore平臺裏面的結構是同樣樣的,只是它們核心方法的自身邏輯處理各有不一樣,好比cookiehandler的認證方法,其內部實現是處理本地cookie並設置context,在受權階段若是發現未認證返回challangeresult,最後由cookiehandler的challanger接管,跳轉未認證頁面,相似的oidc差異不大,它在認證階段發生跳轉,跳轉到咱們在options指定的地址,並完成認證,想要更多瞭解aspnetcore認證機制請參考我前面的文章,記得推介呵呵 。下面咱們看下受權策略怎麼應用。
 1 // homecontroller
 2 public class HomeController : Controller
 3     {
 4         // 其餘成員
 5  
 6         public IActionResult Index()
 7         {
 8             var user = User.Identity;
 9             return View();
10         }
11         // 沒什麼特別的,其實就是aspnetcore內置那套策略機制
12         [Authorize(Policy = "FamilyNamePolicy")]
13         public IActionResult Privacy()
14         {
15             return View();
16         }
17     }
18  
19 // 自定義策略規則
20 public class NameHandler : AuthorizationHandler<NameRequirement >
21     {
22         // 自定義策略處理器
23         protected override Task HandleRequirementAsync(AuthorizationHandlerContext  context,
24             NameRequirement  requirement)
25         {
26             var familyName = context.User.Claims.FirstOrDefault(c => c.Type ==  JwtClaimTypes.FamilyName)?.Value;
27             if (familyName == "demo" && context.User.Identity.IsAuthenticated) // 規則爲claim familyname爲demo&爲登陸用戶
28             {
29                 context.Succeed(requirement);
30                 return Task.CompletedTask;
31             }
32             context.Fail();
33             return Task.CompletedTask;
34         }
35     }
早期版本咱們在作受權的時候通常是基於角色,就靈活性而已,策略更具靈活性,目前是aspnetcore受權主流。說完策略受權以後,咱們簡單來看下accesstoken過時的刷新的問題。
 1 // 刷新accesstoken
 2 private async Task GetNewTokensAsync()
 3         {
 4             var client = new HttpClient();
 5             var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
 6             if (disco.IsError)
 7             { }
 8             var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
 9             // 獲取新的token,包含idtoken accesstoken referencetoken等信息
10             var tokenResponse = await client.RequestRefreshTokenAsync(new  RefreshTokenRequest
11             {
12                 Address = disco.TokenEndpoint,
13                 ClientId = "hybrid client mvc",
14                 ClientSecret = "hybrid secret",
15                 Scope = "api1 openid profile email phone address names",
16                 GrantType = OpenIdConnectGrantTypes.RefreshToken,
17                 RefreshToken = refreshToken
18             });
19             if (tokenResponse.IsError)
20             { }
21             // 刷新過時時間
22             var expiresAt = DateTime.UtcNow +  TimeSpan.FromSeconds(tokenResponse.ExpiresIn);
23             var tokens = new[]
24             {
25                 new AuthenticationToken
26                 {
27                     Name = OpenIdConnectParameterNames.IdToken,
28                     Value = tokenResponse.IdentityToken
29                 },
30                 new AuthenticationToken
31                 {
32                     Name = OpenIdConnectParameterNames.AccessToken,
33                     Value = tokenResponse.AccessToken
34                 },
35                 new AuthenticationToken
36                 {
37                     Name = OpenIdConnectParameterNames.RefreshToken,
38                     Value = tokenResponse.RefreshToken
39                 },
40                 new AuthenticationToken
41                 {
42                     Name = "expires_at",
43                     Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
44                 }
45             };
46             // 本地從新cookie認證
47             var currentAuthenticateResult =
48                 await  HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
49             // 本地存儲token
50             currentAuthenticateResult.Properties.StoreTokens(tokens);
51             // 從新登陸
52              await  HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
53                 currentAuthenticateResult.Principal,  currentAuthenticateResult.Properties);
54             return;
55         }
最後咱們來看下api資源的代碼。
 
API受保護的資源
 1 // startup啓動類
 2 public void ConfigureServices(IServiceCollection services)
 3         {
 4             services.AddMvc();
 5             services.AddMvcCore()
 6             .AddAuthorization()
 7             .AddJsonFormatters();
 8              services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
 9                 .AddIdentityServerAuthentication(options => // 以前這個地方是jwtbeare,我猜只是ids4的擴展方法對jwtbeare換了個馬甲,後續源碼分析會介紹
10                 {
11                     options.Authority = "http://localhost:5000"; // ids4 base基地址
12                     options.ApiName = "api1"; // 刷新token須要提供api名稱
13                     options.RequireHttpsMetadata = false;
14                     options.ApiSecret = "api1 secret"; // 須要刷新token ids4須要提供secret
15                     options.JwtValidationClockSkew = TimeSpan.FromMinutes(1); // 檢查時間,也就是api到ids4token驗證時間間隔
16                 });
17             services.AddMemoryCache();
18         }
19         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
20         {
21             // 註冊中間件 
22             app.UseAuthentication();
23             app.UseMvc();
24         }
 ClientCredentials &  HybridFlow認證流程的兩個實例到此就所有介紹完畢,Fiddler監控圖片我就比貼了,自行看下OAuth2和OIDC協議就行,請求和響應都是嚴格按照這兩協議來實現的,接下來作個簡單的總結吧。從ClientCredentials & HybridFlow這兩個實例代碼來看,其代碼自己並不複雜,只需一些簡單配置便可。IDS4遵循和實現了OAuth2 & OIDC大部協議,若是想要更好的理解IDS4源代碼,首先建議看下OAUTH2和OIDC的一些協議規範,其次就是自身平臺的認證受權系統,好比若是個人client或者api是netframework | netcore,那咱們就須要熟悉這個平臺自身的認證受權系統,其餘異構平臺均同樣。最後一個環節,ids4源碼分析。
 
IDS4源碼分析
我我的在看netcore平臺上面源碼的時候,首先就是找到starpup啓動類,而後找到對應的Middleware中間件,在IDS4這個框架裏邊有一個叫IdentityServerMiddleware的這麼一箇中間件,其實它就是咱們要找的這個中間件,ids4認證受權的入口。下面咱們看下它的定義。
 1 public class IdentityServerMiddleware
 2     {
 3         // 其餘成員...
 4         public IdentityServerMiddleware(RequestDelegate next,  ILogger<IdentityServerMiddleware> logger)
 5         {
 6             _next = next;
 7             _logger = logger;
 8         }
 9         
10         public async Task Invoke(HttpContext context, IEndpointRouter router, IUserSession  session, IEventService events)
11         {
12             // 從本地cookie讀取認證信息,若是以認證直接設置請求上下文
13             await session.EnsureSessionIdCookieAsync();
14             try
15             {
16                 // 這個地方咱們把它理解爲請求路由查找對應的endpoint端點,有點相似攔截,由於若是匹配到請求地址,endpoint端點處理完以後直接response
17                 var endpoint = router.Find(context);
18                 if (endpoint != null)
19                 {
20                     // 若是匹配到由端點處理,好比咱們上面介紹的tokenendpoint在處理完請求以後直接經過executeasync把accesstoken等信息直接寫入response
21                     var result = await endpoint.ProcessAsync(context);
22                     if (result != null)
23                     {
24                         // 寫入response
25                         await result.ExecuteAsync(context);
26                     }
27  
28                     return;
29                 }
30             }
31             catch (Exception ex)
32             {
33                 await events.RaiseAsync(new UnhandledExceptionEvent(ex));
34                 _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message);
35                 throw;
36             }
37  
38             // 若是未被路由端點匹配,傳遞到下一個中間件。
39             await _next(context);
40         }
41     }
IdentityServerMiddleware中間件的定義來看,特別的地方就是這些端點以及它們的處理,咱們的OAuth2 & OIDC 的大部分協議就實如今這裏邊,接下來咱們看看具體有哪些端點?在startup的configureservices方法裏邊,注入了這麼一個方法叫addidentityserver,在這個擴展方法裏面注入了端點,看代碼。
 1 // 注入平臺默認的端點
 2 public static IIdentityServerBuilder AddDefaultEndpoints(this IIdentityServerBuilder  builder)
 3         {
 4             builder.Services.AddTransient<IEndpointRouter, EndpointRouter>();
 5             builder.AddEndpoint<AuthorizeCallbackEndpoint>(EndpointNames.Authorize,  ProtocolRoutePaths.AuthorizeCallback.EnsureLeadingSlash());
 6             builder.AddEndpoint<AuthorizeEndpoint>(EndpointNames.Authorize,  ProtocolRoutePaths.Authorize.EnsureLeadingSlash());
 7             builder.AddEndpoint<CheckSessionEndpoint>(EndpointNames.CheckSession,  ProtocolRoutePaths.CheckSession.EnsureLeadingSlash());
 8              builder.AddEndpoint<DeviceAuthorizationEndpoint>(EndpointNames.DeviceAuthorization,  ProtocolRoutePaths.DeviceAuthorization.EnsureLeadingSlash());
 9             builder.AddEndpoint<DiscoveryKeyEndpoint>(EndpointNames.Discovery,  ProtocolRoutePaths.DiscoveryWebKeys.EnsureLeadingSlash());
10             builder.AddEndpoint<DiscoveryEndpoint>(EndpointNames.Discovery,  ProtocolRoutePaths.DiscoveryConfiguration.EnsureLeadingSlash());
11             builder.AddEndpoint<EndSessionCallbackEndpoint>(EndpointNames.EndSession,  ProtocolRoutePaths.EndSessionCallback.EnsureLeadingSlash());
12             builder.AddEndpoint<EndSessionEndpoint>(EndpointNames.EndSession,  ProtocolRoutePaths.EndSession.EnsureLeadingSlash());
13             builder.AddEndpoint<IntrospectionEndpoint>(EndpointNames.Introspection,  ProtocolRoutePaths.Introspection.EnsureLeadingSlash());
14             builder.AddEndpoint<TokenRevocationEndpoint>(EndpointNames.Revocation,  ProtocolRoutePaths.Revocation.EnsureLeadingSlash());
15             builder.AddEndpoint<TokenEndpoint>(EndpointNames.Token,  ProtocolRoutePaths.Token.EnsureLeadingSlash());
16             builder.AddEndpoint<UserInfoEndpoint>(EndpointNames.UserInfo,  ProtocolRoutePaths.UserInfo.EnsureLeadingSlash());
17             return builder;
18         }
OAuth2協議裏邊定義的端點,大部分都在這裏,tokenendpoint、authorizeendpoint等等還有oidc協議裏邊的userinfoendpoint等,接下來咱們就從裏面挑一兩個詳細介紹。
 
TokenEndPoint端點
TokenEndPoint端點就是咱們那些token的生成都是由它來完成的,老套路,先看定義。
 1 internal class TokenEndpoint : IEndpointHandler
 2     {
 3         private readonly IClientSecretValidator _clientValidator;
 4         private readonly ITokenRequestValidator _requestValidator;
 5         private readonly ITokenResponseGenerator _responseGenerator;
 6         private readonly IEventService _events;
 7         private readonly ILogger _logger;
 8         
 9         // 其餘成員...
10         
11         public TokenEndpoint(
12             IClientSecretValidator clientValidator,
13             ITokenRequestValidator requestValidator,
14             ITokenResponseGenerator responseGenerator,
15             IEventService events,
16             ILogger<TokenEndpoint> logger)
17         {
18             _clientValidator = clientValidator;
19             _requestValidator = requestValidator;
20             _responseGenerator = responseGenerator;
21             _events = events;
22             _logger = logger;
23         }
24       
25         // tokenendpoint端點處理請求入口方法
26         public async Task<IEndpointResult> ProcessAsync(HttpContext context)
27         {
28            
29             // 首先驗證http操做,非post 表單提交直接返回錯誤
30             if (!HttpMethods.IsPost(context.Request.Method) ||  !context.Request.HasFormContentType)
31             {
32                 _logger.LogWarning("Invalid HTTP request for token endpoint");
33                 return Error(OidcConstants.TokenErrors.InvalidRequest);
34             }
35             // 進一步驗證
36             return await ProcessTokenRequestAsync(context);
37         }
38  
39         private async Task<IEndpointResult> ProcessTokenRequestAsync(HttpContext context)
40         {
41             // 首先驗證client 就是oauth2協議裏邊定義的clientid secret等
42             var clientResult = await _clientValidator.ValidateAsync(context);
43             if (clientResult.Client == null) // 若是驗證未經過直接返回invalidclient
44             {
45                 return Error(OidcConstants.TokenErrors.InvalidClient);
46             }
47             var form = (await context.Request.ReadFormAsync()).AsNameValueCollection();
48             _logger.LogTrace("Calling into token request validator: {type}",  _requestValidator.GetType().FullName);
49             // 而後驗證請求參數,這些參數能夠查看OAuth2,根據不一樣的GrantType啓動不一樣的驗證規則,而且生成AuthorizationCode
50             var requestResult = await _requestValidator.ValidateRequestAsync(form,  clientResult);
51             if (requestResult.IsError)
52             {
53                 await _events.RaiseAsync(new TokenIssuedFailureEvent(requestResult));
54                 return Error(requestResult.Error, requestResult.ErrorDescription,  requestResult.CustomResponse);
55             }
56             // create response
57             _logger.LogTrace("Calling into token request response generator: {type}",  _responseGenerator.GetType().FullName);
58             // 生成accesstoken,若是有須要還會生成refencetoken id token等
59             var response = await _responseGenerator.ProcessAsync(requestResult);
60             await _events.RaiseAsync(new TokenIssuedSuccessEvent(response,  requestResult));
61             LogTokens(response, requestResult);
62             // return result
63             _logger.LogDebug("Token request success.");
64             // 返回
65             return new TokenResult(response);
66         }
67     }
接下來咱們看看client的驗證規則,client的驗證規則由 IClientSecretValidator接口實現,下面咱們看下它的定義。
1 public interface IClientSecretValidator
2     {
3         // 驗證
4         Task<ClientSecretValidationResult> ValidateAsync(HttpContext context);
5     }
就幹一件事驗證。它的具體實現由它子類ClientSecretValidator完成。咱們繼續看。
 1 public class ClientSecretValidator : IClientSecretValidator
 2     {
 3        // 其餘成員...
 4         public ClientSecretValidator(IClientStore clients, SecretParser parser,  SecretValidator validator, IEventService events, ILogger<ClientSecretValidator> logger)
 5         {
 6             _clients = clients;
 7             _parser = parser;
 8             _validator = validator;
 9             _events = events;
10             _logger = logger;
11         }
12         // client驗證明現
13         public async Task<ClientSecretValidationResult> ValidateAsync(HttpContext context)
14         {
15             _logger.LogDebug("Start client validation");
16             var fail = new ClientSecretValidationResult
17             {
18                 IsError = true
19             };
20             // 默認預設4種parser,這裏爲PostBodySecretParser,驗證context form是否包含clien secret
21             var parsedSecret = await _parser.ParseAsync(context);
22             if (parsedSecret == null)
23             {
24                 await RaiseFailureEventAsync("unknown", "No client id found");
25                 _logger.LogError("No client identifier found");
26                 return fail;
27             }
28             // 從內存或者其餘存儲介質經過id獲取client,默認是內存裏面加載,若是未重寫資源存儲
29             var client = await _clients.FindEnabledClientByIdAsync(parsedSecret.Id);
30             if (client == null)
31             {
32                 await RaiseFailureEventAsync(parsedSecret.Id, "Unknown client");
33                 _logger.LogError("No client with id '{clientId}' found. aborting",  parsedSecret.Id);
34                 return fail;
35             }
36             SecretValidationResult secretValidationResult = null;
37             if (!client.RequireClientSecret || client.IsImplicitOnly())
38             {
39                 _logger.LogDebug("Public Client - skipping secret validation success");
40             }
41             else
42             {
43                 secretValidationResult = await _validator.ValidateAsync(parsedSecret,  client.ClientSecrets);
44                 if (secretValidationResult.Success == false)
45                 {
46                     await RaiseFailureEventAsync(client.ClientId, "Invalid client  secret");
47                     _logger.LogError("Client secret validation failed for client:  {clientId}.", client.ClientId);
48                     return fail;
49                 }
50             }
51             _logger.LogDebug("Client validation success");
52             // 返回clientresult這麼一個對象    
53             var success = new ClientSecretValidationResult
54             {
55                 IsError = false,
56                 Client = client,
57                 Secret = parsedSecret,
58                 Confirmation = secretValidationResult?.Confirmation
59             };
60             await RaiseSuccessEventAsync(client.ClientId, parsedSecret.Type);
61             return success;
62         }
63     }
認證完client以後,接着驗證request請求,request請求被定義在接口ITokenRequestValidator這個接口裏面,咱們看看它的定義。
  
1  public interface ITokenRequestValidator
2     {
3         
4         Task<TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection  parameters, ClientSecretValidationResult clientValidationResult);
5     }
這個接口辦事也是很是專注的,就這麼一個方法,接下來咱們看看它的實現類 TokenRequestValidator。
 1 internal class TokenRequestValidator : ITokenRequestValidator
 2     {
 3                 // 其餘代碼
 4         public async Task<TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult)
 5         {
 6             
 7             _validatedRequest = new ValidatedTokenRequest
 8             {
 9                 Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)),
10                 Options = _options
11             };
12  
13             if (clientValidationResult == null) throw new ArgumentNullException(nameof(clientValidationResult));
14  
15             _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation);
16             // 驗證protocoltype是發oidc
17             if (_validatedRequest.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect)
18             {
19                 LogError("Invalid protocol type for client",
20                     new
21                     {
22                         clientId = _validatedRequest.Client.ClientId,
23                         expectedProtocolType = IdentityServerConstants.ProtocolTypes.OpenIdConnect,
24                         actualProtocolType = _validatedRequest.Client.ProtocolType
25                     });
26  
27  
28                 return Invalid(OidcConstants.TokenErrors.InvalidClient);
29             }
30                         
31                         // grantType是否爲空
32             var grantType = parameters.Get(OidcConstants.TokenRequest.GrantType);
33             if (grantType.IsMissing())
34             {
35                 LogError("Grant type is missing");
36                 return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType);
37             }
38             // 驗證granttype字符串長度
39             if (grantType.Length > _options.InputLengthRestrictions.GrantType)
40             {
41                 LogError("Grant type is too long");
42                 return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType);
43             }
44  
45             _validatedRequest.GrantType = grantType;
46             // 根據不一樣的granttype,執行指定的驗證規則
47             switch (grantType)
48             {
49                 case OidcConstants.GrantTypes.AuthorizationCode:
50                     return await RunValidationAsync(ValidateAuthorizationCodeRequestAsync, parameters);
51                 case OidcConstants.GrantTypes.ClientCredentials:
52                     return await RunValidationAsync(ValidateClientCredentialsRequestAsync, parameters);
53                 case OidcConstants.GrantTypes.Password:
54                     return await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters);
55                 case OidcConstants.GrantTypes.RefreshToken:
56                     return await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters);
57                 case OidcConstants.GrantTypes.DeviceCode:
58                     return await RunValidationAsync(ValidateDeviceCodeRequestAsync, parameters);
59                 default:
60                     return await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters);
61             }
62         }
63  
64     }
TokenRequestValidator代碼比較多,我刪掉了大部分,都是驗證相關的。接下來咱們看看AuthorizationCode具體的驗證。
 1 private async Task<TokenRequestValidationResult> RunValidationAsync(Func<NameValueCollection, Task<TokenRequestValidationResult>> validationFunc, NameValueCollection parameters)
 2         {
 3             // 仍是驗證,包括AllowedGrantTypes 生成code等等,具體就不貼代碼了。
 4             var result = await validationFunc(parameters);
 5             if (result.IsError)
 6             {
 7                 return result;
 8             }
 9  
10             // run custom validation
11             _logger.LogTrace("Calling into custom request validator: {type}", _customRequestValidator.GetType().FullName);
12  
13             // 自定義驗證,驗證到天荒地老,還好默認沒有實現。
14             var customValidationContext = new CustomTokenRequestValidationContext { Result = result };
15             await _customRequestValidator.ValidateAsync(customValidationContext);
16  
17             if (customValidationContext.Result.IsError)
18             {
19                 if (customValidationContext.Result.Error.IsPresent())
20                 {
21                     LogError("Custom token request validator", new { error = customValidationContext.Result.Error });
22                 }
23                 else
24                 {
25                     LogError("Custom token request validator error");
26                 }
27  
28                 return customValidationContext.Result;
29             }
30  
31             LogSuccess();
32             return customValidationContext.Result;
33         }
終於驗證完了,接下來咱們看看驗證返回的TokenRequestValidationResult這個對象。
1 public class TokenRequestValidationResult : ValidationResult
2     {
3        // 其餘代碼...
4                 // 這個屬性就包含了前面瘋狂驗證的那些參數
5         public ValidatedTokenRequest ValidatedRequest { get; }
6  
7         public Dictionary<string, object> CustomResponse { get; set; }
8     }
瘋狂驗證完以後就返回這麼一個對象TokenRequestValidationResult,裏面囊括了全部request這些驗證過的參數還有個response字典。驗證完以後,包括一些初始化完成以後,接下來就是建立response了。response的建立是由具備這麼一個定義的接口ITokenResponseGenerator實現。
  
1  public interface ITokenResponseGenerator
2     {
3         Task<TokenResponse> ProcessAsync(TokenRequestValidationResult validationResult);
4     }
比較低調,就一個方法。咱們來看下它的實現類TokenResponseGenerator
 1 public class TokenResponseGenerator : ITokenResponseGenerator
 2     {
 3         // 其餘代碼...
 4         public virtual async Task<TokenResponse> ProcessAsync(TokenRequestValidationResult  request)
 5         {
 6             switch (request.ValidatedRequest.GrantType)
 7             {
 8                 case OidcConstants.GrantTypes.ClientCredentials:
 9                     return await ProcessClientCredentialsRequestAsync(request);
10                 case OidcConstants.GrantTypes.Password:
11                     return await ProcessPasswordRequestAsync(request);
12                 case OidcConstants.GrantTypes.AuthorizationCode:
13                     return await ProcessAuthorizationCodeRequestAsync(request);
14                 case OidcConstants.GrantTypes.RefreshToken:
15                     return await ProcessRefreshTokenRequestAsync(request);
16                 case OidcConstants.GrantTypes.DeviceCode:
17                     return await ProcessDeviceCodeRequestAsync(request);
18                 default:
19                     return await ProcessExtensionGrantRequestAsync(request);
20             }
21         }
22     }
這個類裏面代碼量也不小,其餘我都刪掉了,就留下這個入口方法ProcessAsync。接下來咱們看看ProcessAuthorizationCodeRequestAsync方法的實現。
 1 protected virtual async Task<TokenResponse>  ProcessAuthorizationCodeRequestAsync(TokenRequestValidationResult request)
 2         {
 3                         // 生成accesstoken機制,時間比較晚了,生成代碼我就不貼了,我大概說下邏輯吧,jwt爲列,header部分不用說,ploay部分是Claim身份聲明信息,簽名部分由rsa私鑰作的簽名,
 4             // refreshtoken 若是配置須要生成,則生成,配置要求我在實例裏面已經註釋了。
 5             (var accessToken, var refreshToken) = await  CreateAccessTokenAsync(request.ValidatedRequest);
 6             var response = new TokenResponse
 7             {
 8                 AccessToken = accessToken,
 9                 AccessTokenLifetime = request.ValidatedRequest.AccessTokenLifetime,
10                 Custom = request.CustomResponse,
11                 Scope =  request.ValidatedRequest.AuthorizationCode.RequestedScopes.ToSpaceSeparatedString(),
12             };
13                         // 若是須要生成refreshtoken就設置值
14             if (refreshToken.IsPresent())
15             {
16                 response.RefreshToken = refreshToken;
17             }
18                         // AuthorizationCode受權的生成idtoken,包括Hybridflow等
19             if (request.ValidatedRequest.AuthorizationCode.IsOpenId)
20             {
21                 Client client = null;
22                 if (request.ValidatedRequest.AuthorizationCode.ClientId != null)
23                 {
24                     client = await  Clients.FindEnabledClientByIdAsync(request.ValidatedRequest.AuthorizationCode.ClientId);
25                 }
26                 if (client == null)
27                 {
28                     throw new InvalidOperationException("Client does not exist anymore.");
29                 }
30                 var resources = await  Resources.FindEnabledResourcesByScopeAsync(request.ValidatedRequest.AuthorizationCode.RequestedScopes);
31                 var tokenRequest = new TokenCreationRequest
32                 {
33                     Subject = request.ValidatedRequest.AuthorizationCode.Subject,
34                     Resources = resources,
35                     Nonce = request.ValidatedRequest.AuthorizationCode.Nonce,
36                     AccessTokenToHash = response.AccessToken,
37                     ValidatedRequest = request.ValidatedRequest
38                 };
39                 var idToken = await TokenService.CreateIdentityTokenAsync(tokenRequest);
40                 var jwt = await TokenService.CreateSecurityTokenAsync(idToken);
41                 response.IdentityToken = jwt;
42             }
43             // 返回response
44             return response;
45         }
response生成完以後,經過TokenResult包裝並返回,接下來咱們看看TokenResult對象的定義。
 1 internal class TokenResult : IEndpointResult
 2     {
 3         public TokenResponse Response { get; set; }
 4         public TokenResult(TokenResponse response)
 5         {
 6             if (response == null) throw new ArgumentNullException(nameof(response));
 7             Response = response;
 8         }
 9                 // 把tokenresponse寫入context輸出流,json格式
10         public async Task ExecuteAsync(HttpContext context)
11         {
12             context.Response.SetNoCache();
13             var dto = new ResultDto
14             {
15                 id_token = Response.IdentityToken,
16                 access_token = Response.AccessToken,
17                 refresh_token = Response.RefreshToken,
18                 expires_in = Response.AccessTokenLifetime,
19                 token_type = OidcConstants.TokenResponse.BearerTokenType,
20                 scope = Response.Scope
21             };
22             if (Response.Custom.IsNullOrEmpty())
23             {
24                 await context.Response.WriteJsonAsync(dto);
25             }
26             else
27             {
28                 var jobject = ObjectSerializer.ToJObject(dto);
29                 jobject.AddDictionary(Response.Custom);
30                 await context.Response.WriteJsonAsync(jobject);
31             }
32         }
33                 // ids4的團隊也有點隨意啊。 
34         internal class ResultDto
35         {
36             public string id_token { get; set; }
37             public string access_token { get; set; }
38             public int expires_in { get; set; }
39             public string token_type { get; set; }
40             public string refresh_token { get; set; }
41             public string scope { get; set; }
42         }
43     }
到此,這篇隨筆算是寫完了。
最後來個大總結吧,仍是那句話,ids4自己代碼不是那麼的複雜,前提是其一要對OAuth2 & OIDC協議有所瞭解,雖然是英文版本的不要緊,我英語也至關差,不是有有道嗎?並且本身多少也認識幾個單詞啊。其二自身平臺的認證受權機制要比較熟悉,畢竟跟ids4有太多的交互,並且安全要求也比較高,原本打算還講下擴展資源存儲到sqlserver或者mysql,時間太晚了,明天還要上班不寫了,下個月見。謝謝各位看官支持, 有幫到你請點個推介,要求不高呵呵,謝謝。
相關文章
相關標籤/搜索