Skip to content

Latest commit

 

History

History
931 lines (750 loc) · 33.8 KB

File metadata and controls

931 lines (750 loc) · 33.8 KB

五、用户配置文件和访问管理

在上一章中,我们了解了如何在 react-redux 应用程序中使用 Firebase。我们还详细探讨了 redux,并了解了如何以及何时在 react-app 中使用 redux,Firebase 实时数据库将为我们提供应用程序中的实时座位预订状态。在本章中,我们将介绍 Firebase Admin SDK,它提供了一个用户管理 API,用于以完全管理员权限读取和写入实时数据库数据。因此,我们将为我们的应用程序创建一个管理页面,在该页面中,我们可以执行以下操作:

  • 创建新用户
  • 用户搜索引擎,我们可以根据不同的条件搜索用户
  • 所有用户的列表
  • 访问用户元数据,其中包括特定用户的帐户创建日期和上次登录日期
  • 删除用户
  • 更新用户信息而不必以用户身份登录
  • 验证电子邮件
  • 更改用户的电子邮件而不发送电子邮件通知以撤销这些更改
  • 使用电话号码创建新用户,并在不发送 SMS 验证的情况下更改用户的电话号码

首先,我们需要在 Node.js 环境中设置 Firebase Admin SDK,以管理员身份执行前面的操作。

设置 Firebase 管理 SDK

为了使用 Firebase Admin SDK,我们需要一个 Firebase 项目,其中我们有一个与 Firebase 服务通信的服务帐户,以及一个包含服务帐户凭据的配置文件。

要配置 Firebase 管理 SDK,请执行以下步骤:

  1. 登录Firebase 控制台选择<project_name>项目,点击项目概述中的设置图标:

Overview tab

  1. 转到“项目设置”中的“服务帐户”选项卡。
  2. 单击 Firebase 管理部分底部的生成私钥按钮;它将生成包含服务帐户凭据的 JSON 文件:

This JSON file contains very sensitive information about your service account and private encryption key. So never share and store it in a public repository; keep it confidential. If we lose this file because of any reason then, we can generate it again, and we'll no longer access Firebase Admin SDK with the old file.

Firebase CLI

Firebase 提供了一个命令行界面,该界面提供了各种工具来创建、管理、查看和部署 Firebase 项目。使用 Firebase CLI,我们可以轻松地在生产级静态托管上部署和托管我们的应用程序,并且它由 HTTPS 自动提供服务,并由全局 CDN 在一个命令中提供支持。

安装

在安装之前,请确保我们已经在机器上安装了 Node.js 4.0+。如果未安装,则从下载 Node.js 8“LTS”的最新版本 https://nodejs.org 完成安装后,我们可以从npm(节点包管理器)下载 Firebase CLI。

运行此命令在系统上全局安装 Firebase CLI:

npm install -g firebase-tools

要验证安装,请运行以下命令;如果 Firebase CLI 版本正确安装在您的系统上,则会打印该版本:

firebase --version

Firebase 管理集成

现在我们已经成功安装了 Firebase CLI,让我们将现有的应用程序代码从第 3 章、Firebase认证复制到第 5 章、用户配置文件和访问管理中的新目录中。在这里,我们将初始化 Firebase 应用程序,并在初始化应用程序之前运行以下命令登录 Firebase 控制台:

firebase login

成功登录 Firebase 控制台后,运行以下命令初始化项目:

firebase init

一旦我们运行此命令,它将提示您选择 Firebase 功能、项目和目录文件夹(相对于您的项目目录),该文件夹将包含要使用firebase deploy命令上载的hosting资产(默认情况下,它是公共的)。

我们还可以稍后在项目中添加功能,也可以将多个项目与同一目录关联。

Firebase 初始化完成后,运行以下命令安装项目依赖项,然后构建项目:

//run this command to install the project dependencies
npm install

//run this command to build the project
npm run build

要在部署到生产环境之前在本地运行应用程序进行验证,请运行以下命令:

firebase serve

它将从 build 目录或您在firebase.json文件中定义的任何名称本地启动服务器:

这是使用 firebase CLI 进行 firebase 初始化后的文件夹结构。

