1. 程式人生 > >使用Identity Server 4建立Authorization Server (4)

使用Identity Server 4建立Authorization Server (4)

上一篇講了使用OpenId Connect進行Authentication.

下面講

Hybrid Flow和Offline Access

目前我們解決方案裡面有三個專案 Authorization Server, Web api和Mvc Client. 在現實世界中, 他們可能都在不同的地方.

現在讓我們從MvcClient使用從Authorization Server獲取的token來訪問web api. 並且確保這個token不過期.

現在我們的mvcClient使用的是implicit flow, 也就是說, token 被髮送到client. 這種情況下 token的生命可能很短, 但是我們可以重定向到authorization server 重新獲取新的token.

例如, 在SPA(Single Page Application)中, implicit flow基本上就是除了resource owner password flow 以外唯一合適的flow, 但是我們的網站可能會在client(SPA client/或者指使用者)沒使用網站的時候訪問api, 為了這樣做, 不但要保證token不過期, 我們還需要使用別的flow. 我們要介紹一下authorization code flow. 它和implicit flow 很像, 不同的是, 在重定向回到網站的時候獲取的不是access token, 而是從authorization server獲取了一個code, 使用它網站可以交換一個secret, 使用這個secret可以獲取access token和refresh tokens.

Hybrid Flow, 是兩種的混合, 首先identity token通過瀏覽器傳過來了, 然後客戶端可以在進行任何工作之前對其驗證, 如果驗證成功, 客戶端就會再開啟一個通道向Authorization Server請求獲取access token.

首先在Authorization server的InMemoryConfiguration新增一個Client:

new Client
                {
                    ClientId = "mvc_code",
                    ClientName = "
MVC Code Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "socialnetwork" }, AllowOfflineAccess = true, AllowAccessTokensViaBrowser = true }

首先肯定要修改一下ClientId.

GrantType要改成Hybrid或者HybrdAndClientCredentials, 如果只使用Code Flow的話不行, 因為我們的網站使用Authorization Server來進行Authentication, 我們想獲取Access token以便被授權來訪問api. 所以這裡用HybridFlow.

還需要新增一個新的Email scope, 因為我想改變api來允許我基於email來建立使用者的資料, 因為authorization server 和 web api是分開的, 所以使用者的資料庫也是分開的. Api使用使用者名稱(email)來查詢資料庫中的資料.

AllowOfflineAccess. 我們還需要獲取Refresh Token, 這就要求我們的網站必須可以"離線"工作, 這裡離線是指使用者和網站之間斷開了, 並不是指網站離線了.

這就是說網站可以使用token來和api進行互動, 而不需要使用者登陸到網站上. 

修改MvcClient的Startup的ConfigureServices:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            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 = "mvc_code";
                options.ClientSecret = "secret";
                options.ResponseType = "id_token code";
                options.Scope.Add("socialnetwork");
                options.Scope.Add("offline_access");
                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
            });
        }

首先改ClientId和Authorization server一致. 這樣使用者訪問的時候和implicit差不多, 只不過重定向回來的時候, 獲取了一個code, 使用這個code可以換取secret然後獲取access token.

所以需要在網站(MvcClient)上指定Client Secret. 這個不要洩露出去.

還需要改變reponse type, 不需要再獲取access token了, 而是code, 這意味著使用的是Authorization Code flow.

還需要指定請求訪問的scopes: 包括 socialnetwork api和離線訪問

最後還可以告訴它從UserInfo節點獲取使用者的Claims.

執行

點選About, 重定向到Authorization Server:

同時在Authorization Server的控制檯可以看見如下資訊:

這裡可以看到請求訪問的scope, response_type. 還告訴我們respose mode是from_post, 這就是說, 在這登陸後重定向回到網站是使用的form post方式.

然後登陸:

這裡可以看到請求訪問的範圍, 包括個人資訊和Application Access.

點選Yes, Allow:

