IdentityServer4學習及簡單使用

yuan發表於2019-06-11

本文,主要用來記錄IdentityServer4的簡單使用。

一. IdentityServer的預備知識

要學習IdentityServer,需要了解下基於Token的驗證體系,其中涉及到Token, OAuth&OpenID,JWT,協議規範等。

如圖過程,

 

二.  IdentityServer簡單介紹

IdentityServer4 是一個基於OpenID ConnectOAuth 2.0的針對ASP.NET Core 2.0的框架,以中介軟體的形式存在。

通常你可以構建(或重新使用)包含登入和登出頁面的應用程式,IdentityServer中介軟體會向其新增必要的協議頭,以便客戶端應用程式可以使用這些標準協議與其對話。

我們可以用IdentityServer來做什麼?

  1. 身份驗證服務:官方認證的OpenID Connect實現
  2. 單點登入/登出(SSO)
  3. 訪問受控的API : 為不同的客戶提供訪問API的令牌,比如:MVC網站、SPAMobile APP
  4. ...等等

三.簡單專案示例

先列出目錄結構,以及建立順序,來方便閱讀

IdentityServerDemo --> APIService1和APIService2 --> MVCClient

其中,處MVCClient是asp.net core web mvc專案外,其他都是asp.net core web api 專案

建立名為IdentityServerDemo的認證服務

1. 建立一個asp.net core web api專案:IdentityServerDemo

注意,不要設定HTTPS,否則後面使用postman測試時,會no response

2. 新增InMemoryConfiguration

public class InMemoryConfiguration
    {
        public static IConfiguration Configuration { get; set; }
        /// <summary>
        /// Define which APIs will use this IdentityServer
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        { 
            return new[]
            {
                new ApiResource("clientservice", "CAS Client Service"),
                new ApiResource("productservice", "CAS Product Service"),
                new ApiResource("agentservice", "CAS Agent Service")
            };
        }

        /// <summary>
        /// Define which Apps will use thie IdentityServer
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new[]
            {
                new Client
                {
                    ClientId = "client.api.service",
                    ClientSecrets = new [] { new Secret("clientsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice" }
                },
                new Client
                {
                    ClientId = "product.api.service",
                    ClientSecrets = new [] { new Secret("productsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice", "productservice" }
                },
                new Client
                {
                    ClientId = "agent.api.service",
                    ClientSecrets = new [] { new Secret("agentsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "agentservice", "clientservice", "productservice" }
                }
            };
        }

        /// <summary>
        /// Define which uses will use this IdentityServer
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TestUser> GetUsers()
        {
            return new[]
            {
                new TestUser
                {
                    SubjectId = "10001",
                    Username = "test1@hotmail.com",
                    Password = "test1password"
                },
                new TestUser
                {
                    SubjectId = "10002",
                    Username = "test2@hotmail.com",
                    Password = "test2password"
                },
                new TestUser
                {
                    SubjectId = "10003",
                    Username = "test3@hotmail.com",
                    Password = "test3password"
                }
            };
        }
    }
View Code

3. 使用nuget管理器,新增IdentityServer4 ,並且修改StartUp.cs

修改StartUp.cs中的Configure方法

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //啟用IdentityServer
            app.UseIdentityServer();
            app.UseMvc();
        }

修改StartUp.cs中的ConfigureServices方法

public void ConfigureServices(IServiceCollection services)
        {
            //新增IdentityServer
            services.AddIdentityServer()
                       .AddDeveloperSigningCredential()
                       .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
                       .AddInMemoryClients(InMemoryConfiguration.GetClients())
                       .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

這個主要是為了把IdentityServer註冊到容器中,需要對其進行配置,而這個配置主要包含三個資訊:

  1. 哪些api可以使用這個AuthorizationServer
  2. 哪些client可以使用這個AuthorizationServer
  3. 哪些User可以被這個AuthorizationServer識別並授權

這裡的AuthorizationServer 指的就是這個專案的服務:用來認證及授權使用的.

這裡是使用基於記憶體的方式。

對於Token簽名需要一對公鑰和私鑰,IdentityServer為開發者提供了一個AddDeveloperSigningCredential()方法,它會幫我們搞定這個事情並且儲存到硬碟。當切換到正式環境,需要使用真正的證照,更換為

public void ConfigureServices(IServiceCollection services)
    {
        InMemoryConfiguration.Configuration = this.Configuration;

        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());
    }
View Code

此專案,暫時不使用正式的證照了。

4.使用postman獲取token

啟動我們的IdentityServerDemo 專案,

然後使用postman傳送請求

5.引入QuickStartUI

IdentityServer為我們提供了一套UI以使我們能快速的開發具有基本功能的認證/授權介面,下載地址:QuickStartUI

QuickStartUI引入到我們的專案中,目錄結構如下:

5.修改StartUp.cs

修改Configure方法

新增靜態檔案中介軟體

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //啟用IdentityServer
            app.UseIdentityServer();
            //for QuickStart-UI 啟用靜態檔案
            app.UseStaticFiles();
            //app.UseMvc();
            app.UseMvcWithDefaultRoute(); //這裡帶有預設的路由
        }

6.執行程式

登入

點選here

登出

 

IdentityServer整合API Service

1.  新增asp.net core web api專案

注意,這裡也是使用http方式;

2.nuget中安裝IdentityServer4.AccessTokenValidation 

3.修改StartUp.cs檔案

修改configureServices方法

public void ConfigureServices(IServiceCollection services)
        {
            //IdentityServer
            services.AddMvcCore().AddAuthorization().AddJsonFormatters();
            services.AddAuthentication(Configuration["Identity:Scheme"])
                        .AddIdentityServerAuthentication(options =>
                        {
                            options.RequireHttpsMetadata = false; //是否需要https
                            options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";  //IdentityServer授權路徑
                            options.ApiName = Configuration["Service:Name"];  //需要授權的服務名稱
                        });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

修改Configure方法

UseMvc()之前啟用Authentication中介軟體

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //啟用Authentication中介軟體
            app.UseAuthentication();

            app.UseMvc();
        }

修改appsettings.json檔案

{
  "Service": {
    "Name": "clientservice", //本服務的名稱
    "Port": "53064",  //本服務的埠號,根據自己服務啟動時的埠號進行更改
    "DocName": "clientservice",
    "Version": "v1",
    "Title": "CAS Client Service API",
    "Description": "CAS Client Service API provide some API to help you get client information from CAS",
    "Contact": {
      "Name": "CAS 2.0 Team",
      "Email": "EdisonZhou@manulife.com"
    },
    "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml"
  },
  "Identity": { //去請求授權的Identity服務,這裡即IdentityServerDemo的服務啟動時的地址
    "IP": "localhost",
    "Port": "49363",  //IdentityServerDemo專案啟動時的埠號,根據實際情況修改
    "Scheme": "Bearer"
  }
}

 上面是APIService1的新增,對應的服務名稱是clientservice;

 APIService2與之類似,只是把appsettings.json中的clientservice改為productservice.

4. APIService1APIService2Controller新增[Authorize]特性

 [Authorize]
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        ......
    }

 

5. 測試

注意,這裡模擬的是clientservice服務(APIService1)去認證伺服器請求token的過程,所以請求到token,也應該在獲取clientservice相關授權的時候攜帶這個token.

 

 

如果在請求productservice的授權服務中,使用clientservicetoken則會顯示未授權

過程總結:

  1. 首先,在授權服務中,設定需要請求的ApiResource,client,user
  2. postman(相當於client)中,輸入client的相關資訊(client_id,client_serect)去請求token
  3. 然後就可以根據授權服務中相應client的AllowedScopes設定的範圍來請求服務了。

授權服務中的client設定

IdentityServer整合MVC Web Application

1. 新建一個ASP.NET Core MVC專案:MVCClient

 2.為指定方法新增[Authorize]特性

我們為HomeController下的Privacy方法上新增Authorize特性

     [Authorize]
        public IActionResult Privacy()
        {
            return View();
        }

這個時候,直接訪問Privacy,會報錯

而我們希望的效果是:當使用者第一次點選Privacy,頁面重定向到驗證服務(IdentityServerDemo),當使用者登入驗證授權後,再重定向到該網站。

此後一定時間範圍內的第二次,第三次點選,都不需要再重定向到驗證服務,而是直接讀取儲存的token.

3.  MVCClient專案新增OpenID Connect Authentication

而這部分主要集中於做Authentication(身份驗證)而非Authorization(授權)

public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

            //這部分主要是做身份驗證的(Authentication),而不是授權(Authorization)
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc"; //oidc => open id connect
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = "Cookies";
                options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
                options.RequireHttpsMetadata = false;
                options.ClientId = "cas.mvc.client.implicit";
                options.ResponseType = "id_token token";  //允許返回access token
                options.SaveTokens = true;
            });

        }

