Ошибка базы данных Android Firebase: разрешение отклонено при отладке на телефоне

Я верю, что это будет быстро. Мое первое приложение для Android с базой данных Firebase в реальном времени. Когда я запускаю приложение с помощью эмулятора (Android studio Pixel XL API 30 — Android 11.0) — все хорошо, и у меня нет проблем, даже если мои правила таковы:

{
  "rules": {
    ".read": "auth !== null",
    ".write": "auth !== null"
  }
}

или правило для .read установлено в true. Но когда я запускаю ту же версию на своем телефоне, я получаю исключение «Отказано в доступе» при попытке прочитать данные из базы данных.

Для входа я использую GoogleSignIn по этой части кода:

private fun signIn()
{
    var signInIntent: Intent = mGoogleSignInClient.getSignInIntent()
    startActivityForResult(signInIntent, 1)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
{
    super.onActivityResult(requestCode, resultCode, data)

    // Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
    if (requestCode == 1)
    {
        // The Task returned from this call is always completed, no need to attach
        // a listener.
        var task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(
            data
        )
        handleSignInResult(task);
    }
}

private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>)
{
    try
    {
        val account = completedTask.getResult(ApiException::class.java)

        // Signed in successfully, show authenticated UI.
        updateUI(account)
        var intent = Intent(this, AnotherActivity::class.java)
        startActivity(intent)
    } catch (e: ApiException)
    {
        // The ApiException status code indicates the detailed failure reason.
        // Please refer to the GoogleSignInStatusCodes class reference for more information.
        Log.w("Log", "signInResult:failed code = " + e.statusCode)
        updateUI(null)
    }
}

Я снова загрузил google-services.json, но все та же проблема. Также я добавил auth.addAuthStateListener, чтобы получить некоторую информацию об аутентификации firebase.

override fun onCreate(savedInstanceState: Bundle?)
{
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_temperature_setup)

    initializeGoogleSignIn() // to get login context for logout
    auth = Firebase.auth
    auth.addAuthStateListener { firebaseAuth ->
        runit(firebaseAuth) }
    initializeNavigation() // menu etc.
}

private fun runit(firebaseAuth: FirebaseAuth)
{
    if (firebaseAuth.currentUser == null)
    {
        Toast.makeText(this, "no way sir", LENGTH_SHORT).show()
    }
    else
    {
        Toast.makeText(this, "maybe?", LENGTH_SHORT).show()
    }
}

Код, запрашивающий базу данных firebase в реальном времени:

override fun onCreate(savedInstanceState: Bundle?)
{
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_temperature_setup)

    initializeGoogleSignIn() // to get login context for logout
    auth = Firebase.auth
    auth.addAuthStateListener { firebaseAuth ->
        runit(firebaseAuth) }
    initializeNavigation() // menu etc.

    readInitialDataFromDb()
}

private fun readInitialDataFromDb()
{
    var db = Firebase.database.getReference(dbNameRooms)
    db.addListenerForSingleValueEvent(this)
}

override fun onDataChange(snapshot: DataSnapshot)
{
    if (rooms?.isEmpty() == true)
    {
        rooms = snapshot.getValue<List<Room>?>()
        createRoomLayout()
    }
    else
    {
        // anything here?
    }
}

override fun onCancelled(error: DatabaseError)
{
    Toast.makeText(this, "Cancelled reading db", LENGTH_SHORT).show()
}

Когда я запускаю это на эмуляторе, я получаю текущего пользователя и вызываю onDataChange. Запуск этого на моем телефоне Android 10 дает мне null для текущего пользователя и onCancelled с отказом в доступе к базе данных. Есть идеи, как это исправить? Спасибо р

Я не вижу никакого кода, который бы запрашивал базу данных и терпел неудачу, как вы описываете. С вашими правилами вам нужно будет убедиться, что запрос выполняется только после того, как пользователь полностью войдет в систему.

Doug Stevenson 22.12.2020 22:11

Вот что я не понимаю, почему это нормально с помощью эмулятора, а не с телефона. Я ищу разницу между этими двумя прогонами.

Radim H 22.12.2020 22:38

Я в замешательстве. Вы говорите, что у вас нет кода, который запрашивает базу данных? Если у вас есть код, покажите его, а также покажите, что пользователь вошел в систему во время запроса. В противном случае мы ничего не можем сделать.

Doug Stevenson 22.12.2020 22:41

Добавил часть кода для запроса БД - отредактировал исходный пост.