重定向回到了網站. 這裡看起來好像和以前一樣. 但是如果看一下Authorization Server的控制檯:

就會看到一個request. 中介軟體發起了一個請求使用Authorization Code和ClientId和secret來換取了Access token.

當Authorization驗證上述資訊後, 它就會建立一個token.

列印Refresh Token

修改MvcClient的About.cshtml:

@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>
<div>
    <strong>refresh_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("refresh_token")</span>
</div>
<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

重新整理頁面:

看到了refresh token.

這些token包含了什麼時候過期的資訊.

如果access token過期了, 就無法訪問api了. 所以需要確保access token不過期. 這就需要使用refresh token了.

複製一下refresh token, 然後使用postman:

使用這個refresh token可以獲取到新的access token和refresh_token, 當這個access_token過期的時候, 可以使用refresh_token再獲取一個access_token和refresh_token......

而如果使用同一個refresh token兩次, 就會得到下面的結果:

看看Authorization Server的控制檯, 顯示是一個invalid refresh token:

所以說, refresh token是一次性的.

獲取自定義Claims

web api 要求request請求提供access token, 以證明請求的使用者是已經授權的. 現在我們準備從Access token裡面提取一些自定義的Claims, 例如Email.

看看Authorization Server的Client配置:

Client的AllowedScopes已經包括了Email. 但是還沒有配置Authorization Server允許這個Scope. 所以需要修改GetIdentityResources()(我自己的程式碼可能改名成IdentityResources()了):

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

然後需要為TestUser新增一個自定義的Claims;

public static IEnumerable<TestUser> Users()
        {
            return new[]
            {
                new TestUser
                {
                    SubjectId = "1",
                    Username = "[email protected]",
                    Password = "password",
                    Claims = new [] { new Claim("email", "[email protected]om") }
                }
            };
        }

然後需要對MvcClient進行設定, Startup的ConfigureServices:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            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 = "mvc_code";
                options.ClientSecret = "secret";
                options.ResponseType = "id_token code";
                options.Scope.Add("socialnetwork");
                options.Scope.Add("offline_access");
                options.Scope.Add("email");
                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
            });
        }

新增email scope. 所以MvcClient就會也請求這個scope.

執行:

這時在同意(consent)頁面就會出現email address一欄.

同意之後, 可以看到email已經獲取到了.

使用Access Token呼叫Web Api

首先在web api專案建立一個IdentityController:

namespace WebApi.Controllers
{
    [Route("api/[controller]")]
    public class IdentityController: Controller
    {
        [Authorize]
        [HttpGet]
        public IActionResult Get()
        {
            var username = User.Claims.First(x => x.Type == "email").Value;
            return Ok(username);
            //return new JsonResult(from c in User.Claims select new { c.Type, c.Value});
        }

    }
}

我們想要通過自定義的claim: email的值.

然後回到mvcClient的HomeController, 新增一個方法:

        [Authorize]
        public async Task<IActionResult> GetIdentity()
        {
            var token = await HttpContext.GetTokenAsync("access_token");
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
                var content = await client.GetStringAsync("http://localhost:5001/api/identity");
                // var json = JArray.Parse(content).ToString();
                return Ok(new { value = content });
            }
        }

這裡首先通過HttpContext獲得access token, 然後在請求的Authorization Header加上Bearer Token.

讓我們執行一下, 並在MvcClient和Web Api裡面都設好斷點,

登入後在瀏覽器輸入 http://localhost:5002/Home/GetIdentity 以執行GetIdenttiy方法, 然後進入Web Api看看斷點除錯情況:

由於我們已經授權了, 所以可以看到User的一些claims, 而其中沒有email這個claim. 再執行就報錯了.

這是怎麼回事? 我們回到About頁面, 複製一下access_token, 去jwt.io分析一下:

確實沒有email的值, 所以提取不出來.

