Google is committed to advancing racial equity for Black communities. See how.
ترجمت واجهة Cloud Translation API‏ هذه الصفحة.
Switch to English

التحكم في الوصول مع المطالبات المخصصة وقواعد الأمن

تدعم حزمة Firebase Admin SDK تحديد السمات المخصصة في حسابات المستخدمين. وهذا يوفر القدرة على تنفيذ العديد من استراتيجيات التحكم في الوصول ، بما في ذلك التحكم في الوصول المستند إلى الدور ، في تطبيقات Firebase. يمكن أن تمنح هذه السمات المخصصة المستخدمين مستويات مختلفة من الوصول (الأدوار) ، والتي يتم فرضها في قواعد أمان التطبيق.

يمكن تحديد أدوار المستخدم للحالات الشائعة التالية:

  • منح المستخدم امتيازات إدارية للوصول إلى البيانات والموارد.
  • تحديد المجموعات المختلفة التي ينتمي إليها المستخدم.
  • توفير وصول متعدد المستويات:
    • تمييز المشتركين المدفوعين / غير المدفوعين.
    • تمييز المشرفين عن المستخدمين العاديين.
    • تطبيق المعلم / الطالب ، إلخ.
  • قم بإضافة معرف إضافي على المستخدم. على سبيل المثال ، يمكن تعيين مستخدم Firebase إلى UID مختلف في نظام آخر.

دعنا نفكر في حالة تريد فيها تقييد الوصول إلى عقدة قاعدة البيانات "adminContent". يمكنك القيام بذلك من خلال البحث في قاعدة البيانات في قائمة المستخدمين المسؤولين. ومع ذلك ، يمكنك تحقيق نفس الهدف بشكل أكثر كفاءة باستخدام مطالبة مستخدم مخصصة باسم admin مع قاعدة Realtime Database التالية:

 {
  "rules": {
    "adminContent": {
      ".read": "auth.token.admin === true",
      ".write": "auth.token.admin === true",
    }
  }
}
 

يمكن الوصول إلى مطالبات المستخدم المخصصة عبر الرموز المميزة للمصادقة الخاصة بالمستخدم. في المثال أعلاه ، لن يتمكن من الوصول إلى قراءة / كتابة عقدة adminContent إلا للمستخدمين الذين تم تعيين admin على "صحيح" في مطالبة الرمز المميز. نظرًا لأن الرمز المميز للمعرف يحتوي بالفعل على هذه التأكيدات ، فلا حاجة إلى معالجة أو بحث إضافي للتحقق من أذونات المشرف. بالإضافة إلى ذلك ، الرمز المميز للمعرف هو آلية موثوقة لتقديم هذه المطالبات المخصصة. يجب أن تتحقق جميع عمليات الدخول المصادق من الرمز المميز للمعرف قبل معالجة الطلب المرتبط.

تستمد الأمثلة والحلول البرمجية الموضحة في هذه الصفحة من واجهات برمجة تطبيقات Firebase Auth API من جانب العميل وواجهات برمجة تطبيقات Auth من جانب الخادم المقدمة من Admin SDK .

قم بتعيين والتحقق من مطالبات المستخدم المخصصة عبر Admin SDK

يمكن أن تحتوي المطالبات المخصصة على بيانات حساسة ، لذلك يجب تعيينها فقط من بيئة خادم مميزة بواسطة Firebase Admin SDK.

Node.js

 // Set admin privilege on the user corresponding to uid.

admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
  // The new custom claims will propagate to the user's ID token the
  // next time a new one is issued.
});
 

جافا

 // Set admin privilege on the user corresponding to uid.
Map<String, Object> claims = new HashMap<>();
claims.put("admin", true);
FirebaseAuth.getInstance().setCustomUserClaims(uid, claims);
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
 

بيثون

 # Set admin privilege on the user corresponding to uid.
auth.set_custom_user_claims(uid, {'admin': True})
# The new custom claims will propagate to the user's ID token the
# next time a new one is issued.
 

اذهب

 // Get an auth client from the firebase.App
client, err := app.Auth(ctx)
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

// Set admin privilege on the user corresponding to uid.
claims := map[string]interface{}{"admin": true}
err = client.SetCustomUserClaims(ctx, uid, claims)
if err != nil {
	log.Fatalf("error setting custom claims %v\n", err)
}
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued. 

ج #

 // Set admin privileges on the user corresponding to uid.
