---
title: 帳號系統開發筆記
tags: [.Net 8,帳號驗證,MVC,blog]
---
{%hackmd @hackmd/blog-article-base-layout %}
<style>
.blog-date {
margin-top: 60px;
}
.blog-date.no-margin {
margin-top: 20px;
}
body .ui-toc, #ui-toc-affix {
display: none !important;
}
body > #doc.markdown-body {
padding-top: 20px;
}
#doc.markdown-body h2 {
margin-top: 5px;
}
</style>
:arrow_left: [回到部落格首頁](/@siugar/blog-tw) :arrow_right: <a target="_blank" href="/@siugar/AccountSystem">本文單篇連結</a>
# 帳號系統開發筆記
<div class="blog-date no-margin">2025/2/22</div>
<br/>
## 前言
因為新的專案全新開發,採用.Net 8 MVC框架,需要後台帳號系統,所以從無到有完成帳號管理與驗證系統
## 功能主要說明
建立帳號,套用不同角色,可以有不同的權限與帳號驗證登入
## 功能細節
1. 使用者管理
1.1 新增,修改,刪除
1.2 修改使用者密碼
2. 角色管理
2.1 新增,修改,刪除
2.2 權限
- 2.2.1 超級管理員
- 2.2.2 帳號管理
- 2.2.3 所有商店管理(編輯或觀看)
- 2.2.4 圖片管理(編輯或觀看)
- 2.2.5 跑馬燈管理(編輯或觀看)
- 2.2.6 報表管理
3. 登入
3.1 密碼驗證
3.2 失敗三次鎖定30分鐘
4. 個人資訊
4.1 顯示個人資訊
4.2 修改個人密碼
## 模組拆分
1. Account:
1.1 UserMgr 使用者管理模組
1.2 RoleMgr 角色管理模組
2. Auth
2.1 Auth 驗證模組
- 2.1.1 Login 登入
- 2.1.2 InitAcount 站台帳號初始化
2.2 PermissionHandler 權限檢查入口
2.3 PermissionService 權限處理Service
Controller,UseCase, Repository是拆分在不同的project,比方說UseCase隸屬於Domain project,但為了方便說明,就沒有再將Clean Architecture的分專案與檔案結構對應呈現出來。

## 資料規劃
### 靜態資料
```json
"Permission": {
"Policy": [
{
"Name": "SuperAdmin",
"Id": 10,
"DisplayName": "超級管理員"
},
{
"Name": "AccountManage",
"Id": 20,
"DisplayName": "帳戶管理"
},
{
"Name": "EditImageManage",
"Id": 30,
"GroupName": "圖片管理",
"DisplayName": "編輯圖片管理"
},
{
"Name": "ViewImageManage",
"Id": 35,
"GroupName": "圖片管理",
"DisplayName": "觀看圖片管理"
}
]
}
```
我是將Permission設定放在靜態資料內,比如appsetting.json,原因是因為這一塊如果需要修改多是因為要新增功能或是UI顯示面修改,當作靜態設定會比放在資料庫更方便修改與對應程式版本
### 動態資料 - Db 表格規劃

#### 1. 主要拆分成User與Role的表格,和使用者相關的資料擺放在User table, 和角色相關的放在Role table
#### 2. User與Role的關聯性用RolesIds來關連而非新建UserRole表格
2.1 增加關聯性表格實作管理複雜且不利肉眼查詢與除錯
因為多建立這個表格雖然有利於sql查詢,但真正在實作更新角色關聯性相關功能時相對比較麻煩,且增加這張表格不利於肉眼查詢或除錯,單純看一張表格就可以看出資料的關聯性,一開始我有設計但程式碼寫到一半後都改掉了。
2.2 採用JSON格式
格式就是在c#端從IEnumerable<int>轉成Json存放到資料庫,採用db Json 類型的好處是後續sql查詢時可以運用資料庫的內建Json相關函式,這樣可以增加sql查詢的彈性。
#### 賦予多個角色會有權限重疊的狀況
下圖這種權限重疊的狀況是會發生的,所以一定要考量到