所以我們需要把email新增到access token的資料裡面, 這就需要告訴Authorization Server的Api Resource裡面要包括User的Scope, 因為這是Identity Scope, 我們想要把它新增到access token裡:

修改Authorization Server的InMemoryConfiguration的ApiResources():

public static IEnumerable<ApiResource> ApiResources()
        {
            return new[]
            {
                new ApiResource("socialnetwork", "社交網路")
                {
                    UserClaims = new [] { "email" }
                }
            };
        }

這對這個Api Resouce設定它的屬性UserClaims, 裡面寫上email.

然後再執行一下程式, 這裡需要重新登陸, 首先分析一下token:

有email了. 

然後執行GetIdentity(), 在web api斷點除錯, 可以看到UserClaims已經包含了email:

上面這些如果您不會的話, 需要整理總結一下.

使用者使用Authorization Server去登入網站(MvcClient), 也就是說使用者從網站跳轉到第三方的系統完成了身份的驗證, 然後被授權可以訪問web api了(這裡講的是使用者通過mvcClient訪問api). 當訪問web api的時候, 首先和authorization server溝通確認access token的正確性, 然後就可以成功的訪問api了.

重新整理Access Token

根據配置不同, token的有效期可能差別很大, 如果token過期了, 那麼傳送請求之後就會返回401 UnAuthorized.

當然如果token過期了, 你可以讓使用者重定向到Authorization Server重新登陸,再回來操作, 不過這樣太不友好, 太繁瑣了.

既然我們有refresh token了, 那不如向authorization server請求一個新的access token和refresh token. 然後再把這些更新到cookie裡面. 所以下次再呼叫api的時候使用的是新的token.

在MvcClient的HomeController新增RefreshTokens()方法:

首先需要安裝IdentityModel, 它是OpenIdConnect, OAuth2.0的客戶端庫:

        [Authorize]
        public async Task RefreshTokensAsync()
        {
            var authorizationServerInfo = await DiscoveryClient.GetAsync("http://localhost:5000/");
            var client = new TokenClient(authorizationServerInfo.TokenEndpoint, "mvc_code", "secret");
            var refreshToken = await HttpContext.GetTokenAsync("refresh_token");
            var response = await client.RequestRefreshTokenAsync(refreshToken);
            var identityToken = await HttpContext.GetTokenAsync("identity_token");
            var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);
            var tokens = new[]
            {
                new AuthenticationToken
                {
                    Name = OpenIdConnectParameterNames.IdToken,
                    Value = identityToken
                },
                new AuthenticationToken
                {
                    Name = OpenIdConnectParameterNames.AccessToken,
                    Value = response.AccessToken
                },
                new AuthenticationToken
                {
                    Name = OpenIdConnectParameterNames.RefreshToken,
                    Value = response.RefreshToken
                },
                new AuthenticationToken
                {
                    Name = "expires_at",
                    Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
                }
            };
            var authenticationInfo = await HttpContext.AuthenticateAsync("Cookies");
            authenticationInfo.Properties.StoreTokens(tokens);
            await HttpContext.SignInAsync("Cookies", authenticationInfo.Principal, authenticationInfo.Properties);
        }

首先使用一個叫做discovery client的東西來獲取Authorization Server的資訊. Authorization Server裡面有一個discovery節點(endpoint), 可以通過這個地址檢視: /.well-known/openid-configuration. 從這裡可以獲得很多資訊, 例如: authorization節點, token節點, 釋出者, key, scopes等等.

然後使用TokenClient, 引數有token節點, clientId和secret. 然後可以使用這個client和refreshtoken來請求新的access token等. 

找到refresh token後, 使用client獲取新的tokens, 返回結果是tokenresponse. 你可以設斷點檢視一下token reponse裡面都有什麼東西, 這裡就不弄了, 裡面包括identitytoken, accesstoken, refreshtoken等等.

然後需要找到原來的identity token, 因為它相當於是cookie中儲存的主鍵...