這裡我們使用的是implicit這個flow,它主要用於客戶端應用程式(主要指基於javascript的應用),它允許客戶端程式重定向到驗證服務(IdentityServerDemo),而後帶著token重定向回來。

另外,這裡的ResponseType為”id_token token”,表示既獲取id_token也獲取access_token. 而SaveTokens設定為true,表示會將從驗證服務返回的token持久化到cookie中,這樣就不用每次請求token了。

另在configure方法中,設定Authentication中介軟體:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseAuthentication();

            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

主要Authentication中介軟體,要再UseMvc之前。

4. 修改app.settings

{
  "Service": {
    "Name": "cas.mvc.client.implicit", //本服務的名稱
    "Port": "56458",  //服務埠號,根據實際情況調整
    "DocName": "cas.mvc.client.implicit",
    "Version": "v1",
    "Title": "CAS Client Service API",
    "Description": "CAS Client Service API provide some API to help you get client information from CAS",
    "Contact": {
      "Name": "CAS 2.0 Team",
      "Email": "EdisonZhou@manulife.com"
    },
    "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml"
  },
  "Identity": { //去請求授權的Identity服務
    "IP": "localhost",
    "Port": "49363"
  }
}

其中port根據自己此服務啟動後的埠號修改

5.在驗證服務(IdentityServerDemo)中新增MvcClient

修改 InMemoryConfiguration 中的GetClients方法:

public static IEnumerable<Client> GetClients()
        {
            return new[]
            {
                new Client
                {
                    ClientId = "client.api.service",
                    ClientSecrets = new [] { new Secret("clientsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice" }
                },
                new Client
                {
                    ClientId = "product.api.service",
                    ClientSecrets = new [] { new Secret("productsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice", "productservice" }
                },
                new Client
                {
                    ClientId = "agent.api.service",
                    ClientSecrets = new [] { new Secret("agentsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "agentservice", "clientservice", "productservice" }
                },
                new Client
                {
                    ClientId = "cas.mvc.client.implicit",
                    ClientName = "CAS MVC Web App Client",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    RedirectUris = { $"http://localhost:56458/signin-oidc" },
                    PostLogoutRedirectUris = { $"http://localhost:56458/signout-callback-oidc" },
                    AllowedScopes = new [] {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "agentservice", "clientservice", "productservice"
                    },
                    AllowAccessTokensViaBrowser = true // can return access_token to this client
                },
            };
        }

這裡ClientId要和MvcClient中設定的一樣。

RedirectUris是指登入成功以後需要重定向的地址(即重定向到MvcClient中的地址)

PostLogoutRedirectUris是指登出之後需要重定向的地址。

API Service Client的設定不同的就是AllowedScopes中給它增加了OpenIdProfile,因為我們為MvcClient設定的是oidc而不是bearer模式。

最後為了使用這些OpenID Connect Scopes,需要設定這些Identity Resources。

 

InMemoryConfiguration 中增加GetIdentityResources方法:

public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            };
        }

ConfigureServices方法中修改:

 public void ConfigureServices(IServiceCollection services)
        {
            //新增IdentityServer
            services.AddIdentityServer()
                       .AddDeveloperSigningCredential()
                       .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
                       .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
                       .AddInMemoryClients(InMemoryConfiguration.GetClients())
                       .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

6. MvcClient專案的Privacy 頁面中修改如下:

@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's privacy policy.</p>


@using Microsoft.AspNetCore.Authentication
<div>
    <strong>id_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("id_token")</span>
</div>
<div>
    <strong>access_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("access_token")</span>
</div>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

這裡,我們會把id_token和access_token顯示出來

7. 為了退出方便,暫時在HomeController下增加Logout方法

 public async Task Logout()
        {
            await HttpContext.SignOutAsync("Cookies");
            await HttpContext.SignOutAsync("oidc");
        } 

8. 簡單測試

啟動IdentityServerDemo這個驗證服務;

啟動MvcClient這個Mvc Web Application服務;

 

 

 

 

這裡沒有新增可點選的按鈕,可直接在url中修改路徑來登出

 

 

參考網址:

https://www.cnblogs.com/edisonchou/p/identityserver4_foundation_and_quickstart_01.html

 另外推薦edisonchou微服務系列,感覺非常棒

 

 

相關文章