Radim H 22.12.2020 23:17
1
4
481
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ваш код запрашивает базу данных до завершения входа. Вы должны дождаться полного входа в систему, прежде чем правила безопасности, требующие проверки подлинности, будут удовлетворены.

Слушатели состояния аутентификации являются асинхронными и вызываются только тогда, когда пользователь полностью вошел в систему или вышел из нее. Вы должны использовать свой addAuthStateListener примерно так:

    auth.addAuthStateListener { firebaseAuth ->
        if (firebaseAuth.currentUser != null) {
            readInitialDataFromDb()
        }
    }

Обратите внимание, что запрос выполняется только после обнаружения присутствия пользователя.

Это просто предотвратит чтение исключения из базы данных, но это не решит мою проблему. Почему у меня разное поведение при использовании одной и той же учетной записи входа в Google как на эмуляторе, так и на моем телефоне, и у меня разные выходные данные? Спасибо за вашу помощь, но все же мне нужно авторизовать пользователя на моем телефоне, чтобы иметь возможность получать данные из базы данных. Вход в Google показывает, что пользователь успешно аутентифицирован.

Radim H 22.12.2020 23:49

Я могу заверить вас, что поведение, которое вы видите, не связано с устройством или эмулятором. Ошибки «Отказано в доступе» всегда связаны с отсутствием разрешений, предоставленных правилами безопасности. Поскольку ваши правила требуют только вошедшего в систему пользователя, это единственный способ, которым результаты запроса можно считать отклоненными. Вы действительно хотите написать свой код, чтобы избежать ошибки - это требование системы правил безопасности.

Doug Stevenson 22.12.2020 23:52

Привет, Дуг, я понял твою точку зрения, но даже если я изменю код, который ты предлагаешь, проблема все еще существует для моего телефона, но не для эмулятора, поэтому вопрос остается. Я думаю, что мне не хватает информации о связывании googleSingIn с Firebase.auth. У меня есть обычный googleSignIn для firebase (из учебника), но я, вероятно, скучаю по использованию googleSignIn для firebase.auth.

Radim H 23.12.2020 11:36
Ответ принят как подходящий

Итак, нашли первопричину проблемы. Эмулятор аутентифицируется по умолчанию с использованием FirebaseDefaultUser, поэтому он проходит правило аутентификации. На телефоне это работает так, как должно работать, поэтому до тех пор, пока пользователь firebase не будет аутентифицирован, пользователю будет заблокировано использование базы данных firebase в реальном времени (при условии, что вы установили правила, согласно которым каждый пользователь должен быть аутентифицирован для чтения/записи).

Я оставлю это немного открытым, так как не понял, почему там используется пользователь по умолчанию и в чем разница с эмулятором.

Чтобы заставить работать аутентификацию firebase, мне нужно было изменить свой код, чтобы использовать вход в Google, а затем использовать учетные данные для входа в Google, чтобы получить аутентифицированного пользователя firebase. Следующий код является наиболее полезным (из руководства по firebase на его github)

// [START onactivityresult]
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    // Result returned from launching the Intent from 
GoogleSignInApi.getSignInIntent(...);
    if (requestCode == RC_SIGN_IN) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(data)
        try {
            // Google Sign In was successful, authenticate with Firebase
            val account = task.getResult(ApiException::class.java)!!
            Log.d(TAG, "firebaseAuthWithGoogle:" + account.id)
            firebaseAuthWithGoogle(account.idToken!!)
        } catch (e: ApiException) {
            // Google Sign In failed, update UI appropriately
            Log.w(TAG, "Google sign in failed", e)
            // [START_EXCLUDE]
            updateUI(null)
            // [END_EXCLUDE]
        }
    }
}
// [END onactivityresult]

// [START auth_with_google]
private fun firebaseAuthWithGoogle(idToken: String) {
    // [START_EXCLUDE silent]
    showProgressBar()
    // [END_EXCLUDE]
    val credential = GoogleAuthProvider.getCredential(idToken, null)
    auth.signInWithCredential(credential)
            .addOnCompleteListener(this) { task ->
                if (task.isSuccessful) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithCredential:success")
                    val user = auth.currentUser
                    updateUI(user)
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCredential:failure", task.exception)
                    // [START_EXCLUDE]
                    val view = binding.mainLayout
                    // [END_EXCLUDE]
                    Snackbar.make(view, "Authentication Failed.", 
Snackbar.LENGTH_SHORT).show()
                    updateUI(null)
                }

                // [START_EXCLUDE]
                hideProgressBar()
                // [END_EXCLUDE]
            }
}
// [END auth_with_google]

Другие вопросы по теме