前言

今日會展示在 Blazor 中,介紹如何儲存 Token 和存放的幾種位置以及他們的優缺點,還有帶著 Token 發送API

Token 怎麼儲存

拿到 Token 後,應該放哪裡呢 ? 在前端可以存放資料的地方不多,主要有三個地方 記憶體、Cookie、localStorage

記憶體

在拿到伺服器回傳的 Token 後就是在記憶體內,Token 是一種憑證理論上他應該受到保護,用完即丟不要存在任何的地方,這樣安全性是最高的,可是實際上如果一個網頁,不斷地要求使用者進行登入,應該很快就會被罵翻了,所以儲存在其他的方的需求產生了,只要 Token 的效期還沒有到就可以對後端資源做存取

  1. 可以設定效期
  2. 使用 HttpOnly 和 Secure Cookie,可以防止 Javascript 存取
  3. 如果 Token 太大可能會沒辦法放進 Cookie 內
  4. 需注意 CSRF 攻擊

localStorage

  1. 使用上方便
  2. 較容易被 XSS 攻擊

完善登入流程

1. 登入頁面處理

看到昨天建立的 Login.razor,注入 JS Runtime

1
@inject IJSRuntime Js

登入後取得 Token ,再將 Token 儲存到 localStorage 中

1
2
3
4
5
6
 private async Task HandleValidSubmit()
    {
        var token = await(await Http.PostAsJsonAsync("api/Authorize/Login", loginDto)).Content.ReadAsStringAsync();
        // 將 Token 寫入 localStorage
        await Js.InvokeVoidAsync("localStorage.setItem", "token", $"{token}").ConfigureAwait(false);
    }

2. 將登入按鈕放到右上角

MainLayout.razor內,將 About 取代成登入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@inherits LayoutComponentBase

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <a href="login">登入</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

3. 為 Server 專案的 API 加入 Authorize

我們將範例的 WeatherForecast 內的 Get 修改成需要身份驗證

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[HttpGet]
[Authorize]
public IEnumerable<WeatherForecast> Get()
{
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

4. 處理 Client 專案 FetchData.razor

我們在 WeatherForecast 內的 API 加上 Authorize 直接呼叫就會是 401 未授權,所以需要再發出 API Request時帶上 Token

在最上面加入需要用到的服務

1
2
@inject IJSRuntime Js
@inject NavigationManager NavigationManager

寫一個方法讀取 Token

1
2
3
4
5
6
 private string Token = string.Empty;

    private async Task GetToken()
    {
        Token = await Js.InvokeAsync<string>("localStorage.getItem", "user").ConfigureAwait(false);
    }

修改原本的取資料的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 protected override async Task OnInitializedAsync()
    {
        await GetToken();
        if (string.IsNullOrWhiteSpace(Token))
        {
            NavigationManager.NavigateTo($"/login", false);
            return;
        }
        var requestMsg = new HttpRequestMessage(HttpMethod.Get, $"/api/weatherforecast/{DateTime.Now.ToString("yyyy-MM-dd")}");
        requestMsg.Headers.Add("Authorization", $"Bearer {Token}");
        var response = await Http.SendAsync(requestMsg);

        if (response.IsSuccessStatusCode)
        {
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast");
        }
    }

若沒有 Token 會將使用者導向登入頁面

小結

在這一個章節我們完成了基本的權限驗證,如果沒有登入的狀態下點選,FatchData 頁面會直接導向登入頁,待拿到 Token 後才可以進入頁面並且撈取資料,有人可能會有疑惑為什麼 Token 沒有在 Client 專案中驗證呢? 這是一個非常重要的知識點

在 Blazor WebAssembly 應用程式中,授權檢查是可以被略過的,因為使用者可以修改所有的用戶端程式碼。 這同樣也適用於所有的用戶端應用程式技術,包括 JavaScript SPA 架構或任何作業系統的原生應用程式。

前端是不安全的,不可以相信前端,在 Blaozr WebAssembly 你可以做頁面的導向,沒有登入時跳轉到登入頁,可以設定哪些權限可以看到哪些頁面,但是這些在前端都是可以被修改的,真正的驗證應該在後端,就算前端使用者用了一些方法抵達權限不足的頁面,他在提取資料時也後端也不可以放行

下個章節預計會介紹頁面導向與路由