var claims = new Dictionary<string, object>()
{
    { "admin", true },
};
await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(uid, claims);
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
 

يجب ألا يحتوي كائن المطالبات المخصصة على أي أسماء مفاتيح محجوزة لـ OIDC أو أسماء محجوزة في Firebase . يجب ألا تتجاوز حمولة المطالبات المخصصة 1000 بايت.

يمكن لرمز معرف تم إرساله إلى خادم الواجهة الخلفية تأكيد هوية المستخدم ومستوى الوصول باستخدام Admin SDK على النحو التالي:

Node.js

 // Verify the ID token first.
admin.auth().verifyIdToken(idToken).then((claims) => {
  if (claims.admin === true) {
    // Allow access to requested admin resource.
  }
});
 

جافا

 // Verify the ID token first.
FirebaseToken decoded = FirebaseAuth.getInstance().verifyIdToken(idToken);
if (Boolean.TRUE.equals(decoded.getClaims().get("admin"))) {
  // Allow access to requested admin resource.
}
 

بيثون

 # Verify the ID token first.
claims = auth.verify_id_token(id_token)
if claims['admin'] is True:
    # Allow access to requested admin resource.
    pass
 

اذهب

 // Verify the ID token first.
token, err := client.VerifyIDToken(ctx, idToken)
if err != nil {
	log.Fatal(err)
}

claims := token.Claims
if admin, ok := claims["admin"]; ok {
	if admin.(bool) {
		//Allow access to requested admin resource.
	}
} 

ج #

 // Verify the ID token first.
FirebaseToken decoded = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
object isAdmin;
if (decoded.Claims.TryGetValue("admin", out isAdmin))
{
    if ((bool)isAdmin)
    {
        // Allow access to requested admin resource.
    }
}

 

يمكنك أيضًا التحقق من المطالبات المخصصة الحالية للمستخدم ، والتي تتوفر كموقع على كائن المستخدم:

Node.js

 // Lookup the user associated with the specified uid.
admin.auth().getUser(uid).then((userRecord) => {
  // The claims can be accessed on the user record.
  console.log(userRecord.customClaims['admin']);
});
 

جافا

 // Lookup the user associated with the specified uid.
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
System.out.println(user.getCustomClaims().get("admin"));
 

بيثون

 # Lookup the user associated with the specified uid.
user = auth.get_user(uid)
# The claims can be accessed on the user record.
print(user.custom_claims.get('admin'))
 

اذهب

 // Lookup the user associated with the specified uid.
user, err := client.GetUser(ctx, uid)
if err != nil {
	log.Fatal(err)
}
// The claims can be accessed on the user record.
if admin, ok := user.CustomClaims["admin"]; ok {
	if admin.(bool) {
		log.Println(admin)
	}
} 

ج #

 // Lookup the user associated with the specified uid.
UserRecord user = await FirebaseAuth.DefaultInstance.GetUserAsync(uid);
Console.WriteLine(user.CustomClaims["admin"]);
 

يمكنك حذف المطالبات مخصصة للمستخدم عن طريق تمرير فارغة لل customClaims .

نشر المطالبات المخصصة للعميل

بعد تعديل المطالبات الجديدة على مستخدم عبر Admin SDK ، يتم نشرها إلى مستخدم مصدق عليه من جانب العميل عبر رمز المعرف بالطرق التالية:

  • يقوم المستخدم بتسجيل الدخول أو إعادة المصادقة بعد تعديل المطالبات المخصصة. سيحتوي الرمز المميز الصادر نتيجة لذلك على أحدث المطالبات.
  • يتم تجديد رمز المستخدم الحالي لجلسة المستخدم الحالية بعد انتهاء صلاحية رمز أقدم.
  • يتم تحديث الرمز المميز للمعرف من خلال استدعاء currentUser.getIdToken(true) .

الوصول إلى المطالبات المخصصة على العميل

لا يمكن استرداد المطالبات المخصصة إلا من خلال رمز معرف المستخدم. قد يكون الوصول إلى هذه المطالبات ضروريًا لتعديل واجهة مستخدم العميل بناءً على دور المستخدم أو مستوى الوصول. ومع ذلك ، يجب دائمًا فرض الوصول إلى الواجهة الخلفية من خلال الرمز المميز للمعرف بعد التحقق من الصحة وتحليل مطالباته. لا يجب إرسال المطالبات المخصصة مباشرة إلى الواجهة الخلفية ، حيث لا يمكن الوثوق بها خارج الرمز المميز.

بمجرد نشر أحدث المطالبات على رمز معرف المستخدم ، يمكنك الحصول عليها عن طريق استرداد رمز الهوية:

جافا سكريبت

 firebase.auth().currentUser.getIdTokenResult()
  .then((idTokenResult) => {
     // Confirm the user is an Admin.
     if (!!idTokenResult.claims.admin) {
       // Show admin UI.
       showAdminUI();
     } else {
       // Show regular user UI.
       showRegularUI();
     }
  })
  .catch((error) => {
    console.log(error);
  });
 

ذكري المظهر

 user.getIdToken(false).addOnSuccessListener(new OnSuccessListener<GetTokenResult>() {
  @Override
  public void onSuccess(GetTokenResult result) {
    boolean isAdmin = result.getClaims().get("admin");
    if (isAdmin) {
      // Show admin UI.
      showAdminUI();
    } else {
      // Show regular user UI.
      showRegularUI();
    }
  }
});
 

سويفت

 user.getIDTokenResult(completion: { (result, error) in
  guard let admin = result?.claims?["admin"] as? NSNumber else {
    // Show regular user UI.
    showRegularUI()
    return
  }
  if admin.boolValue {
    // Show admin UI.
    showAdminUI()
  } else {
    // Show regular user UI.
    showRegularUI()
  }
})
 

ج موضوعية

 user.getIDTokenResultWithCompletion:^(FIRAuthTokenResult *result,
                                      NSError *error) {
  if (error != nil) {
    BOOL *admin = [result.claims[@"admin"] boolValue];
    if (admin) {
      // Show admin UI.
      [self showAdminUI];
    } else {
      // Show regular user UI.
      [self showRegularUI];
    }
  }
}];
 

أفضل الممارسات للمطالبات المخصصة

تستخدم المطالبات المخصصة فقط لتوفير التحكم في الوصول. لم يتم تصميمها لتخزين بيانات إضافية (مثل ملف التعريف والبيانات المخصصة الأخرى). على الرغم من أن هذا قد يبدو كآلية ملائمة للقيام بذلك ، إلا أنه لا يُنصح به بشدة حيث يتم تخزين هذه المطالبات في الرمز المميز للمعرف ويمكن أن يسبب مشاكل في الأداء لأن جميع الطلبات المصادق عليها تحتوي دائمًا على رمز معرف Firebase مطابق للمستخدم الذي قام بتسجيل الدخول.

  • استخدم المطالبات المخصصة لتخزين البيانات للتحكم في وصول المستخدم فقط. يجب تخزين جميع البيانات الأخرى بشكل منفصل عبر قاعدة بيانات الوقت الحقيقي أو التخزين الآخر من جانب الخادم.
  • المطالبات المخصصة محدودة الحجم. سيؤدي تمرير حمولة مطالبات مخصصة أكبر من 1000 بايت إلى حدوث خطأ.

أمثلة وحالات استخدام

توضح الأمثلة التالية الادعاءات المخصصة في سياق حالات استخدام Firebase محددة.

تحديد الأدوار عبر وظائف Firebase عند إنشاء المستخدم

في هذا المثال ، يتم تعيين المطالبات المخصصة على المستخدم عند الإنشاء باستخدام وظائف السحاب.

يمكن إضافة المطالبات المخصصة باستخدام وظائف السحاب ، ونشرها على الفور مع قاعدة بيانات Realtime. يتم استدعاء الوظيفة فقط عند التسجيل باستخدام مشغل onCreate . بمجرد تعيين المطالبات المخصصة ، يتم نشرها على جميع الجلسات الحالية والمستقبلية. في المرة التالية التي يقوم فيها المستخدم بتسجيل الدخول باستخدام بيانات اعتماد المستخدم ، يحتوي الرمز المميز على المطالبات المخصصة.

التنفيذ من جانب العميل (JavaScript)

 const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
.catch(error => {
  console.log(error);
});

let callback = null;
let metadataRef = null;
firebase.auth().onAuthStateChanged(user => {
  // Remove previous listener.
  if (callback) {
    metadataRef.off('value', callback);
  }
  // On user login add new listener.
  if (user) {
    // Check if refresh is required.
    metadataRef = firebase.database().ref('metadata/' + user.uid + '/refreshTime');
    callback = (snapshot) => {
      // Force refresh to pick up the latest custom claims changes.
      // Note this is always triggered on first call. Further optimization could be
      // added to avoid the initial trigger when the token is issued and already contains
      // the latest claims.
      user.getIdToken(true);
    };
    // Subscribe new listener to changes on that node.
    metadataRef.on('value', callback);
  }
});
 

منطق وظائف السحاب

تمت إضافة عقدة قاعدة بيانات جديدة (البيانات الوصفية / ($ uid)} مع قراءة / كتابة مقيدة للمستخدم المصادق عليه.

 const functions = require('firebase-functions');

const admin = require('firebase-admin');
admin.initializeApp();

// On sign up.
exports.processSignUp = functions.auth.user().onCreate((user) => {
  // Check if user meets role criteria.
  if (user.email &&
      user.email.endsWith('@admin.example.com') &&
      user.emailVerified) {
    const customClaims = {
      admin: true,
      accessLevel: 9
    };
    // Set custom user claims on this newly created user.
    return admin.auth().setCustomUserClaims(user.uid, customClaims)
      .then(() => {
        // Update real-time database to notify client to force refresh.
        const metadataRef = admin.database().ref("metadata/" + user.uid);
        // Set the refresh time to the current UTC timestamp.
        // This will be captured on the client to force a token refresh.
        return metadataRef.set({refreshTime: new Date().getTime()});
      })
      .catch(error => {
        console.log(error);
      });
  }
});
 

قواعد قاعدة البيانات

 {
  "rules": {
    "metadata": {
      "$user_id": {
        // Read access only granted to the authenticated user.
        ".read": "$user_id === auth.uid",
        // Write access only via Admin SDK.
        ".write": false
      }
    }
  }
}
 

تحديد الأدوار عبر طلب HTTP

يعين المثال التالي مطالبات مستخدم مخصصة على مستخدم تم تسجيل دخوله حديثًا عبر طلب HTTP.

التنفيذ من جانب العميل (JavaScript)

 const provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
.then((result) => {
  // User is signed in. Get the ID token.
  return result.user.getIdToken();
})
.then((idToken) => {
  // Pass the ID token to the server.
  $.post(
    '/setCustomClaims',
    {
      idToken: idToken
    },
    (data, status) => {
      // This is not required. You could just wait until the token is expired
      // and it proactively refreshes.
      if (status == 'success' && data) {
        const json = JSON.parse(data);
        if (json && json.status == 'success') {
          // Force token refresh. The token claims will contain the additional claims.
          firebase.auth().currentUser.getIdToken(true);
        }
      }
    });
}).catch((error) => {
  console.log(error);
});
 

تنفيذ الخلفية (Admin SDK)

 app.post('/setCustomClaims', (req, res) => {
  // Get the ID token passed.
  const idToken = req.body.idToken;
  // Verify the ID token and decode its payload.
  admin.auth().verifyIdToken(idToken).then((claims) => {
    // Verify user is eligible for additional privileges.
    if (typeof claims.email !== 'undefined' &&
        typeof claims.email_verified !== 'undefined' &&
        claims.email_verified &&
        claims.email.endsWith('@admin.example.com')) {
      // Add custom claims for additional privileges.
      admin.auth().setCustomUserClaims(claims.sub, {
        admin: true
      }).then(function() {
        // Tell client to refresh token on user.
        res.end(JSON.stringify({
          status: 'success'
        });
      });
    } else {
      // Return nothing.
      res.end(JSON.stringify({status: 'ineligible'});
    }
  });
});
 

يمكن استخدام نفس التدفق عند ترقية مستوى وصول المستخدم الحالي. خذ على سبيل المثال ترقية مستخدم مجاني إلى اشتراك مدفوع. يتم إرسال رمز معرف المستخدم مع معلومات الدفع إلى خادم الخلفية عبر طلب HTTP. عند معالجة الدفعة بنجاح ، يتم تعيين المستخدم كمشترك مدفوع عبر SDK للمشرف. يتم إرجاع استجابة HTTP ناجحة إلى العميل لفرض تحديث الرمز المميز.

تحديد الأدوار عن طريق الواجهة الخلفية

يمكن تعيين برنامج نصي متكرر (لم يبدأ من العميل) ليتم تشغيله لتحديث مطالبات المستخدم المخصصة:

Node.js

 admin.auth().getUserByEmail('user@admin.example.com').then((user) => {
  // Confirm user is verified.
  if (user.emailVerified) {
    // Add custom claims for additional privileges.
    // This will be picked up by the user on token refresh or next sign in on new device.
    return admin.auth().setCustomUserClaims(user.uid, {
      admin: true
    });
  }
})
  .catch((error) => {
    console.log(error);
  });
 

جافا

 UserRecord user = FirebaseAuth.getInstance()
    .getUserByEmail("user@admin.example.com");
// Confirm user is verified.
if (user.isEmailVerified()) {
  Map<String, Object> claims = new HashMap<>();
  claims.put("admin", true);
  FirebaseAuth.getInstance().setCustomUserClaims(user.getUid(), claims);
}
 

بيثون

 user = auth.get_user_by_email('user@admin.example.com')
# Confirm user is verified
if user.email_verified:
    # Add custom claims for additional privileges.
    # This will be picked up by the user on token refresh or next sign in on new device.
    auth.set_custom_user_claims(user.uid, {
        'admin': True
    })
 

اذهب

 user, err := client.GetUserByEmail(ctx, "user@admin.example.com")
if err != nil {
	log.Fatal(err)
}
// Confirm user is verified
if user.EmailVerified {
	// Add custom claims for additional privileges.
	// This will be picked up by the user on token refresh or next sign in on new device.
	err := client.SetCustomUserClaims(ctx, user.UID, map[string]interface{}{"admin": true})
	if err != nil {
		log.Fatalf("error setting custom claims %v\n", err)
	}

} 

ج #

 UserRecord user = await FirebaseAuth.DefaultInstance
    .GetUserByEmailAsync("user@admin.example.com");
// Confirm user is verified.
if (user.EmailVerified)
{
    var claims = new Dictionary<string, object>()
    {
        { "admin", true },
    };
    await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(user.Uid, claims);
}

 

يمكن أيضًا تعديل المطالبات المخصصة بشكل تدريجي من خلال Admin SDK:

Node.js

 admin.auth().getUserByEmail('user@admin.example.com').then((user) => {
  // Add incremental custom claim without overwriting existing claims.
  const currentCustomClaims = user.customClaims;
  if (currentCustomClaims['admin']) {
    // Add level.
    currentCustomClaims['accessLevel'] = 10;
    // Add custom claims for additional privileges.
    return admin.auth().setCustomUserClaims(user.uid, currentCustomClaims);
  }
})
  .catch((error) => {
    console.log(error);
  });
 

جافا

 UserRecord user = FirebaseAuth.getInstance()
    .getUserByEmail("user@admin.example.com");
// Add incremental custom claim without overwriting the existing claims.
Map<String, Object> currentClaims = user.getCustomClaims();
if (Boolean.TRUE.equals(currentClaims.get("admin"))) {
  // Add level.
  currentClaims.put("level", 10);
  // Add custom claims for additional privileges.
  FirebaseAuth.getInstance().setCustomUserClaims(user.getUid(), currentClaims);
}
 

بيثون

 user = auth.get_user_by_email('user@admin.example.com')
# Add incremental custom claim without overwriting existing claims.
current_custom_claims = user.custom_claims
if current_custom_claims.get('admin'):
    # Add level.
    current_custom_claims['accessLevel'] = 10
    # Add custom claims for additional privileges.
    auth.set_custom_user_claims(user.uid, current_custom_claims)
 

اذهب

 user, err := client.GetUserByEmail(ctx, "user@admin.example.com")
if err != nil {
	log.Fatal(err)
}
// Add incremental custom claim without overwriting existing claims.
currentCustomClaims := user.CustomClaims
if currentCustomClaims == nil {
	currentCustomClaims = map[string]interface{}{}
}

if _, found := currentCustomClaims["admin"]; found {
	// Add level.
	currentCustomClaims["accessLevel"] = 10
	// Add custom claims for additional privileges.
	err := client.SetCustomUserClaims(ctx, user.UID, currentCustomClaims)
	if err != nil {
		log.Fatalf("error setting custom claims %v\n", err)
	}

} 

ج #

 UserRecord user = await FirebaseAuth.DefaultInstance
    .GetUserByEmailAsync("user@admin.example.com");
// Add incremental custom claims without overwriting the existing claims.
object isAdmin;
if (user.CustomClaims.TryGetValue("admin", out isAdmin) && (bool)isAdmin)
{
    var claims = new Dictionary<string, object>(user.CustomClaims);
    // Add level.
    claims["level"] = 10;
    // Add custom claims for additional privileges.
    await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(user.Uid, claims);
}