MiniAuth 一個輕量 ASP.NET Core Identity Web 後臺管理中間外掛

阿翰發表於2024-07-21

MiniAuth 一個輕量 ASP.NET Core Identity Web 後臺管理中間外掛

「一行程式碼」為「新、舊專案」 新增 Identity 系統跟使用者、許可權管理網頁後臺系統

開箱即用,避免打掉重寫或是嚴重耦合情況

Github: https://github.com/mini-software/MiniAuth , Gitee: https://gitee.com/shps951023/MiniAuth

Image 1 Image 2
Image 3 Image 4

特點

  • 相容 : 支援 .NET identity Based on JWT, Cookie, Session 等
  • 簡單 : 拔插設計,API、SPA、MVC、Razor Page 等開箱即用
  • 支援多資料庫 : 支援 Oracle, SQL Server, MySQL 等 EF Core
  • 非侵入式 : 不影響現有資料庫、專案結構
  • 多平臺 : 支援 Linux, macOS 環境

安裝

NuGet 安裝套件

快速開始

在 Startup 新增一行程式碼 services.AddMiniAuth() 並執行專案,例子:

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        builder.Services.AddMiniAuth(); // <= ❗❗❗

        var app = builder.Build();
        app.Run();
    }
}

接著訪問管理網頁,Link 為 http(s)://yourhost/miniauth/index.html,預設 admin 管理賬號為 admin@mini-software.github.io 密碼為 E7c4f679-f379-42bf-b547-684d456bc37f (請記得修改密碼),即可管理你的 Identity 使用者、角色、端點。

在需要許可權管理的類別或方法上加上 [Authorize] 或是角色管控 [Authorize(Roles = "角色")],假設沒登入返回 401 狀態, 沒許可權返回 403 狀態。

MiniAuth 預設為單體 Coookie Based identity,如前後端分離專案請更換 JWT 等 Auth。

MiniAuth JWT Identity

只需要簡單指定 AuthenticationType 為 BearerJwt

builder.Services.AddMiniAuth(options:(options) =>
{
    options.AuthenticationType = MiniAuthOptions.AuthType.BearerJwt;
});

請記得自定義 JWT Security Key,如:

builder.Services.AddMiniAuth(options: (options) =>
{
    options.JWTKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("6ee3edbf-488e-4484-9c2c-e3ffa6dcbc09"));
});

獲取使用者 token 方式

前端 Javascript XHR 例子

var data = JSON.stringify({
  "username": "admin@mini-software.github.io",
  "password": "E7c4f679-f379-42bf-b547-684d456bc37f",
  "remember": false
});
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
  if(this.readyState === 4) {
    console.log(this.responseText);
  }
});
xhr.open("POST", "http://yourhost/miniauth/login");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(data);

返回結果

{
    "ok": true,
    "code": 200,
    "message": null,
    "data": {
        "tokenType": "Bearer",
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxZTIxOGY4My1iZjE3LTRhY2YtODhmOS1iOTQ3NjhjOWUwMGMiLCJuYW1lIjoiYWRtaW5AbWluaS1zb2Z0d2FyZS5naXRodWIuaW8iLCJyb2xlIjoibWluaWF1dGgtYWRtaW4iLCJzdWIiOiJhZG1pbkBtaW5pLXNvZnR3YXJlLmdpdGh1Yi5pbyIsIm5iZiI6MTcxODIwNDg5NSwiZXhwIjoxNzE4MjA1Nzk1LCJpYXQiOjE3MTgyMDQ4OTUsImlzcyI6Ik1pbmlBdXRoIn0._-DQ_rcbeju8_nrK2lD5we0rre04_xdDZNF6NhM0Rg0",
        "expiresIn": 900
    }
}

將 accessToken 儲存在 localstorage 或是 cookie 內,呼叫你的 [Authorize] api 時設定 Header Authorization : Bearer + 空格 + accessToken,系統會自動驗證。

舉例:

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function() {
  if(this.readyState === 4) {
    console.log(this.responseText);
  }
});
xhr.open("GET", "http://yourhost:5014/your/api");
xhr.setRequestHeader("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxZTIxOGY4My1iZjE3LTRhY2YtODhmOS1iOTQ3NjhjOWUwMGMiLCJuYW1lIjoiYWRtaW5AbWluaS1zb2Z0d2FyZS5naXRodWIuaW8iLCJyb2xlIjoibWluaWF1dGgtYWRtaW4iLCJzdWIiOiJhZG1pbkBtaW5pLXNvZnR3YXJlLmdpdGh1Yi5pbyIsIm5iZiI6MTcxODIwNDg5NSwiZXhwIjoxNzE4MjA1Nzk1LCJpYXQiOjE3MTgyMDQ4OTUsImlzcyI6Ik1pbmlBdXRoIn0._-DQ_rcbeju8_nrK2lD5we0rre04_xdDZNF6NhM0Rg0");
xhr.send();

設定過期時間

 options.TokenExpiresIn = 30 * 60; 

單位為秒,預設30分鐘,另外注意 .NET JWT ClockSkew JwtBearerOptions 預設要額外加上5分鐘 原因

重新整理 Refresh Token API (JWT)

API : /MiniAuth/refreshToken
Body:

{
   "refreshToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxZTIxOGY4My1iZjE3LTRhY2YtODhmOS1iOTQ3NjhjOWUwMGMiLCJuYmYiOjE3MTg1MjIxOTEsImV4cCI6MTcxODUyMzk5MSwiaWF0IjoxNzE4NTIyMTkxLCJpc3MiOiJNaW5pQXV0aCJ9.HYBWrM2suDiM4OG0FSlXhNgktZIG9l3ufmIAnwZiIoU"
}

