Open ID Connect(OIDC)在 ASP.NET Core中的應用


我們在《ASP.NET Core項目實戰的課程》第一章里面給identity server4做了一個全面的介紹和示例的練習 ,這篇文章是根據大家對OIDC遇到的一些常見問題整理得出。

本文將涉及到以下幾個話題:

  • 什么是OpenId Connect (OIDC)

  • OIDC 對oAuth進行了哪些擴展?

  • Identity Server4提供的OIDC認證服務(服務端)

  • ASP.NET Core的權限體系中的OIDC認證框架(客戶端)

什么是 OIDC

在了解OIDC之前,我們先看一個很常見的場景。假使我們現在有一個網站要集成微信或者新浪微博的登錄,兩者現在依然采用的是oAuth 2.0的協議來實現 。 關於微信和新浪微博的登錄大家可以去看看它們的開發文檔。

在我們的網站集成微博或者新浪微博的過程大致是分為五步:

  1. 准備工作:在微信/新浪微博開發平台注冊一個應用,得到AppId和AppSecret

  2. 發起 oAauth2.0 中的 Authorization Code流程請求Code

  3. 根據Code再請求AccessToken(通常在我們應用的后端完成,用戶不可見)

  4. 根據 AccessToken 訪問微信/新浪微博的某一個API,來獲取用戶的信息

  5. 后置工作:根據用戶信息來判斷是否之前登錄過?如果沒有則創建一個用戶並將這個用戶作為當前用戶登錄(我們自己應用的登錄邏輯,比如生成jwt),如果有了則用之前的用戶登錄。

中間第2到3的步驟為標准的oAuth2 授權碼模式的流程,如果不理解的可以參考阮一峰所寫的《理解oAuth2.0 》一文。我們主要來看第4和5步,對於第三方應用要集成微博登錄這個場景來說最重要的是我希望能快速拿到用戶的一些基本信息(免去用戶再次輸入的麻煩)然后根據這些信息來生成一個我自己的用戶跟微博的用戶Id綁定(為的是下次你使用微博登錄的時候我還能把你再找出來)。

oAuth在這里麻煩的地方是我還需要再請求一次API去獲取用戶數據,注意這個API和登錄流程是不相干的,其實是屬於微博開放平台叢多API中的一個,包括微信開放平台也是這樣來實現。這里有個問題是前面的 2和3是oAuth2的標准化流程,而第4步卻不是,但是大家都這么干(它是一個大家都默許的標准)

於是大家干脆就建立了一套標准協議並進行了一些優化,它叫OIDC

OIDC 建立在oAuth2.0協議之上,允許客戶端(Clients)通過一個授權服務(Authorization Server)來完成對用戶認證的過程,並且可以得到用戶的一些基本信息包含在JWT中。

OIDC對oAuth進行了哪些擴展?

在oAuth2.0授權碼模式的幫助下,我們拿到了用戶信息。

以上沒有認證的過程,只是給我們的應用授權訪問一個API的權限,我們通過這個API去獲取當前用戶的信息,這些都是通過oAuth2的授權碼模式完成的。 我們來看看oAuth2 授權碼模式的流程:

第一步,我們向authorize endpoint請求code的時候所傳遞的response_type表示授權類型,原來只有固定值code

GET /connect/authorize?response_type=code&client_id=postman&state=xyz&scope=api1
        &redirect_uri=http://localhost:5001/oauth2/callback

第二步,上面的請求執行完成之后會返回301跳轉至我們傳過去的redirect_uri並帶上code

https://localhost:5001/oauth2/callback?code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c
&scope=api1
&state=111271607

  

第三步,用code換取access token

POST /connect/token?grant_type=authorization_code&code=835d584d4bc96d46ce49e27ebdbf272e40234d5f31097f63163f17da61fcd01c
&redirect_uri=http://localhost:5001/oauth2/callback
&client_id=postman
&client_secret=secret

  

通過這個POST我們就可以得到access_token

{
    "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjV",
    "expires_in": 3600,
    "token_type": "Bearer"
}

  

我們拿到access_token之后,再把access_token放到authorization頭請求 api來獲取用戶的信息。在這里,這個api不是屬於授權服務器提供的,而是屬於資源服務器。

