۱۴۰۴/۰۷/۱۳ Nebular

Linked Server در SQL Server

تعریف Linked Server

Linked Server در SQL Server یعنی:

ایجاد یک اتصال دائمی (persistent connection) بین سرور فعلی و یک سرور دیگر (که می‌تونه SQL Server یا هر دیتابیس دیگری مثل Oracle، MySQL، یا حتی Excel باشه) تا بتونی مستقیماً از طریق T-SQL به اون سرور query بزنی.

کاربرد اصلی

وقتی یک Linked Server تعریف کردی، می‌تونی مثل یک جدول لوکال، از جداول سرور مقصد Select، Insert، Update، Delete انجام بدی.
یعنی بدون اینکه نیاز به export/import یا SSIS داشته باشی، مستقیماً query می‌زنی.

🧩 مثال کاربردی

فرض کن دو سرور داری:

  • سرور اصلی: SQLServer_Main
  • سرور دوم: SQLServer_Branch

می‌خوای از سرور اصلی، اطلاعات جدول Customers در سرور دوم رو بخونی.

اول Linked Server رو می‌سازی:

EXEC sp_addlinkedserver 
    @server = N'SQLServer_Branch', 
    @srvproduct = N'SQL Server';

بعد اطلاعات لاگین برای اتصال:

EXEC sp_addlinkedsrvlogin 
    @rmtsrvname = N'SQLServer_Branch',
    @useself = 'false',
    @locallogin = NULL,
    @rmtuser = N'sa',
    @rmtpassword = N'YourPassword';

حالا می‌تونی به راحتی کوئری بزنی:

SELECT *
FROM [SQLServer_Branch].[DatabaseName].[dbo].[Customers];

⚙️ ساختار چهار قسمتی نام‌ها

در Linked Server همیشه از فرمت زیر برای دسترسی استفاده می‌کنیم:

[LinkedServerName].[DatabaseName].[SchemaName].[ObjectName]

مثلاً:

SELECT * FROM [MyLinkedServer].[SalesDB].[dbo].[Orders];

🛠 کاربردهای واقعی

  1. ترکیب داده‌ها از چند سرور
    برای مثال گزارش‌گیری از چند دیتابیس فروش در شعب مختلف.
  2. ETL یا مهاجرت داده‌ها
    انتقال داده از یک سیستم به سیستم دیگر با query ساده.
  3. اجرای distributed query
    اجرای کوئری‌هایی که داده‌هاش از چند منبع مختلف میان.

⚠️ نکات مهم (Best Practices)

  • عملکردش نسبت به query لوکال کندتره، چون شبکه درگیر میشه.
  • برای performance بهتر، از OPENQUERY استفاده کن (query سمت سرور مقصد اجرا میشه).
  • سطح دسترسی‌ها رو محدود کن تا امنیت حفظ بشه.
  • وقتی فقط گاهی نیاز داری، بهتره از Linked Server موقت استفاده نکنی، بلکه Connection string در اپلیکیشن بسازی.

🧠 مثال با OPENQUERY (روش بهینه‌تر)

SELECT *
FROM OPENQUERY([SQLServer_Branch], 'SELECT TOP 10 * FROM SalesDB.dbo.Orders');

اینجا کل کوئری در سمت سرور مقصد اجرا میشه و فقط نتیجه به سرور اصلی برمی‌گرده — بسیار سریع‌تر 🚀

چرا باید سطح دسترسی Linked Server رو محدود کنیم؟

وقتی یه Linked Server تعریف می‌کنی، در واقع داری به SQL Server می‌گی:

“هر وقت از این سرور (A) به اون سرور (B) وصل شدم، از فلان کاربر و رمز عبور استفاده کن.”

اگر این تنظیم رو درست انجام ندی، ممکنه:

  • همه‌ی کاربران سرور A بتونن به داده‌های حساس در سرور B دسترسی پیدا کنن 😱
  • یا حتی کاربران بدون پسورد خاصی بتونن query اجرا کنن روی سرور دوم.