Header:

Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW5AbWluaS1zb2Z0d2FyZS5naXRodWIuaW8iLCJyb2xlIjoibWluaWF1dGgtYWRtaW4iLCJzdWIiOiIxZTIxOGY4My1iZjE3LTRhY2YtODhmOS1iOTQ3NjhjOWUwMGMiLCJuYmYiOjE3MTg1MjIxOTEsImV4cCI6MTcxODUyNTc5MSwiaWF0IjoxNzE4NTIyMTkxLCJpc3MiOiJNaW5pQXV0aCJ9.rgAgsziAdLqOC9NYra-M9WQl8BJ99sRdfzRKNkMz9dk

過期時間為 MiniAuthOptions.TokenExpiresIn / 2,預設30分鐘

設定、選項、自定義

預設模式

  • MiniAuth 預設模式為IT Admin 集中使用者管理,使用者註冊、密碼重置等操作需要 Admin 許可權賬號操作,預設 Role = miniauth-admin

關閉 MiniAuth Login

如果你只想用自己的登入邏輯、頁面、API,可以指定登入路徑,關閉開關

// 放在 service 註冊之前
builder.Services.AddMiniAuth(options: (options) =>
{
    options.LoginPath = "/Identity/Account/Login";
    options.DisableMiniAuthLogin = true;
});

自定義預設的 SQLite Connection String

builder.Services.AddMiniAuth(options: (options) =>
{
    options.SqliteConnectionString = "Data Source=miniauth_identity.db";
});

自定義資料庫、使用者、角色

MiniAuth 系統預設使用 SQLite EF Core、IdentityUser、IdentityRole開箱即用
如果需要切換請在 app.UseMiniAuth 泛型指定不同的資料庫、自己的使用者、角色類別。

app.UseMiniAuth<YourDbContext, YourIdentityUser, YourIdentityRole>();

登入、使用者驗證

非 ApiController 預設登入導向 login.html 頁面 (判斷方式Headers["X-Requested-With"] == "XMLHttpRequest" 或是 ApiControllerAttribute)
ApiController 的 Controller 預設不會導向登入頁面,而是返回 401 status code

自定義前端

  • 管理後臺前端在 /src/Frontend_Identity 主體使用 Vue3 + Vite,使用 npm run build 後即可更新 miniauth 的 UI
  • 登入頁面不想使用 miniauth 預設, mvc可以使用 identity 自帶的Scaffolded Login.cshtml ,或是更改 miniauth 前端的 login.html, js, css

自定路由字首

builder.Services.AddMiniAuth(options: (options) =>
{
    options.RoutePrefix = "YourName";
});

預設 RoutePrefix 為 MiniAuth

登入API (JWT)

API: /MiniAuth/login

Body:

{
   "username":"admin@mini-software.github.io",
   "password":"E7c4f679-f379-42bf-b547-684d456bc37f",
   "remember":false
}

Response:

{
    "ok": true,
    "code": 200,
    "message": null,
    "data": {
        "tokenType": "Bearer",
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiYWRtaW5AbWluaS1zb2Z0d2FyZS5naXRodWIuaW8iLCJyb2xlIjoibWluaWF1dGgtYWRtaW4iLCJzdWIiOiIxZTIxOGY4My1iZjE3LTRhY2YtODhmOS1iOTQ3NjhjOWUwMGMiLCJuYmYiOjE3MTg1MjIxOTEsImV4cCI6MTcxODUyNTc5MSwiaWF0IjoxNzE4NTIyMTkxLCJpc3MiOiJNaW5pQXV0aCJ9.rgAgsziAdLqOC9NYra-M9WQl8BJ99sRdfzRKNkMz9dk",
        "expiresIn": 3600,
        "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxZTIxOGY4My1iZjE3LTRhY2YtODhmOS1iOTQ3NjhjOWUwMGMiLCJuYmYiOjE3MTg1MjIxOTEsImV4cCI6MTcxODUyMzk5MSwiaWF0IjoxNzE4NTIyMTkxLCJpc3MiOiJNaW5pQXV0aCJ9.HYBWrM2suDiM4OG0FSlXhNgktZIG9l3ufmIAnwZiIoU"
    }
}

註冊

請使用 ASP.NET Core Identity 自帶的註冊API跟頁面

忘記密碼

請使用 ASP.NET Core Identity 自帶的註冊API跟頁面

獲取使用者資訊

請使用 ASP.NET Core Identity 自帶的註冊API跟頁面

注意事項

注意順序

請將 UseMiniAuth 放在路由生成之後,否則系統無法獲取路由資料作許可權判斷,如 :

app.UseRouting();
app.UseMiniAuth();

請新增 Role 規則

請新增 AddRoles<IdentityRole>(),否則 [Authorize(Roles = "許可權")] 不會生效

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddRoles<IdentityRole>() // ❗❗❗ 
    .AddEntityFrameworkStores<ApplicationDbContext>();

應用在現有的 identity 專案,自定義邏輯

把 AddMiniAuth autoUse 關閉,將 UseMiniAuth 並在泛型引數換上自己的 IdentityDBContext、使用者、許可權認證,放在自己的 Auth 之後,例子:

        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(connectionString));
            builder.Services.AddDatabaseDeveloperPageExceptionFilter();

            builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

            builder.Services.AddControllersWithViews();

            builder.Services.AddMiniAuth(autoUse: false); // <= ❗❗❗


            var app = builder.Build();

            app.UseMiniAuth<ApplicationDbContext, IdentityUser, IdentityRole>();  // <= ❗❗❗ 
            app.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            app.MapRazorPages();

            app.Run();
        }

能切換使用自己的使用者、角色、DB、Identity 邏輯。

分散式系統

  • 資料庫來源請換成 SQL Server、MySQL、PostgreSQL 等資料庫
  • 建議更換 JWT 等 auth 方式

相關文章