OIDC給oAuth2進行擴展之后就填補了這個空白,讓我們可以授權它添加了以下兩個內容:

  • response_type 添加IdToken
  • 添加userinfo endpoint,用idToken可以獲取用戶信息

OIDC對它進行了擴展,現在你有三個選擇:code, id_token和 token,現在我們可以這樣組合來使用。

"response_type" value Flow
code Authorization Code Flow
id_token Implicit Flow
id_token token Implicit Flow
code id_token Hybrid Flow
code token Hybrid Flow
code id_token token Hybrid Flow

我們簡單的來理解一下這三種模式:

  • Authorization Code Flow授權碼模式:保留oAuth2下的授權模式不變response_type=code

  • Implicit Flow 隱式模式:在oAuth2下也有這個模式,主要用於客戶端直接可以向授權服務器獲取token,跳過中間獲取code用code換accesstoken的這一步。在OIDC下,responsetype=token idtoken,也就是可以同時返回access_token和id_token。

  • Hybrid Flow 混合模式: 比較有典型的地方是從authorize endpoint 獲取 code idtoken,這個時候id_token可以當成認證。而可以繼續用code獲取access_token去做授權,比隱式模式更安全。

    再來詳細看一下這三種模式的差異:

Property Authorization Code Flow Implicit Flow Hybrid Flow
access token和id token都通過Authorization endpoint返回 no yes no
兩個token都通過token end point 返回 yes no no
用戶使用的端(瀏覽器或者手機)無法查看token yes no no
Client can be authenticated yes no yes
支持刷新token yes no yes
不需要后端參與 no yes no
       

我們來看一下通過Hybird如何獲取 code、id_token、_以及access_token,然后再用id_token向userinfo endpoint請求用戶信息。

第一步:獲取code,

  • response_type=code id_token
  • scope=api1 openid profile 其中openid即為用戶的唯一識別號
GET /connect/authorize?response_type=code id_token&client_id=postman&state=xyz&scope=api1 openid profile
&nonce=7362CAEA-9CA5-4B43-9BA3-34D7C303EBA7
        &redirect_uri=http://localhost:5001/oauth2/callback

當我們使用OIDC的時候,我們請求里面多了一個nonce的參數,與state有異曲同工之妙。我們給它一個guid值即可。

第二步:我們的redirect_uri在接收的時候即可以拿到code 和 id_token

https://localhost:5001/oauth2/callback#
code=c5eaaaca8d4538f69f670a900d7a4fa1d1300b26ec67fba2f84129f0ab4ffa35
&id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjVjMzA5ZGIwYTE2OGEwOTgGtpbj0GVXNnkKhGdrzA
&scope=openid%20profile%20api1&state=111271607

  

第三步:用code換access_token(這一步與oAuth2中的授權碼模式一致)

第四步:用access_token向userinfo endpoint獲取用戶資料

Get http://localhost:5000/connect/userinfo
Authorization Bearer access_token

  

返回的用戶信息

{
    "name": "scott",
    "family_name": "liu",
    "sub": "5BE86359-073C-434B-AD2D-A3932222DABE"
}

  

以下是我們的流程示意圖。

有人可能會注意到,在這里我們拿到的idtoken沒有派上用場,我們的用戶資料還是通過access_token從userinfo endpoint里拿的。這里有兩個區別:

  1. userinfo endpoint是屬於認證服務器實現的,並非資源服務器,有歸屬的區別
  2. id_token 是一個jwt,里面帶有用戶的唯一標識,我們在判斷該用戶已經存在的時候不需要再請求userinfo endpoint

下圖是對id_token進行解析得到的信息:sub即subject_id(用戶唯一標識 )

對jwt了解的同學知道它里面本身就可以存儲用戶的信息,那么id_token可以嗎?答案當然是可以的,我們將在介紹完identity server4的集成之后最后來實現。

Identity Server4提供的OIDC認證服務

Identity Server4是asp.net core2.0實現的一套oAuth2 和OIDC框架,用它我們可以很快速的搭建一套自己的認證和授權服務。我們來看一下用它如何快速實現OIDC認證服務。

由於用戶登錄代碼過多,完整代碼可以加入ASP.NET Core QQ群 92436737獲取。 此處僅展示配置核心代碼。

過程

  • 新建asp.net core web應用程序
  • 添加identityserver4 nuget引用
  • 依賴注入初始化