将 Firebase 管理验证 API 与 React 一起使用

Firebase Admin SDK 将使我们能够使用 Firebase Auth API 集成您自己的服务器。使用 Firebase Admin SDK,我们可以管理我们的应用程序用户,如ViewCreateUpdateDelete,而无需用户凭据,也可以管理认证令牌,而无需进入 Firebase 管理控制台。

为了实现这一点,我们将在现有的 React 应用程序中创建管理面板。

以下是我们将使用 Firebase Admin SDK 集成到管理面板的功能列表:

  • 创建并验证自定义令牌
  • 具有自定义用户声明的用户级访问角色
  • 查看应用程序用户列表
  • 获取用户配置文件
  • CreateDeleteUpdate用户信息
  • 解析票证状态

初始化管理 SDK

正如我们所看到的,Firebase admin SDK 仅在 Node.Js 中受支持,因此我们将使用 npm init 创建一个新项目,并从npm包中安装 Firebase admin。

运行以下命令安装 firebase admin 并将其保存在您的package.json中:

npm install firebase-admin --save

在 JS 文件中复制以下代码段并初始化 SDK;我们添加了对从 Firebase 管理服务帐户下载的 JSON 文件的引用:

const admin = require('firebase-admin');
const serviceAccount = require('./firebase/serviceAccountKey.json');

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://demoproject-7cc0d.firebaseio.com"
});

现在,我们将创建 RESTfulAPI 与客户端应用程序交互,以访问 AdminSDK 功能。

运行此命令以启动节点管理服务器:

node <fileName>

它将在不同的端口上启动本地服务器,例如http://localhost:3001

创建和验证自定义令牌

Firebase Admin SDK 使我们能够使用外部机制(如 LDAP 服务器)或 Firebase 不支持的第三方 OAuth 提供程序(如 Instagram 或 LinkedIn)对用户进行认证。我们可以使用 Firebase 自定义令牌方法(内置 AdminSDK)完成所有这些工作,也可以使用任何第三方 JWT 库。

让我们看看如何使用 AdminSDK 创建和验证令牌。

要创建自定义令牌,我们必须有一个有效的uid,需要在createCustomToken()方法中传递:

function createCustomToken(req,res){
 const userId = req.body.uid "guest_user"
 admin.auth().createCustomToken(userId)
 .then(function(customToken) {
 res.send(customToken.toJSON());
 })
 .catch(function(error) {
 console.log("Error creating custom token:", error);
 });
}

在前面的函数中,当用户使用用户名和密码登录时,我们有来自客户端的uid,如果凭据有效,我们将从服务器返回自定义 JWT(JSON Web 令牌),客户端设备可以使用该 JWT 向 Firebase 进行认证:

app.get('/login', function (req, res) {
 if(validCredentials(req.body.username,req.body.password)){
    createCustomToken(req,res);
 }
})

一旦通过认证,该身份将用于访问 Firebase 实时数据库和云存储等 Firebase 服务

如果需要,我们还可以添加一些附加字段以包含在自定义令牌中。考虑这个代码:

function createCustomToken(req,res){
 const userId = req.body.uid
 const subscription = {
   paid:true
 }
 admin.auth().createCustomToken(userId)
 .then(function(customToken) {
   res.send(customToken.toJSON());
 })
 .catch(function(error) {
 console.log("Error creating custom token:", error);
 });
}

这些附加字段将在安全规则中的auth/request.auth对象中可用。

一旦 react 方法生成并接收到令牌,我们将通过将自定义令牌传递给 FirebasesignInWithCustomToken()方法,对应用程序的用户进行认证:

const uid = this.state.userId
fetch('http://localhost:3000/login', {
   method: 'POST', // or 'PUT'
   body: JSON.stringify({idToken:idToken}), 
   headers: new Headers({
     'Content-Type': 'application/json'
  })
 }).then(res => res.json())
 .catch(error => console.error('Error:', error))
 .then(res => {
  console.log(res,"after token valid");
  firebase.auth().signInWithCustomToken(res.customToken).catch(function(error) {
    var errorCode = error.code;
    var errorMessage = error.message;
 });
})

成功验证后,用户使用uid指定的帐户登录到我们的应用程序,我们在创建自定义令牌方法时包括了该帐户。

同样,其他 Firebase 认证方法如signInWithEmailAndPassword()signInWithCredential()工作,并且auth/request.auth对象将与用户uid一起在 Firebase 实时数据库安全规则中可用。在前面的示例中,我们指定了生成自定义令牌的原因。

//Firebase Realtime Database Rules
{
 "rules": {
 "admin": {
 ".read": "auth.uid === 'guest_user'"
 }
 }
}
//Google Cloud Storage Rules
service firebase.storage {
 match /b/<firebase-storage-bucket-name>/o {
 match /admin/{filename} {
 allow read, write: if request.auth.uid == "guest_user";
 }
 }
}

同样,我们也可以访问额外传递的对象,这些对象在auth.tokenrequest.auth.token中可用:

//Firebase Realtime Database Rules
{
 "rules": {
 "subscribeServices": {
 ".read": "auth.token.paid === true"
 }
 }
}
service firebase.storage {
 match /b/<firebase-storage-bucket-name>/o {
 match /subscribeServices/{filename} {
 allow read, write: if request.auth.token.paid === true;
 }
 }
}

Firebase 还可以为我们提供用户登录 app 后获取uid的方式;它创建一个唯一标识它们的对应 ID 令牌,我们可以将该令牌发送到服务器进行验证,并允许它们访问应用程序的多个资源。例如,当我们创建与应用程序通信的自定义后端服务器时,我们可能需要使用 HTTPS 安全地标识该服务器上当前登录的用户。

要从 Firebase 检索 ID 令牌,请确保用户已登录到应用程序,我们可以使用以下方法在您的 react 应用程序中检索 ID 令牌:

firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
 // Send this token to custom backend server via HTTPS
}).catch(function(error) {
 // Handle error
});

一旦我们有了这个 ID 令牌,我们就可以将这个 JWT(JSON Web 令牌)发送到后端服务器 Firebase Admin SDK 或任何第三方库来验证它。

Firebase Admin SDK 内置verifyIdToken(idToken)方法对 ID 令牌进行验证和解码;如果提供的令牌未过期、有效且未正确签名,此方法将返回解码的 ID 令牌:

function validateToken(req,res){
  const idToken= req.body.idToken;
  admin.auth().verifyIdToken(idToken)
   .then(function(decodedToken) {
   var uid = decodedToken.uid;
  //...
  }).catch(function(error) {
 // Handle error
 });
}

现在,让我们扩展现有的应用程序,用户只能看到他们提交的票据,我们还将允许用户更新现有的配置文件。我们还将在 React 中创建一个管理面板,并根据角色向用户显示管理 UI。

管理员访问和安全规则的自定义声明

如前所述,Firebase Admin SDK 支持使用令牌定义自定义属性。通过这些自定义属性,可以定义不同的访问级别,包括对应用程序的基于角色的控制,这在应用程序的安全规则中是强制的。

我们需要在以下常见情况下定义用户角色:

  • 授予用户访问资源的管理员角色
  • 为用户分配不同的组
  • 为用户提供多级访问权限,如付费用户、普通用户、经理、支持团队等

我们也可以根据需要限制访问的数据库定义规则,比如我们有数据库节点helpdesk/tickets/all,可以访问所有数据票据的数据。但是,我们只希望管理员用户能够看到所有的票证。为了更有效地实现此目标,请验证电子邮件 ID,并使用以下实时数据库规则添加名为 admin 的自定义用户声明:

{
 "rules": {
  "helpdesk":{
   "tickets":{
       "all": {
         ".read": "auth.token.admin === true",
         ".write": "auth.token.admin === true",
         }
        }
      }
   }
}

Do not confuse Custom claims with Custom Authentication and Firebase Authentication. It applies to users already signed in with supported providers (Email/Password, Github, Google, Facebook, phone, and such), but custom authentication is used when we use different authentication, which is not supported by Firebase. For example, a user signed in with Firebase Auth's Email/Password provider can have access control defined using custom claims.

