1. 程式人生 > >IdentityServer4-前後端分離的授權驗證(六)

IdentityServer4-前後端分離的授權驗證(六)

上兩節介紹完Hybrid模式在MVC下的使用,包括驗證從資料獲取的User和Claim對MVC的身份授權。本節將介紹Implicit模式在JavaScript應用程式中的使用,使用Node.js+Express構建JavaScript客戶端,實現前後端分離。本節授權服務和資源伺服器基於第四和第五節。


 

一、使用Node.js+Express搭建JavaScript客戶端

(1)首先需要Node.js環境

下載並安裝Node.js,官網下載地址:https://nodejs.org/en/ 

輸入指令:node –v  檢測是否已安裝Node.js,已安裝會顯示安裝的Node.js版本

(2)安裝Express

開啟cmd,輸入指令:npm install express-generator –g

輸入指令:express –h    已安裝express會顯示幫助文件

(3)新建檔案,建立JavaScript_Client應用程式

新建資料夾(在D盤新建Express資料夾),cmd進入該資料夾。

輸入:express JavaScript_Client     在當前目錄下建立一個名為JavaScript_Client的應用。目錄結構如下:

(4)安裝依賴包

輸入:cd JavaScript_Client

   進入JavaScript_Client目錄

輸入:npm install   安裝依賴包

(5)啟動並測試專案

輸入:npm start

瀏覽器開啟:http://localhost:3000 

看到以下頁面證明成功了。


 

二、新增JavaScript客戶端測試程式碼

(1)安裝oidc-client庫

輸入:npm install oidc-client –save

我們會發現在D:\express\JavaScript_Client\node_modules\oidc-client\dist  有兩個js檔案

我們只需使用這兩個檔案。把這兩個檔案複製到D:\express\JavaScript_Client\public\ javascripts 目錄下

(2)新增測試用的HTML檔案

使用VSCode開啟JavaScript_Client資料夾,在public(D:\express\JavaScript_Client\public)下新建index.html檔案。新增幾個測試用的按鈕。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <button id="login">Login</button>
    <button id="api">Call API</button>
    <button id="logout">Logout</button>

    <pre id="results"></pre>

    <script src=" javascripts/oidc-client.js"></script>
    <script src="app.js"></script>
</body>
</html> 

(3)新增測試的js檔案

在public下新建app.js檔案。

黏貼以下程式碼

/// <reference path=" javascripts/oidc-client.js" />

function log() {
    document.getElementById('results').innerText = '';

    Array.prototype.forEach.call(arguments, function (msg) {
        if (msg instanceof Error) {
            msg = "Error: " + msg.message;
        }
        else if (typeof msg !== 'string') {
            msg = JSON.stringify(msg, null, 2);
        }
        document.getElementById('results').innerHTML += msg + '\r\n';
    });
}

document.getElementById("login").addEventListener("click", login, false);
document.getElementById("api").addEventListener("click", api, false);
document.getElementById("logout").addEventListener("click", logout, false);

var config = {
    authority: "http://localhost:5000",
    client_id: "js",
    redirect_uri: "http://localhost:5003/callback.html",
    response_type: "id_token token",
    scope:"openid profile api1",
    post_logout_redirect_uri : "http://localhost:5003/index.html",
};
var mgr = new Oidc.UserManager(config);

mgr.getUser().then(function (user) {
    if (user) {
        log("User logged in", user.profile);
    }
    else {
        log("User not logged in");
    }
});

function login() {
    mgr.signinRedirect();
}

function api() {
    mgr.getUser().then(function (user) {
        var url = "http://localhost:5001/identity";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = function () {
            log(xhr.status, JSON.parse(xhr.responseText));
        }
        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
        xhr.send();
    });
}

function logout() {
    mgr.signoutRedirect();
}
View Code

以下對app.js程式碼進行分析

App.js中log函式用來記錄訊息

function log() {
    document.getElementById('results').innerText = '';

    Array.prototype.forEach.call(arguments, function (msg) {
        if (msg instanceof Error) {
            msg = "Error: " + msg.message;
        }
        else if (typeof msg !== 'string') {
            msg = JSON.stringify(msg, null, 2);
        }
        document.getElementById('results').innerHTML += msg + '\r\n';
    });
}

使用oidc-client庫中的UserManager類來管理OpenID連線協議。新增此程式碼以配置和例項化UserManager:

var config = {
    authority: "http://localhost:5000",
    client_id: "js",
    redirect_uri: "http://localhost:5003/callback.html",
    response_type: "id_token token",
    scope:"openid profile api1",
    post_logout_redirect_uri : "http://localhost:5003/index.html",
};
var mgr = new Oidc.UserManager(config);

接下來,UserManager提供一個getUser API來獲取使用者是否登入到JavaScript應用程式。返回的User物件有一個profile屬性,其中包含使用者的宣告。新增此程式碼以檢測使用者是否登入到JavaScript應用程式:

mgr.getUser().then(function (user) {
    if (user) {
        log("User logged in", user.profile);
    }
    else {
        log("User not logged in");
    }
});

接下來,我們要實現登入、api和登出功能。UserManager提供登入使用者的signinRedirect和使用者登出的signoutRedirect。我們在上述程式碼中獲得的使用者物件還有一個access_token屬性,可以使用該屬性對web API進行身份驗證。access_token將通過Bearer模式傳遞給Web API。新增以下程式碼在我們的應用程式中實現這三個功能:

function login() {
    mgr.signinRedirect();
}

function api() {
    mgr.getUser().then(function (user) {
        var url = "http://localhost:5001/identity";

        var xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onload = function () {
            log(xhr.status, JSON.parse(xhr.responseText));
        }
        xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
        xhr.send();
    });
}

function logout() {
    mgr.signoutRedirect();
}

(4)再新建一個callback.html。一旦使用者登入到IdentityServer,這個HTML檔案就是指定的redirect_uri頁面。它將完成OpenID Connect協議與IdentityServer的登入握手。這裡的程式碼都由我們前面使用的UserManager類提供。登入完成後,我們可以將使用者重定向回index.html頁面。新增此程式碼完成登入過程:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <script src=" javascripts/oidc-client.js"></script>
    <script>
        new Oidc.UserManager().signinRedirectCallback().then(function () {
            window.location = "index.html";
        }).catch(function (e) {
            console.error(e);
        });
    </script>
</body>
</html>

 

(8)修改服務埠為5003


 

三、修改授權服務配置,資源伺服器允許跨域呼叫API

(1)修改授權服務配置

在AuthServer專案,開啟Config.cs檔案,在GetClients中新增JavaScript客戶端配置

                // JavaScript Client
                new Client
                {
                    ClientId = "js",
                    ClientName = "JavaScript Client",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowAccessTokensViaBrowser = true,

                    RedirectUris = { "http://localhost:5003/callback.html" },
                    PostLogoutRedirectUris = { "http://localhost:5003/index.html" },
                    AllowedCorsOrigins = { "http://localhost:5003" },

                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api1"
                    },
                }

(2)在資源服務配置允許跨域呼叫api

在ResourceAPI專案,開啟Startup.cs檔案中的ConfigureServices方法,配置CORS,允許Ajax呼叫從http://localhost:5003呼叫http://localhost:5001的Web API。

 //JS-allow Ajax calls to be made from http://localhost:5003 to http://localhost:5001.
            services.AddCors(options =>
            {
                //this defines a CORS policy called "default"
                options.AddPolicy("default", policy =>
                {
                    policy.WithOrigins("http://localhost:5003")
                        .AllowAnyHeader()
                        .AllowAnyMethod();
                });
            });

在Configure方法中將CORS中介軟體新增到管道中

 //JS-Add the CORS middleware to the pipeline in Configure:

            app.UseCors("default");

(3)新增測試用的api介面

新增IdentityController控制器

[Route("[controller]")]
    public class IdentityController : ControllerBase
    {
        [Authorize(Roles ="admin")]
        [HttpGet]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }

(4)測試

執行AuthServer專案,執行ResourceAPI專案。

在VSCode終端輸入:npm start

開啟瀏覽器:http://localhost:5003/

點選Login,使用賬號:zhubingjian 密碼:123  登入

登入返回使用者的Claims資訊

點選Call API,呼叫資源伺服器的API介面

成功獲取介面返回的資訊。


通過這六節的內容,大概地介紹了IdentityServer4中Client的應用場景,包括MVC、前後端分離和服務端。

此外還介紹瞭如何動態配置Client、如何驗證從資料庫中獲取的User以及自定義Claims的方法。

這個系列對IdentityServer4的介紹也是我部落格的起點,寫部落格雖然很花時間,但是可以幫助我加深對知識點的理解。然而文中也體現到我對某些知識點的理解還是不到位的,望大家見諒。

參考官網地址:https://identityserver4.readthedocs.io/en/release/quickstarts/7_javascript_client.html

授權服務和資源服務原始碼地址:https://github.com/Bingjian-Zhu/Mvc-HybridFlow.git

JavaScript客戶端原始碼地址:https://github.com/Bingjian-Zhu/Identity-JavaScript_Client.git