services.AddIdentityServer()
                .AddDeveloperSigningCredential()
                .AddInMemoryIdentityResources(Config.GetIdentityResources())
                .AddInMemoryApiResources(Config.GetApiResources())
                .AddInMemoryClients(Config.GetClients())
                .AddTestUsers(Config.GetTestUsers());
  • 中間件添加
app.UseIdentityServer();
  • 配置

在測試的時候我們新建一個Config.cs來放一些配置信息

api resources

public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1", "API Application"){
                    UserClaims = { "role", JwtClaimTypes.Role }
                }
            };
        }

identity resources

public static IEnumerable<ApiResource> GetApiResources()
        {
            return new List<ApiResource>
            {
                new ApiResource("api1", "API Application"){
                    UserClaims = { "role", JwtClaimTypes.Role }
                }
            };
        }

 

clients

我們要講的關鍵信息在這里,client有一個AllowGrantTypes它是一個string的集合。我們要寫進去的值就是我們在上一節講三種模式: Code,Implict和Hybird。因為這三種模式決定了我們的response_type可以請求哪幾個值,所以這個地方一定不能寫錯。

IdentityServer4.Models.GrantTypes這個枚舉給我們提供了一些選項,實際上是把oAuth的4種和OIDC的3種進行了組保。

public static IEnumerable<Client> GetClients()
        {
            return new List<Client>
            {
                new Client
                {
                    ClientId = "postman",

                    AllowedGrantTypes = GrantTypes.Hybird,
                    RedirectUris = { "https://localhost:5001/oauth2/callback" },

                    ClientSecrets =
                    {
                        new Secret("secret".Sha256())
                    },

                     AllowedScopes = new List<string>
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api1"
                    },

                    AllowOfflineAccess=true,

                },
            };
        }

 

users

public static List<TestUser> GetTestUsers()
        {
            return new List<TestUser> {
            new TestUser {
                SubjectId = "5BE86359-073C-434B-AD2D-A3932222DABE",
                Username = "scott",
                Password = "password",
                Claims = new List<Claim> {

                    new Claim(JwtClaimTypes.Name, "scott"),
                    new Claim(JwtClaimTypes.FamilyName, "liu"),
                    new Claim(JwtClaimTypes.Email, "scott@scottbrady91.com"),
                    new Claim(JwtClaimTypes.Role, "user"),
                }
            }
            };
        }

 

ASP.NET Core的權限體系中的OIDC認證框架

在Microsoft.AspNetCore.All nuget引用中包含了Microsoft.AspNetCore.Authentication.OpenIdConnect即asp.net core OIDC的客戶端。我們需要在依賴注入中添加以下配置:

services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
          .AddCookie("Cookies")
          .AddOpenIdConnect("oidc", options =>
          {
              options.SignInScheme = "Cookies";
              options.Authority = "http://localhost:5000";
              options.RequireHttpsMetadata = false;
              options.ClientId = "postman";
              options.ClientSecret = "secret";
              options.ResponseType = "code id_token";
              options.GetClaimsFromUserInfoEndpoint = true;
              options.Scope.Add("api1");
              options.Scope.Add("offline_access");
          });

 

Authority即我們的用identity server4搭建的認證授權服務器,而其中的GetClaimsFromUserInfoEndpoint則會在拿到id_token之后自動向userinfo endpoint請求用戶信息並放到asp.net core的User Identity下。

我們上面講過,可以不需要請求userinfo endpoint, 直接將用戶信息放到id_token中。

這樣我們就不需要再向userinfo endpoint發起請求,從id_token中即可以獲取到用戶的信息。而有了identity server4的幫助,完成這一步只需要一句簡單的配置即可:

new Client
{
    ClientId = "postman",

    AlwaysIncludeUserClaimsInIdToken = true,
    AllowOfflineAccess=true,
}

 這樣我們在拿到id_token之后,里即包含了我們的用戶信息。

 

 

資料:

曉晨master的identity server4中文文檔  http://www.cnblogs.com/stulzq/p/8119928.html
李念輝身份認證核心: https://www.cnblogs.com/linianhui/archive/2017/05/30/openid-connect-core.html
OIDC協議: http://openid.net/specs/openid-connect-discovery-1_0.html
Jesse騰飛的asp.net core項目實戰第一章identity server4准備 http://video.jessetalk.cn/course/5

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM