Firebase با اجازه دادن به شما برای اصالتسنجی کاربران یا دستگاهها بااستفاده از «نشانهای وب JSON» (JWT) امن، کنترل کامل اصالتسنجی را به شما میدهد. این نشانها را در سرورتان تولید میکنید، آنها را به دستگاه کارخواه برمیگردانید، و سپس از آنها برای اصالتسنجی ازطریق روش signInWithCustomToken() استفاده میکنید.
برای دستیابی به این هدف، باید نقطه پایانی سروری ایجاد کنید که اطلاعات اعتباری ورود به سیستم—مثل نام کاربری و گذرواژه—را بپذیرد و، درصورت معتبر بودن اطلاعات اعتباری، «توکن وب JSON» سفارشی برگرداند. سپس دستگاه مشتری میتواند از JWT سفارشی برگشتی از سرورتان برای اصالتسنجی با Firebase (iOS+، Android، وب) استفاده کند. پساز اصالتسنجی، این هویت هنگام دسترسی به دیگر سرویسهای Firebase، مثل Firebase Realtime Database و Cloud Storage، استفاده خواهد شد. علاوهبراین، محتوای JWT در
شیء auth در
Realtime Database Security Rules و شیء
request.auth در
Cloud Storage Security Rules دردسترس خواهد بود.
میتوانید با «کیت توسعه نرمافزار Firebase Admin» کد سفارشی ایجاد کنید، یا اگر سرورتان به زبانی نوشته شده است که Firebase بهطور بومی از آن پشتیبانی نمیکند، میتوانید از کتابخانه JWT طرف سوم استفاده کنید.
قبل از شروع
نشانهای سفارشی JWT امضاشدهای هستند که کلید خصوصی استفادهشده برای امضا متعلق به حساب سرویس Google است. چندین روش برای مشخص کردن حساب سرویس Google وجود دارد که باید توسط Firebase Admin SDK برای امضای نشانهای سفارشی استفاده شود:
- استفاده از فایل JSON حساب سرویس -- این روش را میتوان در هر محیطی استفاده کرد، اما لازم است فایل JSON حساب سرویس را همراه با کدتان بستهبندی کنید. باید دقت ویژهای شود تا فایل JSON حساب خدمات درمعرض طرفهای خارجی قرار نگیرد.
- اجازه دادن به «کیت توسعه نرمافزار سرپرست» برای شناسایی حساب سرویس -- این روش میتواند در محیطهای مدیریتشده توسط Google مثل «توابع Google Cloud» و App Engine استفاده شود. ممکن است لازم باشد برخی اجازههای اضافی را ازطریق کنسول Google Cloud پیکربندی کنید.
- استفاده از شناسه حساب سرویس -- وقتی در محیط مدیریتشده Google استفاده شود، این روش نشانهها را بااستفاده از کلید حساب سرویس مشخصشده امضا میکند. بااینحال، از سرویس وب از دور استفاده میکند و ممکن است لازم باشد اجازههای اضافی را برای این حساب خدمات ازطریق کنسول Google Cloud پیکربندی کنید.
استفاده از فایل JSON حساب سرویس
فایلهای JSON حساب سرویس حاوی همه اطلاعات مربوط به حسابهای سرویس (ازجمله کلید خصوصی RSA) است. میتوانید آنها را از کنسول Firebase بارگیری کنید. برای کسب اطلاعات بیشتر درباره نحوه مقداردهی اولیه «کیت توسعه نرمافزار مدیر» با فایل JSON حساب سرویس، دستورالعملهای راهاندازی «کیت توسعه نرمافزار مدیر» را دنبال کنید.
این روش مقداردهی اولیه برای طیف گستردهای از استقرارهای «کیت توسعه نرمافزار سرپرست» مناسب است. همچنین «کیت توسعه نرمافزار سرپرست» را قادر میسازد تا بدون برقراری تماس با «میانای برنامهسازی کاربردی» ازراهدور، بهصورت محلی نشانهای سفارشی ایجاد و امضا کند. نقطه ضعف اصلی این رویکرد این است که شما را ملزم میکند فایل JSON حساب سرویس را همراه با کدتان بستهبندی کنید. همچنین توجه داشته باشید که کلید خصوصی در فایل JSON حساب سرویس اطلاعات حساس است و باید مراقب باشید که محرمانه بماند. بهطور خاص، از افزودن فایلهای JSON حساب سرویس به کنترل نسخه عمومی خودداری کنید.
اجازه دادن به «کیت توسعه نرمافزار سرپرست» برای شناسایی حساب سرویس
اگر کد شما در محیطی که Google مدیریت میکند مستقر شده باشد، «کیت توسعه نرمافزار سرپرست» میتواند تلاش کند روشی برای امضای نشانهای سفارشی بهطور خودکار پیدا کند:
اگر کدتان در محیط استاندارد App Engine برای Java، Python، یا Go مستقر شده باشد، «کیت توسعه نرمافزار سرپرست» میتواند از سرویس «هویت برنامه» موجود در آن محیط برای امضای نشانهای سفارشی استفاده کند. سرویس «هویت برنامه» دادهها را بااستفاده از حساب سرویسی که Google App Engine برای برنامه شما تدارک دیده است امضا میکند.
اگر کد شما در محیط مدیریتشده دیگری (برای نمونه، Google Cloud Functions، Google Compute Engine) مستقر شده باشد، «کیت توسعه نرمافزار مدیریت Firebase» میتواند رشته شناسه حساب سرویس را بهطور خودکار از سرور فراداده محلی شناسایی کند. سپس از شناسه حساب سرویس شناساییشده بههمراه سرویس IAM برای امضای از دور نشانها استفاده میشود.
برای استفاده از این روشهای امضا، «کیت توسعه نرمافزار» را با اطلاعات اعتباری پیشفرض «برنامه Google» مقداردهی اولیه کنید و رشته شناسه حساب سرویس را مشخص نکنید:
Node.js
initializeApp();
جاوا
FirebaseApp.initializeApp();
پایتون
default_app = firebase_admin.initialize_app()
رفتن
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
سی شارپ
FirebaseApp.Create();
برای آزمایش کردن همان کد بهصورت محلی، فایل JSON حساب خدمات را بارگیری کنید و متغیر محیطی GOOGLE_APPLICATION_CREDENTIALS را تنظیم کنید تا به آن اشاره کند.
اگر «کیت توسعه نرمافزار Firebase Admin» مجبور باشد رشته شناسه حساب سرویس را پیدا کند، این کار را زمانی انجام میدهد که کد شما برای اولینبار یک کد سفارشی ایجاد میکند. نتیجه در حافظه نهان ذخیره میشود و برای عملیات امضای رمز بعدی استفاده میشود. شناسه حساب سرویس خودکارکشفشده معمولاً یکی از حسابهای سرویس پیشفرض ارائهشده توسط Google Cloud است:
همانند شناسههای حساب سرویس که بهطور صریح مشخص شدهاند، شناسههای حساب سرویس که بهطور خودکار شناسایی شدهاند نیز باید اجازه iam.serviceAccounts.signBlob را داشته باشند تا ایجاد رمز سفارشی کار کند. برای اعطای اجازههای لازم به حسابهای خدمات پیشفرض، ممکن است لازم باشد از بخش
IAM و سرپرست
کنسول Google Cloud استفاده کنید. برای جزئیات بیشتر، بخش عیبیابی در زیر را ببینید.
استفاده از شناسه حساب سرویس
برای حفظ یکپارچگی بین بخشهای مختلف برنامه، میتوانید شناسه حساب خدماتی را مشخص کنید که کلیدهای آن برای امضای نشانهها هنگام اجرا در محیط مدیریتشده Google استفاده خواهد شد. این کار میتواند خطمشیهای IAM را سادهتر و ایمنتر کند و از نیاز به افزودن فایل JSON حساب سرویس در کدتان جلوگیری کند.
شناسه حساب سرویس را میتوانید در کنسول
Google Cloud،
یا در فیلد client_email فایل JSON حساب سرویس بارگیریشده پیدا کنید.
شناسههای حساب سرویس نشانیهای ایمیلی هستند که قالب زیر را دارند:
<client-id>@<project-id>.iam.gserviceaccount.com. این شناسهها حسابهای
سرویس را در Firebase و Google Cloud پروژه بهطور منحصربهفرد شناسایی میکنند.
برای ایجاد نشانهای سفارشی بااستفاده از شناسه حساب خدمات جداگانه، «کیت توسعه نرمافزار» را همانطور که در زیر نشان داده شده است مقداردهی اولیه کنید:
Node.js
initializeApp({
serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});
جاوا
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
.build();
FirebaseApp.initializeApp(options);
پایتون
options = {
'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)
رفتن
conf := &firebase.Config{
ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
سی شارپ
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.GetApplicationDefault(),
ServiceAccountId = "my-client-id@my-project-id.iam.gserviceaccount.com",
});
شناسههای حساب سرویس اطلاعات حساس نیستند و بنابراین آشکار شدن آنها
بیاهمیت است. بااینحال، برای امضا کردن نشانهای سفارشی با حساب سرویس مشخصشده، «کیت توسعه نرمافزار Firebase Admin» باید سرویس ازراهدوری را فراخوانی کند.
علاوهبراین، باید مطمئن شوید که حساب خدماتی که «کیت توسعه نرمافزار سرپرست» برای انجام این تماس استفاده میکند
—معمولاً {project-name}@appspot.gserviceaccount.com—
iam.serviceAccounts.signBlob
اجازه را داشته باشد.
برای جزئیات بیشتر، بخش عیبیابی در زیر را ببینید.
ایجاد نشانهای سفارشی بااستفاده از Firebase Admin SDK
«کیت توسعه نرمافزار Firebase Admin» روشی داخلی برای ایجاد نشانهای سفارشی دارد. حداقل باید uid ارائه دهید که میتواند هر رشتهای باشد اما باید کاربر یا دستگاهی را که اصالتسنجی میکنید بهصورت منحصربهفرد شناسایی کند. این نشانها پساز یک ساعت منقضی میشوند.
Node.js
const uid = 'some-uid';
getAuth()
.createCustomToken(uid)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
جاوا
String uid = "some-uid";
String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client
پایتون
uid = 'some-uid'
custom_token = auth.create_custom_token(uid)
رفتن
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.CustomToken(ctx, "some-uid")
if err != nil {
log.Fatalf("error minting custom token: %v\n", err)
}
log.Printf("Got custom token: %v\n", token)
سی شارپ
var uid = "some-uid";
string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client
همچنین میتوانید بهصورت اختیاری ادعاهای اضافی را برای گنجاندن در
رمز سفارشی مشخص کنید. برای مثال، در زیر، فیلد premiumAccount به
رمز سفارشی اضافه شده است که در auth / request.auth شیء
در «قوانین امنیتی» شما دردسترس خواهد بود:
Node.js
const userId = 'some-uid';
const additionalClaims = {
premiumAccount: true,
};
getAuth()
.createCustomToken(userId, additionalClaims)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
جاوا
String uid = "some-uid";
Map<String, Object> additionalClaims = new HashMap<String, Object>();
additionalClaims.put("premiumAccount", true);
String customToken = FirebaseAuth.getInstance()
.createCustomToken(uid, additionalClaims);
// Send token back to client
پایتون
uid = 'some-uid'
additional_claims = {
'premiumAccount': True
}
custom_token = auth.create_custom_token(uid, additional_claims)
رفتن
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
claims := map[string]interface{}{
"premiumAccount": true,
}
token, err := client.CustomTokenWithClaims(ctx, "some-uid", claims)
if err != nil {
log.Fatalf("error minting custom token: %v\n", err)
}
log.Printf("Got custom token: %v\n", token)
سی شارپ
var uid = "some-uid";
var additionalClaims = new Dictionary<string, object>()
{
{ "premiumAccount", true },
};
string customToken = await FirebaseAuth.DefaultInstance
.CreateCustomTokenAsync(uid, additionalClaims);
// Send token back to client
نامهای دادهواحد سفارشی رزروشده
ورود به سیستم بااستفاده از نشانهای سفارشی در کارخواهها
پساز ایجاد یک کد سفارشی، باید آن را به برنامه مشتری خود ارسال کنید. برنامه مشتری با فراخوانی signInWithCustomToken() با کد سفارشی اصالتسنجی میکند:
iOS+
Objective-C
[[FIRAuth auth] signInWithCustomToken:customToken
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
// ...
}];
Swift
Auth.auth().signIn(withCustomToken: customToken ?? "") { user, error in
// ...
}
Android
mAuth.signInWithCustomToken(mCustomToken)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCustomToken:success");
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCustomToken:failure", task.getException());
Toast.makeText(CustomAuthActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
}
});
Unity
auth.SignInWithCustomTokenAsync(custom_token).ContinueWith(task => {
if (task.IsCanceled) {
Debug.LogError("SignInWithCustomTokenAsync was canceled.");
return;
}
if (task.IsFaulted) {
Debug.LogError("SignInWithCustomTokenAsync encountered an error: " + task.Exception);
return;
}
Firebase.Auth.AuthResult result = task.Result;
Debug.LogFormat("User signed in successfully: {0} ({1})",
result.User.DisplayName, result.User.UserId);
});
C++
firebase::Future<firebase::auth::AuthResult> result =
auth->SignInWithCustomToken(custom_token);
Web
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
Web
import { getAuth, signInWithCustomToken } from "firebase/auth";
const auth = getAuth();
signInWithCustomToken(auth, token)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// ...
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ...
});
اگر اصالتسنجی موفقیتآمیز باشد، کاربر شما اکنون با حساب مشخصشده توسط uid موجود در
رمز سفارشی به سیستم برنامه مشتری وارد خواهد شد. اگر آن حساب قبلاً وجود نداشته باشد، گزارشی برای آن کاربر ایجاد خواهد شد.
بههمان روشی که در دیگر روشهای ورود به سیستم (مثل
signInWithEmailAndPassword() و signInWithCredential()) استفاده میشود،
شیء auth در
Realtime Database Security Rules و
شیء request.auth در
Cloud Storage Security Rules با uid کاربر پر خواهد شد. در این مورد، uid همان چیزی خواهد بود که
هنگام تولید کد سفارشی مشخص کردهاید.
قوانین پایگاه داده
{
"rules": {
"adminContent": {
".read": "auth.uid === 'some-uid'"
}
}
}
قوانین فضای ذخیرهسازی
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /adminContent/{filename} {
allow read, write: if request.auth != null && request.auth.uid == "some-uid";
}
}
}
اگر نشان سفارشی حاوی ادعاهای اضافی باشد، میتوان آنها را از
شیء auth.token (Firebase Realtime Database) یا request.auth.token
(Cloud Storage) در قوانین شما ارجاع داد:
قوانین پایگاه داده
{
"rules": {
"premiumContent": {
".read": "auth.token.premiumAccount === true"
}
}
}
قوانین فضای ذخیرهسازی
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /premiumContent/{filename} {
allow read, write: if request.auth.token.premiumAccount == true;
}
}
}
ایجاد نشانهای سفارشی بااستفاده از کتابخانه JWT طرف سوم
اگر زیرینه شما به زبانی است که «کیت توسعه نرمافزار Firebase Admin» رسمی ندارد، همچنان میتوانید نشانهای سفارشی را بهصورت دستی ایجاد کنید. ابتدا، کتابخانه JWT طرف سومی را برای زبان خود پیدا کنید. سپس، از آن کتابخانه JWT برای ایجاد یک JWT استفاده کنید که شامل ادعاهای زیر باشد:
| ادعاهای دادهواحد سفارشی | ||
|---|---|---|
alg |
الگوریتم | "RS256" |
iss |
صادرکننده | نشانی ایمیل حساب خدمات پروژه شما |
sub |
موضوع | نشانی ایمیل حساب خدمات پروژه شما |
aud |
مخاطبان | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
زمان صدور | زمان فعلی، برحسب ثانیه از تاریخ یونیکس |
exp |
زمان انقضا |
زمان انقضای دادهواحد برحسب ثانیه از تاریخ یونیکس. میتواند حداکثر ۳۶۰۰ ثانیه بعداز iat باشد.
توجه: این فقط زمان انقضای خود رمز سفارشی را کنترل میکند. اما وقتی کاربری را بااستفاده از signInWithCustomToken() به سیستم وارد میکنید، تا زمانی که جلسه او نامعتبر شود یا کاربر از سیستم خارج شود،
در دستگاه به سیستم وارد خواهد ماند.
|
uid |
شناسه متمایز کاربر واردشده باید رشتهای بین ۱ تا ۱۲۸ نویسه باشد. uidهای کوتاهتر عملکرد
بهتری ارائه میدهند.
|
|
claims (اختیاری) |
ادعاهای سفارشی اختیاری برای افزودن به متغیرهای «قوانین امنیتی» auth /
request.auth
|
|
در اینجا چند نمونه پیادهسازی از نحوه ایجاد نشانهای سفارشی در زبانهای مختلف که «کیت توسعه نرمافزار Firebase Admin» از آنها پشتیبانی نمیکند آورده شده است:
PHP
استفاده از php-jwt:
// Requires: composer require firebase/php-jwt
use Firebase\JWT\JWT;
// Get your service account's email address and private key from the JSON key file
$service_account_email = "abc-123@a-b-c-123.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----...";
function create_custom_token($uid, $is_premium_account) {
global $service_account_email, $private_key;
$now_seconds = time();
$payload = array(
"iss" => $service_account_email,
"sub" => $service_account_email,
"aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat" => $now_seconds,
"exp" => $now_seconds+(60*60), // Maximum expiration time is one hour
"uid" => $uid,
"claims" => array(
"premium_account" => $is_premium_account
)
);
return JWT::encode($payload, $private_key, "RS256");
}
Ruby
استفاده از ruby-jwt:
require "jwt"
# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."
def create_custom_token(uid, is_premium_account)
now_seconds = Time.now.to_i
payload = {:iss => $service_account_email,
:sub => $service_account_email,
:aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
:iat => now_seconds,
:exp => now_seconds+(60*60), # Maximum expiration time is one hour
:uid => uid,
:claims => {:premium_account => is_premium_account}}
JWT.encode payload, $private_key, "RS256"
end
پساز ایجاد کردن کد سفارشی، آن را به برنامه کارخواهتان ارسال کنید تا از آن برای اصالتسنجی با Firebase استفاده کند. برای نحوه انجام این کار، نمونههای کد بالا را ببینید.
عیبیابی
این بخش برخیاز مشکلات رایجی را که توسعهدهندگان ممکن است هنگام ایجاد نشانهای سفارشی با آنها مواجه شوند و نحوه حلوفصل کردن آنها را شرح میدهد.
IAM API فعال نیست
اگر شناسه حساب سرویس را برای امضای نشانها مشخص میکنید، ممکن است خطایی مشابه خطای زیر دریافت کنید:
Identity and Access Management (IAM) API has not been used in project 1234567890 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
«کیت توسعه نرمافزار Firebase Admin» از IAM API برای امضا کردن کدها استفاده میکند. این خطا نشان میدهد که درحالحاضر IAM API برای پروژه Firebase شما فعال نیست. پیوند موجود در پیام خطا را در مرورگر وب باز کنید و روی دکمه «فعال کردن میانای برنامهسازی کاربردی» کلیک کنید تا آن را برای پروژهتان فعال کنید.
حساب سرویس اجازههای لازم را ندارد
اگر حساب خدماتی که «کیت توسعه نرمافزار Firebase Admin» با آن اجرا میشود اجازه iam.serviceAccounts.signBlob را نداشته باشد، ممکن است پیام خطایی مانند پیام زیر دریافت کنید:
Permission iam.serviceAccounts.signBlob is required to perform this operation
on service account projects/-/serviceAccounts/{your-service-account-id}.
برای حل این مشکل، نقش IAM Service Account Token Creator (سازنده کد حساب سرویس) را به حساب سرویس مناسب اعطا کنید. حساب سرویس پیشفرض مورداستفاده به محیط و نسخه Cloud Functions بستگی دارد:
- Cloud Functions (نسل اول): از حساب خدمات پیشفرض App Engine استفاده میکند.
- Cloud Functions (نسل دوم): از حساب سرویس پیشفرض Compute Engine استفاده میکند.
در کنسول Google Cloud، به IAM و سرپرست بروید.
روی نماد ویرایش مربوط به حساب خدماتی که میخواهید بهروز کنید کلیک کنید.
روی افزودن نقش دیگر کلیک کنید.
"Service Account Token Creator"را در فیلتر جستجو تایپ کنید و آن را از نتایج انتخاب کنید.برای تأیید اعطای نقش، روی ذخیره کلیک کنید.
برای جزئیات بیشتر درباره این فرایند، به مستندات IAM مراجعه کنید، یا بااستفاده از ابزارهای خط فرمان gcloud، نحوه بهروزرسانی نقشها را یاد بگیرید.
حساب سرویس تشخیص داده نشد
اگر پیام خطایی مشابه پیام زیر دریافت کردید، «کیت توسعه نرمافزار مدیر Firebase» بهدرستی مقداردهی اولیه نشده است.
Failed to determine service account ID. Initialize the SDK with service account credentials or specify a service account ID with iam.serviceAccounts.signBlob permission.
اگر برای شناسایی خودکار شناسه حساب سرویس به کیت توسعه نرمافزار متکی هستید، مطمئن شوید کد در محیط مدیریتشده Google با سرور فراداده مستقر شده باشد. درغیراینصورت، حتماً فایل JSON حساب سرویس یا شناسه حساب سرویس را در مقداردهی اولیه کیت توسعه نرمافزار مشخص کنید.