## 部分畫面展示
### 1. 使用者管理

### 2. 建立使用者

### 3. 角色管理

### 4. 新增角色

## 技術細節介紹
### 1. Policy-based 授權[^PolocyBased]
#### 主要需求點:
1.1 要可以動態建立角色(Role)與修改角色(Role)包含的權限
1.2 後台要可以開啟多台
1.3 動態變更權限後,可以盡快生效而非要使用者重新登入
使用這種方法可以在每次觸發Action或Api時檢查權限,透過自訂的 PermissionHandler 來驗證權限,最優化的做法是將權限資料放在redis,而我因為預期實際同時使用者人數不會太多,每次可以先到資料庫查詢,未來真的有性能壓力出現時,需要優化再搬到redis都還是有機會的。
#### 前端範例 - 示範判斷EditImageManage是否存在來決定是否顯示"圖片新增"按紐
```html
@inject IAuthorizationService AuthorizationService
@{
var permissionEdit = await AuthorizationService.AuthorizeAsync(User, "EditImageManage");
var changeable = permissionEdit.Succeeded;
@if (changeable)
{
<form asp-action="GoAddYourImageView" asp-controller="YourImageMgr" method="get">
<button type="submit" class="btn btn-primary">圖片新增</button>
</form>
}
}
```
#### 後端範例 - 透過Policy控制整個Controller與特定的Action對應不同的Policy
以下面範例來說,擁有ViewImage權限的人可以訪問整個ImageMgrController但只有EditImage權限的人
```csharp
[Authorize(Policy = "ViewImage")]
public class ImageMgrController : Controller
{
//節略
public async Task<IActionResult> Index()
{
IEnumerable<Image>? resultList = await _queryImageRepository.GetImageList(500, 0);
return View(_mapper.ToViewModel(resultList));
}
[Authorize(Policy = "EditImage")]
public async Task<IActionResult> AddImage(AddImageReq req, IFormFile? file, [FromServices] AddImageUseCase useCase)
{
//節略
}
}
```
### 2. 資料庫User Table採用Argon2演算法先加鹽再雜湊加密
將使用者密碼運算Hash後存在資料庫是常見的做法,然後登入時,帳密驗證再次密碼Hash比對,這做法如果資料庫的User表格洩漏出去很容易所有的密碼驗證就會被破解,所以通常會再外加一段salt,強化被破解的難易度,而怎麼算出hash以及salt就有很多演算法。
目前我自己實作或是整合套建了三種演算法:SHA256,BCrypt與Argon2。一開始我自己寫了SHA256的做法,但後來發現這方法比較沒那麼安全所以找了插件BCrypt.Net-Next實裝了BCrypt,最後實際採用BouncyCastle.Cryptography的Argon2,他是2015年密碼雜湊競賽(PHC)冠軍,抗破解能力最強,所以我選擇了這一種。
參考資料[^Argon2參數推薦]我有提供推薦的Argon2的參數設定,有興趣的可以參考看看。
## 結論
雖然開發完成後現在看起來似乎理所當然分成UserMgr, RoleMgr與Auth。但當時因為第一次開發且因為專案需要拆分階段開發,先從登入流程開始寫,後面才慢慢補上UserMgr與RoleMgr,當初一開始寫時是建立了一個AccountMgr負責Login與初始化帳號創建,中間持續開發時發現會遇到User,Role表格將會共用於AccounMgr以及UserMgr,導致破壞單一功能原則(SRP),所以最後在功能開發告一段落後做了重構,將AccountRepository抽到UserRepository與RoleRepository,避免多個地方都去接觸資料庫,最終改成現在這個結構上比較清晰的版本。
## 參考資料
[^PolocyBased]: [Policy-based authorization in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-8.0)
[^Argon2參數推薦]: [Owasp的Argon2參數推薦](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id)