使用 Admin SDK 添加自定义声明

在 Firebase 管理 SDK 中,我们可以使用 Firebase 内置的setCustomUserClaims()方法应用自定义声明:

admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
});

使用发送应用程序的 Admin SDK 验证自定义声明

Firebase Admin SDK 还为我们提供了使用verifyIdToken()方法验证令牌的方法:

 admin.auth().verifyIdToken(idToken).then((claims) => {
  if (claims.admin === true) {
    // Allow access to admin resource.
   }
 });

我们还可以检查自定义声明是否在用户对象中可用:

admin.auth().getUser(uid).then((userRecord) => {
   console.log(userRecord.customClaims.admin);
});

现在,让我们看看如何在现有应用程序中实现这一点。

首先,让我们在 Node Admin SDK 后端服务器中创建一个 restful API:

app.post('/setCustomClaims', (req, res) => {
 // Get the ID token passed by the client app.
 const idToken = req.body.idToken;
 console.log("accepted",idToken,req.body);
 // Verify the ID token
 admin.auth().verifyIdToken(idToken).then((claims) => {
 // Verify user is eligible for admin access or not
 if (typeof claims.email !== 'undefined' &&
 claims.email.indexOf('@adminhelpdesk.com') != -1) {
 // Add custom claims for admin access.
 admin.auth().setCustomUserClaims(claims.sub, {
 admin: true,
 }).then(function() {
 // send back to the app to refresh token and shows the admin UI.
 res.send(JSON.stringify({
 status: 'success',
 role:'admin'
 }));
 });
 } else if (typeof claims.email !== 'undefined'){
 // Add custom claims for admin access.
 admin.auth().setCustomUserClaims(claims.sub, {
 admin: false,
 }).then(function() {
 // Tell client to refresh token on user.
 res.send(JSON.stringify({
 status: 'success',
 role:'employee'
 }));
 });
 }
 else{
 // return nothing
 res.send(JSON.stringify({status: 'ineligible'}));
 }
 })
 });

我已经在 Firebase 控制台中借助 admin SDK 手动创建了一个带[email protected]的管理员用户;我们需要验证并添加管理员的自定义声明。

现在,打开App.JSX并添加以下代码段;根据角色设置应用程序的初始状态:

 constructor() {
 super();
 this.state = {
   authenticated : false,
   data:'',
   userUid:'',
     role:{
       admin:false,
       type:''
     }
   }
 }

现在,在componentWillMount()组件生命周期方法中调用前面的 API,我们需要从firebase.auth().onAuthStateChanged((user))中获取来自用户对象的idToken并发送给服务器进行验证:

this.getIdToken(user).then((idToken)=>{
 console.log(idToken);
 fetch('http://localhost:3000/setCustomClaims', {
   method: 'POST', // or 'PUT'
   body: JSON.stringify({idToken:idToken}), 
   headers: new Headers({
     'Content-Type': 'application/json'
   })
 }).then(res => res.json())
  .catch(error => console.error('Error:', error))
  .then(res => {
   console.log(res,"after token valid");
   if(res.status === 'success' && res.role === 'admin'){
      firebase.auth().currentUser.getIdToken(true);
       this.setState({
         authenticated:true,
         data:user.providerData,
         userUid:user.uid,
             role:{
                 admin:true,
                 type:'admin'
             }
     })
 }
 else if (res.status === 'success' && res.role === 'employee'){
 this.setState({
     authenticated:true,
     data:user.providerData,
     userUid:user.uid,
     role:{
         admin:false,
         type:'employee'
         }
     })
 }
 else{
     ToastDanger('Invalid Token !!')
 }

在前面的代码中,我们使用fetchAPI 发送 HTTP 请求。它类似于 XMLHttpRequest,但它有新的特性,而且功能更强大。根据响应,我们正在设置组件的状态,并将组件注册到路由中。

这就是我们的路由组件的外观:

{
 this.state.authenticated && !this.state.role.admin
 ?
 (
 <React.Fragment>
 <Route path="/view-ticket" render={() => (
 <ViewTicketTable userId = {this.state.userUid} />
 )}/>
 <Route path="/add-ticket" render={() => (
 <AddTicketForm userId = {this.state.userUid} userInfo = {this.state.data} />
 )}/>
 <Route path="/user-profile" render={() => (
 <ProfileUpdateForm userId = {this.state.userUid} userInfo = {this.state.data} />
 )}/>
 </React.Fragment>
 )
 :
 (
 <React.Fragment>
   <Route path="/get-alluser" component = { AppUsers }/>
   <Route path="/tickets" component = { GetAllTickets }/>
   <Route path="/add-new-user" component = { NewUserForm }/>
 </React.Fragment>
 )
 }

以下是我们正在注册和呈现的组件列表,如果用户是管理员,则为管理员组件:

  • AppUser:获取申请用户列表,并负责删除用户,按不同条件搜索用户
  • Tickets:查看所有车票列表并更改车票状态
  • NewUserForm:将新用户添加到应用程序中

我们正在使用 Node.js Firebase Admin SDK 服务器执行前面的操作。

创建一个名为admin的文件夹,并在其中创建一个名为getAllUser.jsx的文件。其中,我们将创建一个 React 组件,负责获取用户列表并将其显示到 UI 中;我们还将添加按不同条件搜索用户的功能,如电子邮件 ID、电话号码等。

getAllUser.jsx文件中,我们的渲染方法是这样的:

<form className="form-inline">
//Search Input
     <div className="form-group" style={marginRight}>
         <input type="text" id="search" className="form-control"
         placeholder="Search user" value={this.state.search} required  
         />
     </div>
//Search by options
     <select className="form-control" style={marginRight}>
         <option value="email">Search by Email</option>
         <option value="phone">Search by Phone Number</option>
     </select>
     <button className="btn btn-primary btn-sm">Search</button>
 </form>

我们还在render方法中增加了表格,显示用户列表:

 <tbody>
 {
 this.state.users.length > 0 ?
 this.state.users.map((list,index) => {
 return (
 <tr key={list.uid}>
 <td>{list.email}</td>
 <td>{list.displayName}</td> 
 <td>{list.metadata.lastSignInTime}</td> 
 <td>{list.metadata.creationTime}</td> 
 <td>
     <button className="btn btn-sm btn-primary" type="button" style={marginRight} onClick={()=>            {this.deleteUser(list.uid)}}>Delete User</button>
       <button className="btn btn-sm btn-primary" type="button" onClick={()=>                        {this.viewProfile(list.uid)}}>View Profile</button>
 </td> 
 </tr>
 )
 }) :
 <tr>
     <td colSpan="5" className="text-center">No users found.</td>
 </tr>
 }
 </tbody>

这是表体,用动作按钮显示用户列表,现在我们需要在componentDidMount()方法中调用用户 API:

fetch('http://localhost:3000/users', {
 method: 'GET', // or 'PUT'
 headers: new Headers({
 'Content-Type': 'application/json'
 })
 }).then(res => res.json())
 .catch(error => console.error('Error:', error))
 .then(response => {
 console.log(response,"after token valid");
 this.setState({
   users:response
 })
 console.log(this.state.users,'All Users');
 })

同样,我们需要调用其他 API 来删除、查看用户配置文件和搜索:

deleteUser(uid){
 fetch('http://localhost:3000/deleteUser', {
     method: 'POST', // or 'PUT'
     body:JSON.stringify({uid:uid}),
     headers: new Headers({
         'Content-Type': 'application/json'
     })
 }).then(res => res.json())
     .catch(error => console.error('Error:', error))
 }
//Fetch User Profile
 viewProfile(uid){
 fetch('http://localhost:3000/getUserProfile', {
     method: 'POST', // or 'PUT'
     body:JSON.stringify({uid:uid}),
     headers: new Headers({
         'Content-Type': 'application/json'
     })
 }).then(res => res.json())
     .catch(error => console.error('Error:', error))
     .then(response => {
         console.log(response.data,"User Profile");
     })
 }

对于搜索,Firebase Admin SDK 有内置的方法:getUserByEmail()getUserByPhoneNumber()。我们可以采用与我们在 Firebase 管理 API 中创建的delete()fetch()相同的方式实现这些功能:

//Search User by Email
searchByEmail(emailId){
 fetch('http://localhost:3000/searchByEmail', {
 method: 'POST', // or 'PUT'
 body:JSON.stringify({email:emailId}),
 headers: new Headers({
 'Content-Type': 'application/json'
 })
 }).then(res => res.json())
 .catch(error => console.error('Error:', error))
 .then(response => {
 console.log(response.data,"User Profile");
 this.setState({
    users:response
 })
 })
 }

请看下面的node.jsAPI 代码片段:

function listAllUsers(req,res) {
 var nextPageToken;
 // List batch of users, 1000 at a time.
 admin.auth().listUsers(1000,nextPageToken)
 .then(function(data) {
 data = data.users.map((el) => {
 return el.toJSON();
 })
 res.send(data);
 })
 .catch(function(error) {
 console.log("Error fetching the users from firebase:", error);
 });
}
function deleteUser(req, res){
  const userId = req.body.uid;
  admin.auth().deleteUser(userId)
  .then(function() {
    console.log("Successfully deleted user"+userId);
    res.send({status:"success", msg:"Successfully deleted user"})
  })
  .catch(function(error) {
    console.log("Error deleting user:", error);
  res.send({status:"error", msg:"Error deleting user:"})
  });
}
function searchByEmail(req, res){
  const searchType = req.body.email;
  admin.auth().getUserByEmail(userId)
  .then(function(userInfo) {
    console.log("Successfully fetched user information associated with this email"+userId);
    res.send({status:"success", data:userInfo})
  })
  .catch(function(error) {
    console.log("Error fetching user info:", error);
  res.send({status:"error", msg:"Error fetching user informaition"})
  });
}

现在,我们将创建一个 API,根据用户的请求调用前面的函数:

app.get('/users', function (req, res) {
 listAllUsers(req,res);
})
app.get('/deleteUser', function (req, res) {
 deleteUser(req,res);
})
app.post('/searchByEmail', function (req, res){
 searchByEmail(req, res)
})

现在,让我们快速浏览一下浏览器中的应用程序,看看它的外观,并尝试用管理员用户登录:

A screenshot of our application when logged in with admin credentials; the purpose is to show the UI and console when we log in as admin

看起来太棒了!只需看看前面的屏幕截图;它为管理员显示了不同的导航,如果您可以在控制台中看到,它将显示带有自定义声明对象的令牌,我们将其添加到此用户以访问管理员:

看起来很棒!我们可以通过操作按钮和搜索 UI 查看应用程序的用户。

现在,考虑从列表中删除用户,同时用户会话处于活动状态并使用该应用程序。在这种情况下,我们需要为用户管理会话并提示重新验证,因为每次用户登录时,用户凭据都会发送到 Firebase 认证后端,并交换 Firebase ID 令牌(JWT)和刷新令牌。

以下是我们需要管理用户会话的常见场景:

  • 用户被删除
  • 用户已禁用
  • 电子邮件地址和密码已更改

Firebase Admin SDK 还提供了使用revokeRefreshToken()方法撤销特定用户会话的能力。它撤销给定用户的活动刷新令牌。如果我们重置密码,Firebase 认证后端将自动撤销用户令牌。

请参考 Firebase 云函数的以下代码片段,根据特定的uid撤销用户:

const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// Revoke all refresh tokens for a specified user for whatever reason.
function revokeUserTokens(uid){
return admin.auth().revokeRefreshTokens(uid)
.then(() => {
    // Get user's tokensValidAfterTime.
    return admin.auth().getUser(uid);
})
.then((userRecord) => {
    // Convert to seconds as the auth_time in the token claims is in seconds too.
    const utcRevocationTimeSecs = new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
    // Save the refresh token revocation timestamp. This is needed to track ID token
    // revocation via Firebase rules.
    const metadataRef = admin.database().ref("metadata/" + userRecord.uid);
    return metadataRef.set({revokeTime: utcRevocationTimeSecs});
  });
}

正如我们所知,Firebase ID 令牌是无状态 JWT,只能通过将请求发送到 Firebase 认证后端服务器以检查令牌的状态是否已撤销来验证。因此,在服务器上执行此检查的成本非常高,并且会增加额外的工作量,需要额外的网络请求负载。我们可以通过设置检查吊销的 Firebase 规则来避免此网络请求,而不是将请求发送到 Firebase Admin SDK。

这是声明没有客户端访问权限的规则的正常方法,每个用户的写入存储吊销时间:

{
"rules": {
    "metadata": {
        "$user_id": {
            ".read": "$user_id === auth.uid",
            ".write": "false",
            }
        }
    }
}

但是,如果我们希望只允许未经撤销和验证的用户访问受保护的数据,则必须配置以下规则:

{
 "rules": {
     "users": {
         "$user_id": {
         ".read": "$user_id === auth.uid && auth.token.auth_time >     (root.child('metadata').child(auth.uid).child('revokeTime').val() || 0)",
         ".write": "$user_id === auth.uid && auth.token.auth_time > (root.child('metadata').child(auth.uid).child('revokeTime').val() || 0)"
             }
         }
     }
}

每当撤销用户对浏览器令牌的刷新时,tokensValidAfterTimeUTC 时间戳将保存在数据库节点中。 当要验证用户的 ID 令牌时,必须将附加检查布尔标志传递给verifyIdToken()方法。如果用户的令牌被吊销,则应注销应用程序或要求用户使用 Firebase 认证客户端 SDK 提供的重新认证 API 进行重新认证。

例如,我们在该方法的setCustomClaims上方创建了一个方法;只需在catch方法中添加以下代码:

 .catch(error => {
     // Invalid token or token was revoked:
     if (error.code == 'auth/id-token-revoked') {
     //Shows the alert to user to reauthenticate
     // Firebase Authentication API gives the API to reauthenticateWithCredential /reauthenticateWithPopup /reauthenticateWithRedirect
 }
 });

此外,如果令牌被吊销,则将通知发送到客户端应用程序以重新验证。

考虑电子邮件/密码 FixBasic 认证提供者的这个例子:

let password = prompt('Please provide your password for reauthentication');
let credential = firebase.auth.EmailAuthProvider.credential(
firebase.auth().currentUser.email, password);
firebase.auth().currentUser.reauthenticateWithCredential(credential)
.then(result => {
// User successfully reauthenticated.
})
.catch(error => {
// An error occurred.
});

现在,让我们单击“所有票证”链接以查看所有用户提交的票证列表:

作为管理员用户,我们可以更改将在 Firebase 实时数据库中更新的票证的状态。现在,如果单击 createnewuser,它将显示表单以添加用户信息。

让我们创建一个新组件,并向 render 方法添加以下代码:

<form className="form" onSubmit={this.handleSubmitEvent}>
 <div className="form-group">
 <input type="text" id="name" className="form-control"
 placeholder="Enter Employee Name" value={this.state.name} required onChange={this.handleChange} />
 </div>
 <div className="form-group">
 <input type="text" id="email" className="form-control"
 placeholder="Employee Email ID" value={this.state.email} required onChange={this.handleChange} />
 </div>
 <div className="form-group">
 <input type="password" id="password" className="form-control"
 placeholder="Application Password" value={this.state.password} required onChange={this.handleChange} />
 </div>
 <div className="form-group">
 <input type="text" id="phoneNumber" className="form-control"
 placeholder="Employee Phone Number" value={this.state.phoneNumber} required onChange={this.handleChange} />
 </div>
 <div className="form-group">
 <input
 type="file"
 ref={input => {
 this.fileInput = input;
 }}
 />
 </div>
 <button className="btn btn-primary btn-sm">Submit</button>
 </form>

handleSubmitEvent(e)上,我们需要调用createNewUser()Firebase admin SDK 方法,将表单数据传递给它:

e.preventDefault();
 //React form data object
 var data = {
 email:this.state.email,
 emailVerified: false,
 password:this.state.password,
 displayName:this.state.name,
 phoneNumber:this.state.phoneNumber,
 profilePhoto:this.fileInput.files[0],
 disabled: false
 }
 fetch('http://localhost:3000/createNewUser', {
   method: 'POST', // or 'PUT'
   body:JSON.stringify({data:data}),
   headers: new Headers({
   'Content-Type': 'application/json'
 })
 }).then(res => res.json())
 .catch(error => { 
 ToastDanger(error)
 })
 .then(response => {
 ToastSuccess(response.msg)
 });

再次启动服务器并在浏览器中打开应用程序。让我们尝试使用管理员凭据在应用程序中创建新用户:

Create New User component; the purpose of the image is to show the alert message when we fill the form and submit to the Firebase to create a new user

看起来很棒;我们已在应用程序中成功创建新用户,并返回 Firebase 为新用户自动生成的uid

现在,让我们继续前进,用普通用户登录:

如果您查看前面的屏幕截图,一旦我们使用任何 Firebase 认证提供商登录应用程序,在仪表板上,它会显示用户的所有票证,但它应该只显示与此电子邮件 ID 关联的票证。为此,我们需要更改数据结构和 Firebase 节点引用。

这是应用程序中最重要的部分,我们需要计划如何保存和检索数据,以使过程尽可能简单。

数据在 JSON 树中的结构

在 Firebase 实时数据库中,所有数据都存储为 JSON 对象,这是一个云托管的 JSON 树。当我们将数据添加到数据库中时,它将成为现有 JSON 结构中的一个节点,并带有一个关联键,该键由 Firebase 自动生成。我们还可以提供自己的自定义密钥,如用户 ID 或任何语义名称,也可以使用push()方法提供。

例如,在我们的帮助台应用程序中,我们将票据存储在一个路径中,例如/helpdesk/tickets;现在我们将用/helpdesk/tickets/$uid/$ticketKey替换此。请看下面的代码:

var newTicketKey = firebase.database().ref('/helpdesk').child('tickets').push().key;
 // Write the new ticket data simultaneously in the tickets list and the user's ticket list.
 var updates = {};
 updates['/helpdesk/tickets/' + userId + '/' + newTicketKey] = data;
 updates['/helpdesk/tickets/all/'+ newTicketKey] = data;

以下是数据结构如何从数据库中创建和检索票证:

在上图中,高亮显示的节点为$uid,属于提交票据的用户。

下面是我们的完整代码的外观:

var newTicketKey = firebase.database().ref('/helpdesk').child('tickets').push().key;
 // Write the new ticket data simultaneously in the tickets list and the user's ticket list.
 var updates = {};
 updates['/helpdesk/tickets/' + userId + '/' + newTicketKey] = data;
 updates['/helpdesk/tickets/all/'+ newTicketKey] = data;

 return firebase.database().ref().update(updates).then(()=>{
 ToastSuccess("Saved Successfully!!");
 this.setState({
    issueType:"",
    department:"",
    comment:""
 });
 }).catch((error)=>{
    ToastDanger(error.message);
 });

打开浏览器,重新提交票据;现在查看票证仪表板:

看起来很棒!现在用户只能看到他们提交的票据。在下一章中,我们将了解如何在数据库中的数据中应用安全规则和常见的安全威胁。

总结

本章介绍了如何配置和初始化 Firebase Admin SDK,以在 NodeJS 中创建应用程序后端。它还解释了如何使用 Firebase Admin 的用户管理 API 管理我们的应用程序用户,而不必访问 Firebase 控制台,例如:

  • Create
  • Delete
  • Update
  • Remove

Firebase Admin SDK 使我们能够创建和验证自定义 JWT 令牌,这允许用户向任何提供者进行认证,即使 Firebase Auth Providers 列表中没有该提供者。它还使您能够在用户信息发生任何更改(如用户被删除、禁用、电子邮件地址或密码更改等)时管理用户的会话。

我们还学习了如何控制对自定义声明的访问。这有助于我们在 Firebase 应用程序中实现基于角色的访问控制,为用户提供不同级别的访问(角色)。

在下一章中,我们将学习数据库安全风险和防止此类威胁的检查表。我们还将看到 Firebase 实时数据库的安全部分和 Firebase 实时数据库规则语言。