برنامه نویسی

پیاده سازی کنترل دسترسی مبتنی بر نقش در SvelteKit

فهرست مطالب

پیش نیازها

قبل از شروع، باید پیش نیازهای زیر را داشته باشید:

  • دانش اولیه SvelteKit: آشنایی با مفاهیم اصلی SvelteKit، مانند مسیریابی، کامپوننت ها و فروشگاه ها، مفید خواهد بود.
  • Node.js و npm: باید Node.js و npm (Node Package Manager) را روی سیستم خود نصب کنید. اگر قبلاً این کار را نکرده اید، می توانید آنها را از وب سایت رسمی Node.js دانلود و نصب کنید.
  • Git: مطمئن شوید که Git را روی دستگاه خود نصب کرده اید. اگر نه، می توانید آن را از وب سایت Git دانلود کنید.

بنر Github

آشنایی با کنترل دسترسی مبتنی بر نقش (RBAC)

RBAC یک مدل امنیتی است که مجوزهای کاربر را بر اساس نقش آنها در یک برنامه سازماندهی می کند.

هر نقش با مجموعه‌ای از مجوزها مرتبط است و به کاربران نقش‌های خاصی اختصاص داده می‌شود تا مشخص کنند به چه چیزی می‌توانند دسترسی داشته باشند.

نقش ها و مجوزها

نقش‌ها مجموعه‌ای از مجوزهای مرتبط را نشان می‌دهند که مشخص می‌کنند کاربر چه کاری می‌تواند در یک برنامه انجام دهد.

به عنوان مثال، یک برنامه کاربردی ممکن است نقش هایی مانند Admin، Editor، و Viewer هر کدام با مجموعه های مختلفی از مجوزها.

از سوی دیگر، مجوزها، اقدامات یا عملیات خاصی هستند که یک کاربر با یک نقش خاص می تواند انجام دهد.

این موارد می تواند از مشاهده و ویرایش داده ها تا مدیریت حساب های کاربری یا پیکربندی های سیستم را شامل شود.

راه اندازی پروژه SvelteKit

قبل از شروع پیاده سازی RBAC، به یک پروژه SvelteKit آماده کار با آن نیاز داریم.

برای ساده نگه داشتن کارها، از یک پروژه تجارت الکترونیکی آزمایشی به عنوان الگوی شروع خود استفاده خواهیم کرد. کد کامل این پروژه را می توانید در این مخزن GitHub پیدا کنید.

برای دریافت قالب استارتر، دستورات زیر را اجرا کنید:

git clone https://github.com/TropicolX/svelte-rbac-demo.git
cd svelte-rbac-demo
git checkout starter
npm install
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ساختار پروژه باید مانند شکل زیر باشد:

تصویری از ساختار پروژه

در مرحله بعد، اجازه دهید به فایل‌ها و دایرکتوری‌های ضروری که برای پروژه با آنها کار می‌کنیم، بپردازیم:

  • /lib: این فهرست شامل تمام ابزارها و مؤلفه های ما خواهد بود.
  • /routes: شامل تمام مسیرهای موجود در برنامه ما است.

    • /(protected): این پوشه حاوی تمام مسیرهای محافظت شده ما است. فقط کاربرانی که وارد سیستم شده اند ممکن است بتوانند در این مسیرها پیمایش کنند.
    • /login: این مسیر به صفحه ورود است.
    • /unauthorized: این مسیری است که کاربران را هنگامی که دسترسی به یک صفحه غیرمجاز ندارند به آن هدایت می کنیم.
    • +layout.svelte: این شامل طرح UI و منطق است که برای هر صفحه اعمال می شود.
    • +page.svelte: این مسیر صفحه اصلی ما است.
  • hooks.server.js: این فایل حاوی تمام منطق سمت سرور است که برای احراز هویت کاربران در هنگام رفتن به یک مسیر محافظت شده و هدایت آنها در صورت لزوم نیاز داریم. همچنین داده های کاربر را روی سرور واکشی می کند و در سرور ذخیره می کند event.locals هدف – شی.

بررسی اجمالی پروژه

برای شروع پروژه، دستور زیر را در پوشه اصلی اجرا کنید:

npm run dev
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

شما باید صفحه ای مانند صفحه زیر را ببینید:

اسکرین شات صفحه اصلی پروژه تجارت الکترونیک