🧩 نحوه کنترل دسترسی با sp_addlinkedsrvlogin

دستور زیر مشخص می‌کنه چه کاربری از سرور محلی، با چه کاربری در سرور مقصد وصل بشه:

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'LinkedServerName',
    @useself = 'false',
    @locallogin = 'LocalUserName',
    @rmtuser = 'RemoteUserName',
    @rmtpassword = 'Password123';

پارامترها:

پارامترتوضیح
@rmtsrvnameاسم Linked Server
@useselfاگر 'true' باشه، همون لاگین فعلی روی سرور محلی برای اتصال استفاده میشه
@localloginنام کاربری در سرور محلی
@rmtuserنام کاربری در سرور مقصد
@rmtpasswordرمز عبور کاربر در سرور مقصد

🧠 سه سناریوی مهم امنیتی

1. ✅ امن‌ترین حالت: دسترسی اختصاصی برای هر کاربر

هر کاربر لوکال فقط با یک یوزر خاص به سرور مقصد وصل بشه.

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = 'report_user',
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

🔹 مزیت: دقیقاً می‌دونی چه کسی داره به چی دسترسی داره.
🔹 عیب: باید برای هر کاربر جدا تنظیم کنی.


2. ⚠️ حالت عمومی (برای همه کاربران لوکال)

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = NULL,
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

یعنی همه کاربران لوکال با همون کاربر branch_reader به سرور مقصد وصل میشن.
🔸 این روش رایجه ولی خطرناکه، چون کنترل جزئی نداری.


3. ❌ بدترین حالت: استفاده از sa یا sysadmin

@rmtuser = 'sa'

🚫 هرگز نکن.
چون اگه کسی از سرور A بتونه query اجرا کنه، عملاً دسترسی کامل به کل سرور B پیدا می‌کنه.


🔐 چند توصیه امنیتی حرفه‌ای

  1. برای Linked Server یک یوزر اختصاصی بساز (مثلاً linked_reader) با حداقل سطح دسترسی (فقط SELECT یا EXEC روی Stored Procedure مجاز).
  2. از Windows Authentication استفاده کن تا کنترل توسط Active Directory انجام بشه.
  3. در فایروال فقط پورت مورد نیاز (پیش‌فرض 1433) رو بین دو سرور باز کن.
  4. از SSL/TLS برای ارتباط امن بین سرورها استفاده کن.
  5. دسترسی Public یا sysadmin رو به Linked Server نده.
  6. در تنظیمات Linked Server، گزینه‌ی RPC Out و Data Access رو فقط در صورت نیاز فعال کن.

بررسی تنظیمات امنیتی موجود

برای دیدن لاگین‌های فعلی مرتبط با Linked Server:

EXEC sp_helplinkedsrvlogin 'LinkedServerName';

برای حذف تنظیم ناامن:

EXEC sp_droplinkedsrvlogin 'LinkedServerName', NULL;

می‌خوای در ادامه برات توضیح بدم گزینه‌های امنیتی Linked Server در GUI (از SSMS) مثل:

  • “Be made using the login’s current security context”
  • “Be made using this security context”

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = 'report_user',
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

توضیح خط به خط

  1. EXEC sp_addlinkedsrvlogin
    فراخوانی پروسیجر سیستمی‌ای هست که یک نقشه (mapping) بین لاگین‌های محلی (سروری که کوئری اجرا می‌کنه) و یک حساب روی سرور مقصد برای یک Linked Server تعریف می‌کنه. این پروسیجر مشخص می‌کنه وقتی یک لاگین محلی از طریق Linked Server وصل میشه، با چه credentials ای به سرور remote وصل بشه.
  2. @rmtsrvname = 'BranchServer'
    اسم Linked Server (همون نامی که با sp_addlinkedserver تعریف کردی) رو مشخص می‌کنه. در واقع این mapping برای آنِ Linked Server به نام BranchServer ساخته میشه.
  3. @useself = 'false'
    تعیین می‌کنه که اتصال به سرور مقصد با همان security context کاربر فعلی (current login) انجام شود یا با یک credential مشخص.
    • 'true' → از لاگین فعلی (impersonate) استفاده میشه (pass-through). این حالت معمولاً برای Windows Authentication و در صورت پیکربندی Kerberos/Delegation کاربرد داره.
    • 'false' → از @rmtuser/@rmtpassword استفاده میشه (یعنی شما صریحاً یک نام کاربری و رمز برای سرور مقصد مشخص کردی).
      در این مثال چون 'false' هست، هر بار که این mapping اعمال بشه اتصال با branch_reader خواهد بود — نه با لاگین محلی.
  4. @locallogin = 'report_user'
    مشخص می‌کنه این mapping فقط برای چه لاگینی در سرور محلی اعمال بشه.
    • اگر یک کاربر با لاگین سرور report_user از Linked Server استفاده کنه، اتصال به BranchServer با credential مشخص شده در @rmtuser انجام میشه.
    • اگر این پارامتر NULL بود، یعنی یک mapping عمومی برای همه لاگین‌های محلی ساخته شده (همه با همان @rmtuser وصل می‌شن).
    • اگر لاگین دیگری (غیر از report_user) کوئری بزنه و هیچ mapping دیگری نداشته باشیم، اتصال با این mapping انجام نمی‌شه و یا بسته به تنظیمات، خطا میاره یا از mapping عمومی (اگر وجود داشته باشه) استفاده میشه.
    نکته: locallogin اشاره به Server Login داره (نه صرفاً Database User). یعنی اسم لاگینی که در Instance تعریف شده.
  5. @rmtuser = 'branch_reader'
    نام کاربری‌ای روی سرور مقصد هست که قرار است هنگام اتصال استفاده شود.
    • معمولاً این یک SQL Login در سرور مقصد است (یعنی در آن سرور CREATE LOGIN branch_reader باید وجود داشته باشد).
    • سطح دسترسی این حساب روی دیتابیس‌های مقصد تعیین‌کننده عملیاتی است که remote session می‌تواند انجام دهد (مثلاً فقط SELECT یا اجازه EXEC روی Stored Procedure خاص).
  6. @rmtpassword = 'StrongP@ss1'
    رمز عبور @rmtuser را مشخص می‌کند (وقتی @rmtuser یک SQL Login است).
    • این مقدار هنگام ذخیره mapping استفاده می‌شود تا SQL Server هنگام اتصال به سرور مقصد این credentials را ارسال کند.
    • هشدار امنیتی: نگه‌داری password به‌صورت متن قابل خواندن در اسکریپت‌ها و مکان‌های ناامن می‌تواند مشکل‌ساز باشد — اگر امکانش هست از Windows Auth یا روش‌های امن‌تر استفاده کن.

نتیجهٔ عملی (وقتی این mapping وجود داشته باشه)

  • اگر کاربری با Login سرور report_user بخواد کوئری روی Linked Server اجرا کنه: SELECT * FROM [BranchServer].[SalesDB].[dbo].[Customers]; این اتصال با credentials branch_reader و رمز StrongP@ss1 به سرور BranchServer برقرار می‌شه و عملیات با مجوزهای branch_reader انجام میشه.
  • اگر لاگین دیگری (مثلاً other_user) اقدام کنه، این mapping اعمال نمی‌شه (مگر اینکه mapping عمومی با @locallogin = NULL وجود داشته باشه یا mapping جداگانه‌ای برای آن لاگین ساخته شده باشد).

دستورات کمکی (برای بررسی / حذف / تست)

  • دیدن mappingهای تعریف‌شده برای یک Linked Server: EXEC sp_helplinkedsrvlogin 'BranchServer';
  • حذف همین mapping: EXEC sp_droplinkedsrvlogin 'BranchServer', 'report_user';
  • تست اینکه کوئری با کدام لاگین remote اجرا میشه: EXECUTE AS LOGIN = 'report_user'; SELECT * FROM [BranchServer].[SalesDB].[dbo].[Customers]; REVERT; (این باعث میشه session فعلی impersonate شه و mapping شبیه رفتار واقعی تست بشه.)

نکات امنیتی و عملی (خلاصه و مهم)

  • در سرور مقصد مطمئن شو branch_reader وجود داره و فقط مجوزهای لازم (مثلاً فقط SELECT روی جداول مشخص) بهش داده شده.
  • هرگز برای mapping از حساب‌های با امتیاز بالا مثل sa یا sysadmin استفاده نکن.
  • اگر می‌تونی از Windows Authentication + Kerberos delegation استفاده کن — امن‌تره چون رمز در اسکریپت ذخیره نمیشه. اما راه‌اندازی Kerberos نیاز به پیکربندی AD و delegation داره.
  • برای اعمال granular security بهتر است به جای دادن db_datareader کلی، فقط مجوز SELECT روی جداول/گزاره‌های مورد نیاز بده.
  • اجرای sp_addlinkedsrvlogin معمولاً نیاز به دسترسی مدیریتی (مثلاً sysadmin یا مالک linked server) داره — پس این کار رو با حسابی که حق مناسب داره انجام بده.

فرض کن دو تا سرور داری:

  • سرور اصلی: MainServer
  • سرور شعبه: BranchServer
    و روی سرور شعبه یک دیتابیس به اسم SalesDB داریم که جدول Customers داخلشه.

می‌خوای از سرور اصلی بتونی مستقیماً به اطلاعات SalesDB روی سرور شعبه دسترسی پیدا کنی.


🧩 مرحله ۱: ساخت Linked Server

اول باید به SQL Server بگی که “یه سرور دیگه به اسم BranchServer وجود داره”.

EXEC sp_addlinkedserver 
    @server = N'BranchServer',
    @srvproduct = N'SQL Server';

🟢 با این دستور، یک Linked Server به اسم BranchServer ساخته شد.
اما هنوز SQL Server نمی‌دونه با چه یوزری باید به اون وصل بشه.


🔐 مرحله ۲: ساخت یوزر امن روی سرور مقصد (BranchServer)

می‌ریم روی سرور شعبه (BranchServer) و یه یوزر با دسترسی محدود می‌سازیم:

-- در سطح سرور
CREATE LOGIN branch_reader WITH PASSWORD = 'StrongP@ss1';
GO

-- در دیتابیس SalesDB
USE SalesDB;
CREATE USER branch_reader FOR LOGIN branch_reader;
GO

-- فقط اجازه‌ی SELECT روی تمام جدول‌ها
EXEC sp_addrolemember 'db_datareader', 'branch_reader';
GO

✅ نتیجه:

  • branch_reader فقط می‌تونه دیتاها رو بخونه
  • نمی‌تونه چیزی حذف یا تغییر بده
  • نمی‌تونه به دیتابیس‌های دیگه دسترسی داشته باشه

⚙️ مرحله ۳: تعریف نحوه اتصال در سرور اصلی (MainServer)

حالا باید بگی:

وقتی از سرور اصلی (MainServer) وصل شدم به BranchServer، با چه کاربری لاگین کنم؟

اینجا از sp_addlinkedsrvlogin استفاده می‌کنیم:

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = NULL,  -- یعنی همه کاربران محلی از همین credential استفاده کنن
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

🟢 حالا هر کسی از سرور اصلی بخواد کوئری بزنه روی BranchServer،
SQL Server با یوزر branch_reader به اون سرور وصل میشه.


📊 مرحله ۴: تست ارتباط

الان می‌تونی از سرور اصلی بنویسی:

SELECT * FROM [BranchServer].[SalesDB].[dbo].[Customers];

✅ و نتیجه جدول از سرور شعبه نمایش داده میشه،
بدون اینکه نیاز باشه دستی فایل بفرستی یا import/export کنی.


🧠 مرحله ۵: امنیت و کنترل

  • فقط یوزر branch_reader به سرور مقصد وصل میشه.
  • اون یوزر فقط SELECT داره (امن).
  • هیچکس پسورد اصلی سرور شعبه (sa) رو نداره.
  • اگر بخوای، می‌تونی به جای @locallogin = NULL مشخص کنی فقط یک کاربر خاص در سرور اصلی حق استفاده از Linked Server رو داشته باشه.

🔁 خلاصه کل داستان:

مرحلهکاردستور اصلیتوضیح ساده
1معرفی سرور مقصدsp_addlinkedserverSQL Server بفهمه یه سرور دیگه وجود داره
2ساخت یوزر امن در سرور مقصدCREATE LOGIN / CREATE USERیه حساب بساز که فقط بتونه دیتا بخونه
3تنظیم ارتباط بین دو سرورsp_addlinkedsrvloginبگو با چه یوزر و رمزی به سرور مقصد وصل بشه
4استفاده از اونSELECT ... FROM [LinkedServer].[DB].[Schema].[Table]مثل دیتابیس خودت ازش استفاده کن

هدف

می‌خوای مثلاً فقط یه لاگین خاص (مثلاً report_user) بتونه از Linked Server استفاده کنه.
نه اینکه هر کاربری در سرور اصلی بتونه از همون branch_reader استفاده کنه.


⚙️ نحوه کار Linked Server از نظر امنیت

وقتی کاربری در سرور اصلی (MainServer) میاد کوئری میزنه:

SELECT * FROM [BranchServer].[SalesDB].[dbo].[Customers];

SQL Server این مسیر رو طی می‌کنه:

1️⃣ نگاه می‌کنه ببینه کاربر فعلی (مثلاً report_user) چه mapping ای برای BranchServer داره.
2️⃣ اگه پیدا کرد → با همون credential (rmtuser, rmtpassword) به سرور مقصد وصل میشه.
3️⃣ اگه پیدا نکرد → میره ببینه آیا mapping عمومی وجود داره (@locallogin = NULL) یا نه.
4️⃣ اگه اونم نبود → بسته به تنظیمات Linked Server ممکنه خطا بده یا از credential فعلی استفاده کنه.


🔐 سناریو ۱ — حالت عمومی (همه اجازه دارن)

وقتی اینطوری بنویسی:

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = NULL,  -- یعنی برای همه کاربران
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

✅ یعنی هر کاربری روی سرور اصلی (admin، developer، یا هر کس دیگه)
وقتی کوئری بزنه روی [BranchServer],
با یوزر branch_reader به اون سرور وصل میشه.

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


🧠 سناریو ۲ — محدود به کاربر خاص (حالت حرفه‌ای و امن‌تر)

فرض کن فقط report_user باید بتونه از Linked Server استفاده کنه.
در این صورت @locallogin رو مقداردهی می‌کنی:

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = 'report_user',
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

✅ حالا فقط کسی که با لاگین report_user به سرور اصلی وصل شده
می‌تونه از Linked Server استفاده کنه.

بقیه کاربرا وقتی کوئری بزنن مثلاً:

SELECT * FROM [BranchServer].[SalesDB].[dbo].[Customers];

🔴 با خطا روبه‌رو می‌شن مثل:

Login failed for user ''. The user is not associated with a trusted SQL Server connection.

⚖️ ترکیب هر دو حالت (برای چند یوزر خاص)

می‌تونی چند mapping جدا بسازی برای چند تا کاربر خاص:

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = 'report_user',
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = 'admin_user',
    @rmtuser = 'branch_admin',
    @rmtpassword = 'AdminP@ss2';

✅ نتیجه:

  • report_user → با branch_reader وصل میشه (فقط read)
  • admin_user → با branch_admin وصل میشه (مثلاً full access)
  • سایر کاربران → اصلاً اجازه اتصال ندارن

🔍 نکته امنیتی کلیدی

SQL Server همیشه ترتیب رو رعایت می‌کنه:

  1. اگر mapping خاص برای لاگین فعلی پیدا کنه → از اون استفاده می‌کنه.
  2. اگر نباشه ولی mapping عمومی (@locallogin = NULL) وجود داشته باشه → از اون استفاده می‌کنه.
  3. اگر هیچکدوم نباشه → اتصال رد میشه یا با context فعلی تلاش می‌کنه (که معمولاً شکست می‌خوره مگر delegation تنظیم شده باشه).

🧩 بررسی و حذف mappingها

می‌خوای ببینی الان چه mappingهایی برای یه Linked Server داری:

EXEC sp_helplinkedsrvlogin 'BranchServer';

می‌خوای یکی از mappingها رو حذف کنی:

EXEC sp_droplinkedsrvlogin 'BranchServer', 'report_user';

می‌خوای mapping عمومی رو حذف کنی (یعنی همه دسترسی عمومی رو از بین ببری):

EXEC sp_droplinkedsrvlogin 'BranchServer', NULL;

🧠 جمع‌بندی ساده:

حالتپارامتر @localloginنتیجه
برای همه کاربرانNULLهمه می‌تونن وصل شن با یوزر مشترک
برای یک کاربر خاص‘report_user’فقط اون کاربر وصل میشه
برای چند کاربر خاصچند بار اجرای دستور با نام‌های مختلفهرکدوم credential مخصوص خودشون دارن
برای هیچ‌کسهیچ mapping ندههیچ‌کس اجازه نداره

در مثال زیر:

EXEC sp_addlinkedsrvlogin  
    @rmtsrvname = 'BranchServer',
    @useself = 'false',
    @locallogin = 'report_user',
    @rmtuser = 'branch_reader',
    @rmtpassword = 'StrongP@ss1';

🔹 جواب کوتاه:
هیچ‌کدوم از کاربران معمولی (مثل report_user) نیازی ندارند پسورد 'StrongP@ss1' را بدانند یا وارد کنند.
چون این پسورد داخل تنظیمات Linked Server ذخیره می‌شود و SQL Server به‌صورت خودکار هنگام برقراری اتصال از آن استفاده می‌کند.


🧠 توضیح کامل:

وقتی شما با این دستور login mapping تعریف می‌کنی، داری به SQL Server می‌گی:

«هر وقت کاربر report_user از سرور اصلی (مثلاً MainServer) خواست از طریق Linked Server به BranchServer وصل شود،
SQL Server خودش به‌صورت خودکار با یوزر branch_reader و پسورد 'StrongP@ss1' برود به سرور مقصد.»

یعنی:

  • کاربر report_user در سرور اصلی فقط به Linked Server دسترسی دارد.
  • خودش مستقیماً با branch_reader لاگین نمی‌کند.
  • SQL Server پشت صحنه، authentication را انجام می‌دهد.

✅ نتیجه:

نقشتوضیح
report_userکاربر در سرور اصلی است، کوئری می‌زند. مثلاً: SELECT * FROM BranchServer.DBName.dbo.Table1
branch_readerکاربر در سرور مقصد (Linked Server) است، فقط برای خواندن داده‌ها تعریف شده.
StrongP@ss1پسوردی که فقط مدیر سیستم می‌داند، در تنظیمات Linked Server ذخیره می‌شود، کاربران آن را هرگز وارد نمی‌کنند.

🛡️ امنیت:

چون StrongP@ss1 در metadata (و رمزنگاری‌شده) نگهداری می‌شود، کاربران نباید هیچ‌وقت به آن دسترسی داشته باشند.
مدیر با دستور زیر می‌تونه ببیند چه mapping هایی وجود دارد:

EXEC sp_helplinkedsrvlogin 'BranchServer';

ولی پسورد را نمی‌تواند بخواند (فقط سیستم می‌داند).

Accept Cookies
Accept Cookies
[your-shortcode]