然後設定一下過期時間.

然後將老的identity token和新獲取到的其它tokens以及過期時間, 組成一個集合.

然後使用這些tokens來重新登陸使用者. 不過首先要獲取當前使用者的authentication資訊, 使用HttpContext.AuthenticateAsync("Cookies"), 引數是AuthenticationScheme. 然後修改屬性, 儲存新的tokens.

最後就是重登入, 把當前使用者資訊的Principal和Properties傳進去. 這就會更新客戶端的Cookies, 使用者也就保持登陸並且重新整理了tokens.

先簡單呼叫一下這個方法:

[Authorize]
        public async Task<IActionResult> GetIdentity()
        {
            await RefreshTokensAsync();
            var token = await HttpContext.GetTokenAsync("access_token");
            using (var client = new HttpClient())
            {
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
                var content = await client.GetStringAsync("http://localhost:5001/api/identity");
                //var json = JArray.Parse(content).ToString();
                return Ok(new { value = content });
            }
        }

正式生產環境中可不要這麼做, 正式環境中應該在401之後, 呼叫這個方法, 如果再失敗, 再返回錯誤.

執行一下:

發現獲取的access token是空的, 一定是哪出現了問題, 看一下 authorization server的控制檯:

說refresh token不正確(應該是記憶體資料和cookie資料不匹配). 那就重新登陸.

看斷點, 有token了:

並且和About頁面顯示的不一樣, 說明重新整理token了.

也可以看一下authorization server的控制檯:

說明成功請求了token.

今天先到這裡.

相關推薦

使用Identity Server 4建立Authorization Server (2)

可能 參數 ecif fig register startup 類型 cal mat 第一部分: http://www.cnblogs.com/cgzl/p/7780559.html 第一部分主要是建立了一個簡單的Identity Server. 接下來繼續: 建立Web

使用Identity Server 4建立Authorization Server (5)

連接字符串 mapr path 框架 ise network edit setting pin 預備知識: http://www.cnblogs.com/cgzl/p/7746496.html 第一部分: http://www.cnblogs.com/cgzl/p/7780

使用Identity Server 4建立Authorization Server (6) - js(angular5) 客戶端

include 節點 ogr 包含 發的 for icon ets list 預備知識: http://www.cnblogs.com/cgzl/p/7746496.html 第一部分: http://www.cnblogs.com/cgzl/p/7780559.html

使用Identity Server 4建立Authorization Server (3)

上一部分簡單的弄了個web api 並通過Client_Credentials和ResourceOwnerPassword兩種方式獲取token然後進行api請求. 這次講一下Authentication 身份驗證 (而Authorization是授權, 注意區分), 使用的是OpenIdCon

使用Identity Server 4建立Authorization Server (1)

官方文件很詳細的. 使用OAuth可以更安全, 這裡我們的authorization server和web api 以及網站將分別獨立執行.  建立authorization server 建立asp.net core 專案使用空模板. 專案建立後, 執行方式改為使用控制檯執行而不是IISEx

使用Identity Server 4建立Authorization Server (4)

上一篇講了使用OpenId Connect進行Authentication. 下面講 Hybrid Flow和Offline Access 目前我們解決方案裡面有三個專案 Authorization Server, Web api和Mvc Client. 在現實世界中, 他們可能都在不同

ASP.NET Core3.1使用Identity Server4建立Authorization Server-1

# 前言 網上關於Identity Server4的資料有挺多的,之前是一直看楊旭老師的,最近專案中有使用到,在使用.NET Core3.1的時候有一些不同。所以在此記錄一下。 > 預備知識: https://www.cnblogs.com/cgzl/p/9405796.html > > 本文內容參考 >

ASP.NET Core3.1使用Identity Server4建立Authorization Server-2

# 前言 # 建立Web Api專案 在同一個解決方案下建立一個Web Api專案`IdentityServer4.WebApi`,然後修改Web Api的launchSettings.json。參考第一節,當然可以不修改的,埠號為`5001`。 ``` csharp { "profiles":

【Kettle】4、SQL SERVER到SQL SERVER數據轉換抽取實例

serve 端口號 alt 指定 映射 pac 自動獲取 查詢語句 維護 1、系統版本信息   System:Windows旗艦版 Service Pack1   Kettle版本:6.1.0.1-196   JDK版本:1.8.0_72 2、連接數據庫   本次實例連接數

sql server 高可用故障轉移(4)

設置 inf 初始 08 r2 執行 進行 利用 iscs info 二臺sql服務器配置ISCSI虛擬磁盤 在上篇我們利用ISCSI Target軟件在DC-ISCSCI上創建了三個ISCSI虛擬磁盤,在下面我們將為大家介紹SQL-CL01(hsr1 50)和

CentOS7.4下 VNC Server的搭建和客戶端的連接配置

all centos connect linux water view con -- ref CentOS7.4下 VNC Server的搭建和客戶端的連接配置 服務器版本:CentOS Linux release 7.4.1708 (Core) yum方式安裝VN

CentOS7.4下 VNC Server的搭建和客戶端的連線配置

CentOS7.4下 VNC Server的搭建和客戶端的連線配置 伺服器版本:CentOS Linux release 7.4.1708 (Core) yum方式安裝VNC server yum install tigervnc-server 啟動

Web填坑之路(4) --- web server大概解釋

Web Server中文名稱叫網頁伺服器或web伺服器。web伺服器也稱為WWW伺服器,主要功能是提供網上資訊瀏覽服務。 Web伺服器可以解析HTTP協議。當Web伺服器接收到一個HTTP請求,會返回一個HTTP響應,例如返回一個HTML頁面。為了處理一個請求,Web伺服器可以響應一個靜態頁面或圖片,進行頁

Windows Server 2008 R2作業系統Apache2.4+Python3.6+Flask的配置

目前看Windows Server 2008 R2和Win10下配置這套系統沒有區別 我的安裝位置: Apache2.4 ---  D:\Apache24 python3.6---D:\Python36 1、配置環境變數:MOD_WSGI_APACHE_ROO

CAS單點登入(4):cas-4.0.0-server 配置mysql資料庫查詢認證

目錄 目錄 概述 環境要求 建立資料庫 Tips 配置cas-server 配置dataSource 配置passwordEnco

CAS單點登入(2):cas-4.0.0-server 去掉https驗證

目錄 目錄 去掉https驗證 1. 修改deployerConfigContext.xml新增p:requireSecure=”false” 2. 修改ticketGrantingTicketCookieGenerat

CAS單點登入(1):cas-4.0.0-server 簡單部署

下載CAS4.0.0 選擇4.0.0 的原因是:4.0.0以後打包比較麻煩,4.0.0版本內有打包好的war 下載地址 github專案地址 cas 4.0.0地址 解壓找到war包部署 解壓 cas-server-4.

kali E: 無法定位軟體包 php-cgi ,kali安裝DHCP提示;下列軟體包有未滿足的依賴關係: isc-dhcp-server : 依賴: isc-dhcp-common (= 4.2.

1.apt-get install php5-cgi 就行了 2. kali安裝DHCP提示;下列軟體包有未滿足的依賴關係: isc-dhcp-server : 依賴: isc-dhcp-commo

lwip1.4.0 http server實現及POST 實現

lwip1.4.0之http server實現及POST 實現 一、HTTP SERVER的實現  lwip預設的http server 在 apps/httpserver_raw 主要核心檔案為 fs.c fs.h(讀取相關html相關資源), httpd.c htt

centos7上建立vnc server

名稱 splay systemctl pla /dev/ 開啟 service pan bsp centos7跟6比設置方法不一樣 yum groupinstall "X Window System" -yyum grouplistyum groupinstall "GNO