بعد، بیایید وارد برنامه شویم. روی دکمه “ورود به سیستم” در گوشه سمت راست بالای صفحه کلیک کنید.

صفحه ورود به سیستم

می‌توانید به‌عنوان مدیر یا مشتری با استفاده از اطلاعات کاربری زیر وارد سیستم شوید:

  • مدیر:

    • پست الکترونیک: admin@gmail.com
    • کلمه عبور: admin123
  • مشتری:

    • پست الکترونیک: john@mail.com
    • کلمه عبور: changeme

پس از ورود به سیستم، به صفحه اصلی هدایت می شوید و می توانید دو پیوند جدید را در نوار ناوبری مشاهده کنید:

صفحه اصلی اولیه هنگام ورود به سیستم

این ما را به مشکل فعلی برنامه تجارت الکترونیکی ما می‌رساند: باید اطمینان حاصل کنیم که فقط ادمین‌ها می‌توانند به صفحه مدیریت دسترسی داشته باشند و به همین ترتیب، فقط مشتریان می‌توانند به صفحه نمایه دسترسی داشته باشند.

علاوه بر این، ما باید اطمینان حاصل کنیم که فقط مدیران و مشتریان با مجوزهای لازم می توانند اقدامات خاصی را در صفحات مربوطه خود انجام دهند.

به عنوان مثال، فقط یک مدیر با مجوز ایجاد محصولات باید بتواند این کار را در صفحه مدیریت انجام دهد.

یکپارچه سازی تنظیمات و ابزارهای RBAC

اکنون که پروژه Svelekit را راه اندازی کرده و با آن آشنا شده ایم، اجازه دهید پیاده سازی RBAC را در برنامه خود آغاز کنیم. ما با تعریف نقش ها و مجوزها در برنامه شروع می کنیم.

تعریف نقش ها و مجوزها

در پروژه SvelteKit یک فایل جدید به نام ایجاد کنید constants.js در src دایرکتوری و کد زیر را اضافه کنید:

// src/constants.js
export const ROLES = {
    ADMIN: "admin",
    CUSTOMER: "customer",
};

export const PERMISSIONS = {
    CREATE_PRODUCTS: "create:products",
    UPDATE_PRODUCTS: "update:products",
    DELETE_PRODUCTS: "delete:products",
    READ_PROFILE: "read:profile",
    UPDATE_PROFILE: "update:profile",
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در کد بالا، نقش‌ها و ثابت‌های مجوز برنامه را تعریف کردیم.

در مرحله بعد، باید مجوزهای هر نقش را تعریف کنیم. حرکت به lib دایرکتوری و ایجاد یک rolePermissions.js فایل با کد زیر:

// src/lib/rolePermissions.js
import { PERMISSIONS, ROLES } from "../constants";

export const rolePermissions = {
    [ROLES.ADMIN]: [
        PERMISSIONS.CREATE_PRODUCTS,
        PERMISSIONS.UPDATE_PRODUCTS,
        PERMISSIONS.DELETE_PRODUCTS,
    ],
    [ROLES.CUSTOMER]: [PERMISSIONS.READ_PROFILE, PERMISSIONS.UPDATE_PROFILE],
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کد بالا هر یک از نقش ها را با مجوزهای خاص تنظیم می کند. ما می خواهیم که کاربران ادمین بتوانند محصولات را ایجاد، به روز رسانی و حذف کنند.

از سوی دیگر، ما می خواهیم مشتریان بتوانند جزئیات پروفایل خود را بخوانند و به روز کنند.

پیاده سازی توابع سودمند RBAC

در مرحله بعد، اجازه دهید برخی از توابع کاربردی ایجاد کنیم تا بررسی کنیم آیا یک کاربر دارای نقش یا مجوز خاصی است یا خیر. این توابع کنترل دسترسی را در سراسر برنامه اعمال می کنند.

در lib دایرکتوری، ایجاد یک rbacUtils.js فایل و کد زیر را اضافه کنید:

// src/lib/rbacUtils.js
import { rolePermissions } from "./rolePermissions";

// Function to check if a user has a specific role
export function checkRole(user, requiredRole) {
    if (!user) {
        return false;
    }

    return user.role === requiredRole;
}

// Function to check if a user has specific permissions
export function checkPermissions(user, requiredPermissions) {
    if (!user) {
        return false;
    }

    const userPermissions = rolePermissions[user.role];
    return userPermissions?.includes(requiredPermissions);
}

// Function to check if a user has both a specific role and permissions
export function checkRoleAndPermissions(
    user,
    requiredRole,
    requiredPermissions
) {
    return (
        checkRole(user, requiredRole) &&
        checkPermissions(user, requiredPermissions)
    );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با وجود این پیکربندی‌ها، می‌توانیم RBAC را در برنامه خود ادغام کنیم.

ناوبری پویا بر اساس نقش های کاربر

رابط کاربری ناوبری اولین ناحیه ای است که در برنامه ما RBAC را پیاده سازی می کنیم. ما باید اطمینان حاصل کنیم که کاربران فقط پیوندهای ناوبری مربوط به نقش خود را می بینند.

ابتدا به سمت +layout.svelte فایل در routes پوشه و کد زیر را اضافه کنید:


<script>
    ...
    import Loading from "$lib/components/Loading.svelte";
    import { checkRole } from "$lib/rbacUtils";
    import { ROLES } from "../constants";
    import { user, cart, products } from "../stores";
    import "../app.css";

    ...
    $: isAdmin = checkRole($user, ROLES.ADMIN);
    $: isCustomer = checkRole($user, ROLES.CUSTOMER);
</script>

...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا دو متغیر تعریف کردیم isAdmin و isCustomer، که به ترتیب بررسی می کند که آیا کاربر مدیر یا مشتری است. ما همچنین از اعلان‌های واکنشی برای تعریف متغیرها استفاده کردیم تا هرگونه تغییر در داده‌های کاربر، به عنوان مثال، اگر کاربر از سیستم خارج شود، مقادیر آنها را به‌روزرسانی کند.

در مرحله بعد، بیایید پیوندهای ناوبری خود را به روز کنیم:


...

<header
    class="bg-gray-900 text-white py-4 px-6 md:px-12 flex items-center justify-between"
>
    ...
    <nav class="hidden md:flex items-center space-x-6">
        {#if isAdmin}
            <a class="hover:text-gray-300" href="/admin">Admin</a>
        {/if}
        {#if isCustomer}
            <a class="hover:text-gray-300" href="/user">Profile</a>
        {/if}
    </nav>
    ...
</header>

...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نسخه ی نمایشی اجرای ناوبری پویا

اکنون، لینک‌های پیمایش برای صفحه‌های ادمین و نمایه به صورت مشروط بر اساس نقش کاربر نمایش داده می‌شوند.

ایمن سازی مسیرها بر اساس نقش های کاربر

در بخش قبل، ناوبری سمت مشتری پویا را بر اساس نقش های کاربر اضافه کردیم. با این حال، کاربران همچنان می توانند با استفاده از ناوبری مرورگر به مسیرهای غیرمجاز حرکت کنند. برای جلوگیری از این امر، باید مستقیماً با افزودن حفاظت مبتنی بر نقش، مسیرها را ایمن کنیم.

ما می توانیم این کار را در Svelte با اضافه کردن a انجام دهیم +layout.server.js فایل در هر مسیر برای اجرای سمت سرور load تابع. تابع بارگذاری هر زمان که کاربر صفحه را بارگیری کند اجرا می شود و بررسی می کند که آیا کاربر بر اساس نقش خود به مسیر دسترسی دارد یا خیر. اگر آنها مجاز نباشند، به آن هدایت می شوند /unauthorized صفحه

بیایید با پیاده سازی این در صفحه مدیریت شروع کنیم. سر به سمت admin دایرکتوری در (protected) پوشه، ایجاد یک +layout.server.js فایل با کد زیر:

// src/routes/(protected)/admin/+layout.server.js
import { redirect } from "@sveltejs/kit";

import { checkRole } from "$lib/rbacUtils";
import { ROLES } from "../../../constants";

/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
    const user = locals.user;
    const isAdmin = checkRole(user, ROLES.ADMIN);

    if (!isAdmin) {
        redirect(307, "/unauthorized");
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نسخه ی نمایشی برای ایمن کردن مسیر مدیریت

حال، اگر یک کاربر غیر ادمین سعی کند به مسیر مدیریت دسترسی پیدا کند، به درستی هدایت می شود.

بعد، بیایید خود را ایمن کنیم user مسیر در داخل (protected) پوشه، به user دایرکتوری و ایجاد یک +layout.server.js فایل با کد زیر:

// src/routes/(protected)/user/+layout.server.js
import { redirect } from "@sveltejs/kit";

import { checkRoleAndPermissions } from "$lib/rbacUtils";
import { PERMISSIONS, ROLES } from "../../../constants";

/** @type {import('./$types').LayoutServerLoad} */
export function load({ locals }) {
    const user = locals.user;
    const isCustomerAndCanViewProfile = checkRoleAndPermissions(
        user,
        ROLES.CUSTOMER,
        PERMISSIONS.READ_PROFILE
    );

    if (!isCustomerAndCanViewProfile) {
        redirect(307, "/unauthorized");
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

نسخه ی نمایشی برای ایمن کردن مسیر کاربر

در اینجا، ما با استفاده از آن کارها را کمی متفاوت انجام می دهیم checkRoleAndPermissions عملکردی برای اطمینان از اینکه کاربر هم مشتری است و هم اجازه دارد یک نمایه را قبل از دسترسی به صفحه نمایه بخواند.

این تابع می تواند در سناریوهای پیچیده تری که نقشی ممکن است دارای مجوزهای اضافی یا سفارشی متفاوت از مجوزهای پیش فرض باشد، مفید باشد.

در ادامه به نحوه مدیریت مجوزهای نقش در کامپوننت ها می پردازیم.

مدیریت مجوزها در کامپوننت ها

اکنون که مسیرهای خود را ایمن کرده‌ایم، قدم بعدی محافظت از اقدامات و ویژگی‌های موجود در آن صفحات است تا اطمینان حاصل شود که تنها کاربرانی که مجوزهای لازم را دارند می‌توانند به آنها دسترسی داشته باشند.

به عنوان مثال، می توانید سه اقدام اصلی را در صفحه مدیریت انجام دهید:

  • یک محصول ایجاد کنید
  • یک محصول را ویرایش کنید
  • حذف یک محصول

اسکرین شات از داشبورد مدیریت

در حال حاضر، یک ادمین به صورت پیش‌فرض اجازه انجام تمام این اقدامات را دارد. اما فرض کنید بعداً در توسعه، تغییری برای لغو آن ایجاد شد create:products اجازه از نقش مدیر پیش فرض و تنها به چند مدیر منتخب داده می شود.

در آن صورت، اطمینان از اینکه فقط آن ادمین‌های خاص می‌توانند محصولی را ایجاد کنند، بسیار مهم است.

ما می توانیم مجوزهای پروژه خود را با استفاده از checkPermissions تابع سودمند

به عنوان مثال، در +page.svelte فایل در admin دایرکتوری، ما می توانیم کد زیر را اضافه کنیم تا مطمئن شویم که فقط مدیران با آن create:product مجوز می تواند به دکمه “افزودن محصول جدید” دسترسی داشته باشد:


<script>
    import { checkPermissions } from "$lib/rbacUtils";
    import { PERMISSIONS } from "../../../constants";
    import { user, products } from "../../../stores";

    $: canCreateProducts = checkPermissions($user, PERMISSIONS.CREATE_PRODUCTS);
</script>

<div class="container mx-auto px-4 md:px-8 py-12">
    <div class="flex flex-wrap items-center justify-between mb-8">
        <h2 class="text-3xl md:text-4xl font-bold">Admin Dashboard</h2>
        {#if canCreateProducts}
            <button
                class="mt-4 min-[457px]:mt-0 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2 bg-gray-900 text-white hover:bg-gray-800"
            >
                Add new product
            </button>
        {/if}
    </div>
    ...
</div>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر حذف کنیم PERMISSIONS.CREATE_PRODUCTS از مجوزهای مدیریت در rolePermissions.js فایل، می‌توانیم اعمال تغییر را مشاهده کنیم:

// src/lib/rolePermissions.js
...

export const rolePermissions = {
    [ROLES.ADMIN]: [
        PERMISSIONS.UPDATE_PRODUCTS,
        PERMISSIONS.DELETE_PRODUCTS,
    ],
    ...
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اسکرین شات از داشبورد مدیریت بدون دکمه محصول جدید

بیایید با ایمن کردن ویژگی ویرایش و حذف نیز یک قدم جلوتر برویم:


<script>
    ...
    $: canDeleteProducts = checkPermissions($user, PERMISSIONS.DELETE_PRODUCTS);
    $: canUpdateProducts = checkPermissions($user, PERMISSIONS.UPDATE_PRODUCTS);
</script>

...
<tbody class="[&_tr:last-child]:border-0">
    {#each $products as product (product.id)}
        <tr
            class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted"
        >
            ...
            <td class="p-4 align-middle [&:has([role=checkbox])]:pr-0">
                <div class="flex items-center space-x-2">
                    {#if canUpdateProducts}
                        
                        <button
                            class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 w-10"
                        >
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                width="24"
                                height="24"
                                viewBox="0 0 24 24"
                                fill="none"
                                stroke="currentColor"
                                stroke-width="2"
                                stroke-linecap="round"
                                stroke-linejoin="round"
                                class="h-4 w-4"
                            >
                                <path
                                    d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"
                                ></path>
                            </svg>
                        </button>
                    {/if}

                    {#if canDeleteProducts}
                        
                        <button
                            class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 w-10"
                        >
                            <svg
                                xmlns="http://www.w3.org/2000/svg"
                                width="24"
                                height="24"
                                viewBox="0 0 24 24"
                                fill="none"
                                stroke="currentColor"
                                stroke-width="2"
                                stroke-linecap="round"
                                stroke-linejoin="round"
                                class="h-4 w-4"
                            >
                                <path d="M3 6h18"></path>
                                <path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"
                                ></path>
                                <path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"
                                ></path>
                            </svg>
                        </button>
                    {/if}
                </div>
            </td>
        </tr>
    {/each}
</tbody>
...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در مرحله بعد، بیایید همین سیاست را در صفحه نمایه خود اجرا کنیم. حرکت به +page.svelte فایل در user دایرکتوری و کد زیر را اضافه کنید:


<script>
    import { checkPermissions } from "$lib/rbacUtils";
    import { PERMISSIONS } from "../../../constants";
    import { user } from "../../../stores";

    $: canEditProfile = checkPermissions($user, PERMISSIONS.UPDATE_PROFILE);

    let name = $user.name;
    let email = $user.email;
</script>

<div class="h-[calc(100svh-64px)] w-full flex items-center justify-center">
    <div
        class="rounded-lg border bg-card text-card-foreground shadow-sm w-[28rem] max-w-md"
    >
        ...
        <div class="p-6 space-y-4">
            <div class="space-y-2">
                ...
                <input
                    ...
                    disabled={!canEditProfile}
                />
            </div>
            <div class="space-y-2">
                ...
                <input
                    ...
                    disabled={!canEditProfile}
                />
            </div>
            <div class="space-y-2">
                ...
                <input
                    ...
                    disabled={!canEditProfile}
                />
            </div>
            <div class="space-y-2">
                ...
                <input
                    ...
                    disabled={!canEditProfile}
                />
            </div>
        </div>
        <div class="flex items-center p-6">
            <button
                ...
                disabled={!canEditProfile}
            >
                Save
            </button>
        </div>
    </div>
</div>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا، ما از canEditProfile متغیر برای تعیین اینکه آیا فیلدهای ورودی و دکمه ذخیره غیرفعال می شوند یا خیر. اگر مقدار باشد false، فیلدهای ورودی و دکمه ارسال غیرفعال می شود و بالعکس.

و با آن، ما باید یک برنامه وب تجارت الکترونیک کاملاً ایمن با استفاده از RBAC داشته باشیم!

نسخه ی نمایشی نهایی RBAC

نتیجه

در این آموزش، نحوه پیاده سازی Role-Based Access Control (RBAC) در برنامه های Sveltekit را بررسی کردیم.

ما با یادگیری در مورد اصول RBAC، مانند نقش ها و مجوزها و اهمیت آنها در توسعه وب، شروع کردیم.

سپس اجرای آن را مورد بررسی قرار دادیم که شامل پیکربندی نقش‌ها و مجوزهای سفارشی، یکپارچه‌سازی ابزارهای RBAC، ایمن‌سازی مسیرها و مدیریت مجوزها در اجزا بود.

همانطور که به توسعه برنامه های SvelteKit خود ادامه می دهید، به یاد داشته باشید که به طور منظم پیکربندی RBAC خود را برای انطباق با نیازهای در حال تغییر و کاهش خطرات امنیتی بالقوه مرور و به روز کنید.

علاوه بر این، برای حفظ یکپارچگی و اثربخشی اجرای RBAC خود، نظارت مستمر، ثبت و آزمایش را در اولویت قرار دهید.

شما می توانید کد پروژه کامل را در این Repo GitHub پیدا کنید.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا