firebase auth

本文最初发布在Auth0.com博客上 ,并经许可在此处重新发布。

在这个分为两部分的系列教程中,我们将学习如何构建一个使用Auth0身份验证保护Node后端和Angular前端安全的应用程序。 我们的服务器和应用程序还将使用自定义令牌对Firebase Cloud Firestore数据库进行身份验证,以便用户在使用Auth0登录后可以安全方式留下实时评论。

可以在angular-firebase GitHub存储库中找到Angular应用程序代码,在firebase-auth0-nodeserver存储库中找到Node API。

使用Auth0对Firebase和Angular进行身份验证:第1部分

本教程的第1部分将介绍:

  1. Firebase和Auth0
  2. 我们将建立什么
  3. 角度CLI
  4. Auth0客户端和API
  5. 具有服务帐户的Firebase项目
  6. 节点API
  7. 设置Angular应用
  8. Angular应用架构
  9. 实施共享模块
  10. 实现路由和延迟加载模块
  11. 加载和错误组件
  12. 验证逻辑
  13. 核心逻辑
  14. 下一步

Firebase和Auth0

Firebase是一个移动和Web应用程序开发平台。 Firebase于2014年被Google收购,并将继续在Google的领导下进行开发。 Firebase提供了NoSQL数据库( RTDB或Realtime Database and Cloud Firestore,在撰写本文时为beta版),该 数据库托管在云中,并使用Web套接字进行连接以向应用程序提供实时功能。

Auth0是基于云的平台,提供身份验证和授权即服务。 作为身份验证提供程序,Auth0使开发人员可以轻松地实现和自定义其应用程序的登录和授权安全性。

选择Auth0 + Firebase身份验证

如果您已经熟悉Firebase的产品,您可能会问:为什么我们要在Firebase中使用自定义令牌实现Auth0,而不是坚持使用Firebase的内置身份验证 ?

首先,在这里有一个重要的区别。 使用Auth0保护Firebase并不意味着您使用Firebase身份验证。 Firebase具有自定义身份验证方法 ,允许开发人员将其首选身份解决方案 Firebase身份验证集成。 这种方法使开发人员能够实施Firebase身份验证,以便它与专有系统或其他身份验证提供程序无缝运行。

我们可能想将Auth0与Firebase身份验证集成的潜在原因有很多。 另外,在某些情况下,仅使用基本的Firebase身份验证就足够了。 让我们来探索。

如果您满足以下条件,则可以单独使用Firebase的内置身份验证

  • 只想对Firebase RTDB或Firestore进行身份验证,而无需对其他后端进行身份验证
  • 只需要少量的登录选项,不需要企业标识提供程序,与您自己的用户存储数据库集成等。
  • 不需要大量的用户管理,配置文件扩充等,并且可以通过API严格地管理用户
  • 无需自定义身份验证流程
  • 无需遵守有关用户数据存储的合规性法规。

如果您执行以下操作,则应考虑将Auth0与自定义Firebase令牌结合使用:

  • 已经实现了Auth0,并希望向您的应用添加实时功能
  • 需要轻松使用发行的令牌来保护 Firebase 提供的后端
  • 需要整合社交身份提供者 ,而不仅仅是Google,Facebook,Twitter和GitHub
  • 需要集成企业身份提供程序 ,例如Active Directory,LDAP,ADFS,SAMLP等。
  • 需要定制的身份验证流程
  • 需要通过API 易于管理的仪表板进行强大的用户管理
  • 希望能够动态丰富用户个人资料
  • 想要可自定义的无密码登录 , 多因素身份验证 , 违反密码安全性 , 异常检测等功能。
  • 必须遵守HIPAA,GDPR,SOC2等合规性法规 。

本质上,如果您有一个非常简单的应用程序具有基本的身份验证需求,并且仅使用Firebase数据库,则Firebase的基本身份验证提供程序就足够了。 但是,如果您还需要更多, Firebase提供了一种将其服务其他身份验证解决方案一起使用的好方法 。 这是许多开发人员都将面对的更为现实的情况,因此我们将在此处详细探讨。

我们将建立什么

我们将构建一个使用Auth0保护的Node.js API,该API会铸造自定义Firebase令牌,并返回十种不同犬种的数据。

我们还将构建一个名为“ Popular Dogs”的Angular前端应用程序,该应用程序显示有关2016年十种最受欢迎​​的狗的信息,该信息由美国养犬俱乐部(AKC)按公众受欢迎程度排名。 我们的应用将通过Auth0保护,调用Node API来获取狗数据,并调用API获取Firebase令牌以授权用户使用Cloud Firestore实时添加和删除评论。 该应用程序将使用共享模块以及实现延迟加载。

带有Auth0自定义令牌的Angular Firebase应用

要实施该应用程序,您将需要以下内容:

  • 角度CLI
  • 具有客户端和API配置的免费Auth0帐户
  • 具有服务帐户的免费Firebase项目

让我们开始吧!

角度CLI

确保在本地计算机上安装了带有NPM的Node.js。 运行以下命令以全局安装Angular CLI :

$ npm install -g @angular/cli@latest

我们将使用CLI生成Angular应用及其几乎所有架构。

Auth0客户端和API

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

您需要一个Auth0帐户来管理身份验证。 您可以在此处注册一个免费帐户 。

Auth0登录屏幕

接下来,设置Auth0客户端应用程序和API,以便Auth0可以与Angular应用程序和Node API交互。

设置Auth0客户端

  1. 转到您的Auth0信息中心 ,然后点击创建新客户端按钮。
  2. 为您的新应用命名(例如Angular Firebase ),然后选择单页Web应用
  3. 在新的Auth0客户端应用的设置中,将http://localhost:4200/callback允许的回调URL中
  4. 启用使用Auth0(而不是IdP)进行单点登录的切换。
  5. 在“ 设置”部分的底部,单击“显示高级设置”。 选择OAuth选项卡,并验证JsonWebToken签名算法是否设置为“ RS256”。
  6. 如果需要,您可以建立一些社交关系 。 然后,可以在“ 连接”选项卡下的“ 客户端”选项中为您的应用启用它们。 上面的屏幕快照中显示的示例使用用户名/密码数据库,Facebook,Google和Twitter。

注意:对于生产,请确保您设置了自己的社交密钥,并且不要将社交连接设置为使用Auth0开发密钥。

设置Auth0 API

  1. 转到Auth0信息中心中的API ,然后单击“创建API”按钮。 输入API的名称,例如Firebase Dogs API 。 将标识符设置为您的API端点URL。 在本教程中,我们的API标识符为http://localhost:1337/签名算法应为“ RS256”。
  2. 您可以在新API设置的“ 快速入门”选项卡下查阅Node.js示例。 在接下来的步骤中,我们将使用Express , express-jwt和jwks-rsa以这种方式实现Node API。

现在,我们准备在Angular客户端和Node后端API上实现Auth0身份验证。

具有服务帐户的Firebase项目

接下来,您将需要一个免费的Firebase项目。

创建Firebase项目

  1. 转到Firebase控制台,然后使用您的Google帐户登录。
  2. 单击添加项目
  3. 在弹出的对话框中,为您的项目命名(例如Angular Firebase Auth0 )。 将根据您选择的名称生成一个项目ID。 然后,您可以选择您的国家/地区。
  4. 单击创建项目按钮。

生成管理员SDK密钥

为了创建自定义的Firebase令牌 ,您需要访问Firebase Admin SDK 。 要获得访问权限,您必须在新的Firebase项目中创建一个服务帐户。

单击Firebase控制台侧栏中项目概述旁边的齿轮图标,然后从出现的菜单中选择项目设置

Firebase项目设置

在设置视图中,单击“ 服务帐户”选项卡。 将显示Firebase Admin SDK UI,其中显示配置代码段。 默认情况下,选择Node.js。 这是我们想要的技术,我们将在Node API中实现它。 单击生成新私钥按钮。

将出现一个对话框,警告您秘密存储私钥。 我们将永远不要将这个密钥签入公共存储库。 单击Generate Key按钮,将密钥下载为.json文件。 我们将很快将此文件添加到我们的Node API。

节点API

可以在firebase-auth0-nodeserver GitHub存储库中找到本教程的完整Node.js API。 让我们学习如何构建此API。

节点API文件结构

我们将要设置以下文件结构:

firebase-auth0-nodeserver/
|--firebase/
|--.gitignore
|--<your-firebase-admin-sdk-key>.json
|--.gitignore
|--config.js
|--dogs.json
|--package.json
|--routes.js
|--server.js

您可以使用命令行生成必要的文件夹和文件,如下所示:

$ mkdir firebase-auth0-nodeserver
$ cd firebase-auth0-nodeserver
$ mkdir firebase
$ touch firebase/.gitignore
$ touch .gitignore
$ touch config.js
$ touch dogs.json
$ touch package.json
$ touch routes.js
$ touch server.js

Firebase Admin SDK密钥和Git忽略

现在,将您先前下载的Firebase Admin SDK .json密钥文件移动到firebase文件夹中。 我们会确保文件夹已签入,但绝对不会使用firebase/.gitignore将其内容推送到存储库中,如下所示:

# firebase/.gitignore
*
*/
!.gitignore

.gitignore配置可确保Git忽略firebase目录中的所有文件和文件夹, 除了 .gitignore文件本身。 这使我们可以提交(基本上)空的文件夹。 我们的.json Firebase Admin SDK密钥可以存在于此文件夹中,我们不必担心通过filename忽略它。

注意:如果我们将项目拉到多台计算机上并且生成了不同的密钥(具有不同的文件名),这将特别有用。

接下来,我们为根目录的.gitignore添加代码:

# .gitignore
config.js
node_modules

狗的JSON数据

接下来,我们将添加十个犬种的数据。 为简便起见,您可以简单地将此数据复制并粘贴到dogs.json文件中。

依存关系

让我们像这样添加我们的package.json文件:

{
"name": "firebase-auth0-nodeserver",
"version": "0.1.0",
"description": "Node.js server that authenticates with an Auth0 access token and returns a Firebase auth token.",
"repository": "https://github.com/auth0-blog/firebase-auth0-nodeserver",
"main": "server.js",
"scripts": {
"start": "node server"
},
"author": "Auth0",
"license": "MIT",
"dependencies": {},
"devDependencies": {}
}

我们将使用命令行安装依赖项,最新版本将自动保存到package.json文件中:

$ npm install --save body-parser cors express express-jwt jwks-rsa firebase-admin

我们需要body-parsercorsexpress来提供我们的API端点。 身份验证将依赖于express-jwtjwks-rsa ,而Firebase令牌铸造是通过firebase-admin SDK(我们将使用生成的密钥进行访问)实现的。

组态

config.js文件中,添加以下代码,并将占位符值替换为您自己的设置:

// config.js
module.exports = {
AUTH0_DOMAIN: '<Auth0 Domain>', // e.g., you.auth0.com
AUTH0_API_AUDIENCE: '<Auth0 API Audience>', // e.g., http://localhost:1337/
FIREBASE_KEY: './firebase/<Firebase JSON>', // e.g., your-project-firebase-adminsdk-xxxxx-xxxxxxxxxx.json
FIREBASE_DB: '<Firebase Database URL>' // e.g., https://your-project.firebaseio.com
};

服务器

有了我们的数据,配置和依赖关系,我们现在就可以实现我们的Node服务器。 打开server.js文件并添加:

// server.js
// Modules
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
// App
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors());
// Set port
const port = process.env.PORT || '1337';
app.set('port', port);
// Routes
require('./routes')(app);
// Server
app.listen(port, () => console.log(`Server running on localhost:${port}`));

这将使用http://localhost:1337/ Express启动我们的Node服务器。

注意:请注意,这是我们在Auth0中设置的API标识符。

API路由

接下来打开routes.js文件。 这是我们定义API端点,保护它们和铸造自定义Firebase令牌的地方。 添加以下代码:

// routes.js
// Dependencies
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const firebaseAdmin = require('firebase-admin');
// Config
const config = require('./config');
module.exports = function(app) {
// Auth0 athentication middleware
const jwtCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://${config.AUTH0_DOMAIN}/.well-known/jwks.json`
}),
audience: config.AUTH0_API_AUDIENCE,
issuer: `https://${config.AUTH0_DOMAIN}/`,
algorithm: 'RS256'
});
// Initialize Firebase Admin with service account
const serviceAccount = require(config.FIREBASE_KEY);
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
databaseURL: config.FIREBASE_DB
});
// GET object containing Firebase custom token
app.get('/auth/firebase', jwtCheck, (req, res) => {
// Create UID from authenticated Auth0 user
const uid = req.user.sub;
// Mint token using Firebase Admin SDK
firebaseAdmin.auth().createCustomToken(uid)
.then(customToken =>
// Response must be an object or Firebase errors
res.json({firebaseToken: customToken})
)
.catch(err =>
res.status(500).send({
message: 'Something went wrong acquiring a Firebase token.',
error: err
})
);
});
// Set up dogs JSON data for API
const dogs = require('./dogs.json');
const getDogsBasic = () => {
const dogsBasicArr = dogs.map(dog => {
return {
rank: dog.rank,
breed: dog.breed,
image: dog.image
}
});
return dogsBasicArr;
}
// GET dogs (public)
app.get('/api/dogs', (req, res) => {
res.send(getDogsBasic());
});
// GET dog details by rank (private)
app.get('/api/dog/:rank', jwtCheck, (req, res) => {
const rank = req.params.rank * 1;
const thisDog = dogs.find(dog => dog.rank === rank);
res.send(thisDog);
});
};

在较高级别,我们的路由文件执行以下操作:

  • 设置身份验证检查,以确保只有登录用户才能使用jwtCheck中间件访问路由
  • 使用从Firebase项目服务帐户生成的私钥初始化Firebase Admin SDK。
  • 提供返回自定义Firebase令牌的安全GET端点
  • 提供一个公共GET *端点,该端点返回dogs数据的简短版本
  • 提供一个安全的GET *端点,该端点返回按等级要求的特定狗的详细数据。

*端点使用相同基本数据集的变体来模拟更复杂的API。

您可以阅读代码注释以获取更多详细信息。

提供API

您可以通过运行以下命令来提供Node API:

$ node server

然后,该API将在http:// localhost:1337可用。

注意:如果尝试在浏览器中访问安全路由,则应收到401 Unauthorized错误。

这就是我们的服务器! 保持API保持运行状态,以便Angular应用可以访问它,我们将在下一步中对其进行设置。

设置Angular应用

现在是时候创建我们的Angular应用程序并设置一些其他依赖项了。

创建新的Angular应用

您应该早先已经安装了Angular CLI 。 现在,我们可以使用CLI生成我们的项目及其架构。 要创建一个新应用,请选择一个包含文件夹,然后运行以下命令:

$ ng new angular-firebase --routing --skip-tests

--routing标志生成具有路由模块的应用程序,而--skip-tests生成不具有.spec.ts文件的根组件。

注意:为简便起见,本文将不涉及测试。 如果您想了解有关在Angular中进行测试的更多信息,请查看教程的结论以获取更多资源。

安装前端依赖项

现在,让我们安装前端依赖项:

$ cd angular-firebase
$ npm install --save auth0-js@latest firebase@latest angularfire2@latest

我们将需要auth0-js库在Angular应用中实现Auth0身份验证。 我们还需要在firebase JS SDK和angularfire2角火力地堡库来实现我们的火力地堡的实时评论。

添加Bootstrap CSS

为了简化样式,我们将Bootstrap CSS CDN链接添加到index.html文件的<head> ,如下所示:

<!-- src/index.html -->
...
<head>
...
<title>Top 10 Dogs</title>
...
<link
rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
crossorigin="anonymous">
</head>
...

服务角度应用

您可以使用以下命令为Angular应用提供服务:

$ ng serve

该应用程序将在浏览器中的http:// localhost:4200上运行 。

Angular应用架构

我们将使用Angular CLI为我们的应用程序预先生成完整的架构。 这样,我们可以在实现逻辑和模板之前确保模块正常运行。

我们的应用将使用延迟加载模块化方法 。 本教程中的示例应用程序很小,但是我们希望以可扩展的,真实的方式构建它。

根模块

使用ng new命令生成Angular应用时,已经创建了根模块。 根模块位于src/app/app.module.ts 。 我们在Angular应用程序中生成的任何未指定其他模块子目录的组件都将自动导入并在我们的根模块中声明。

现在,使用CLI生成一个组件:

# create CallbackComponent:
$ ng g component callback --is --it --flat --no-spec

该命令由以下内容组成:

  • ng g component :生成具有以下内容的callback组件文件:
  • --is内联样式
  • --it内联模板
  • --flat不包含文件夹
  • --no-spec.spec测试文件

用户登录到我们的应用程序后,我们将使用回调组件来处理重定向。 这是一个非常简单的组件。

注意: ggenerate的快捷方式。 我们还可以使用c作为component的快捷方式,使此命令成为ng gc 但是,为了清楚起见,本教程将不对生成的文件类型使用快捷方式。

核心模块架构

接下来,我们将创建CoreModule及其组件和服务。 这是一个共享模块。 在Angular项目文件夹的根目录中,运行以下CLI命令。 请确保您运行ng g module core第一个命令,就像这样:

# create Core module:
$ ng g module core
# create API service with no .spec file:
$ ng g service core/api --no-spec
# create HeaderComponent with inline styles, no .spec file, and export in module:
$ ng g component core/header --is --no-spec --export=true
# create LoadingComponent with inline styles, inline template, no folder, no .spec file, and export in module:
$ ng g component core/loading --is --it --flat --no-spec --export=true
# create ErrorComponent with inline styles, inline template, no folder, no .spec file, and export in module:
$ ng g component core/error --is --it --flat --no-spec --export=true
# create Dog type interface:
$ ng g interface core/dog
# create DogDetail type interface:
$ ng g interface core/dog-detail

首先创建模块可确保在该模块的文件夹中创建的组件随后将被导入并自动在该父模块(而不是应用程序的根模块)中声明。

注意:如果要在另一个模块中使用共享模块的组件,则需要export组件并声明它们。 我们可以使用--export=true标志通过CLI自动执行此操作。

这是我们的应用程序需要访问的共享核心服务,组件和模型的基本架构。

身份验证模块架构

接下来,我们将创建AuthModule 。 执行以下CLI命令(同样,请确保首先生成模块):

# create Auth module:
$ ng g module auth
# create AuthService with no .spec file:
$ ng g service auth/auth --no-spec
# create Auth route guard with no .spec file:
$ ng g guard auth/auth --no-spec

我们的Auth模块提供了管理身份验证所需的服务和路由防护,但没有任何组件。 这也是一个共享模块。

狗模块架构

我们的应用程序的主页将由DogsModule提供。 根据AKC的排名,这将是2016年十只最受欢迎的狗的名单。 使用以下CLI命令来为该延迟加载的页面模块生成结构:

# create Dogs module:
$ ng g module dogs
# create DogsComponent with inline styles and no .spec file:
$ ng g component dogs/dogs --is --no-spec

狗模块架构

我们的应用程序还将为Dogs组件中列出的每条狗提供详细页面,以便用户可以了解有关每种品种的更多信息。 使用以下CLI命令来为延迟加载的DogModule生成结构:

# create Dog module:
$ ng g module dog
# create DogComponent with inline styles and no .spec file:
$ ng g component dog/dog --is --no-spec

注释模块架构

最后,我们需要实现Firebase实时注释所需的架构。 使用下面的CLI命令生成的结构CommentsModule

# create Comments module:
$ ng g module comments
# create Comment model class:
$ ng g class comments/comment
# create CommentsComponent with no .spec file:
$ ng g component comments/comments --no-spec --export=true
# create CommentFormComponent with inline styles and no .spec file:
$ ng g component comments/comments/comment-form --is --no-spec

环境配置

让我们将Auth0和Firebase的配置信息添加到Angular前端。 打开environment.ts文件并添加:

// src/environments/environment.ts
const FB_PROJECT_ID = '<FIREBASE_PROJECT_ID>';
export const environment = {
production: false,
auth: {
clientId: '<AUTH0_CLIENT_ID>',
clientDomain: '<AUTH0_DOMAIN>', // e.g., you.auth0.com
audience: '<AUTH0_API_AUDIENCE>', // e.g., http://localhost:1337/
redirect: 'http://localhost:4200/callback',
scope: 'openid profile email'
},
firebase: {
apiKey: '<FIREBASE_API_KEY>',
authDomain: `${FB_PROJECT_ID}.firebaseapp.com`,
databaseURL: `https://${FB_PROJECT_ID}.firebaseio.com`,
projectId: FB_PROJECT_ID,
storageBucket: `${FB_PROJECT_ID}.appspot.com`,
messagingSenderId: '<FIREBASE_MESSAGING_SENDER_ID>'
},
apiRoot: '<API URL>' // e.g., http://localhost:1337/ (DO include trailing slash)
};

用适当的Auth0,Firebase和API信息替换<angle brackets>占位符。

您可以在您为本教程创建的客户端和API的设置的Auth0仪表板中找到Auth0配置。

单击标有“ 将Firebase添加到您的Web应用程序”的大图标后,您可以在Firebase控制台项目概述中找到Firebase配置,如下所示:

将Firebase添加到您的Web应用程序

添加加载图片

开始在Angular应用中实现功能之前,我们要做的最后一件事是添加加载图像。 创建以下文件夹: src/assets/images

然后将此加载的SVG图像保存到该文件夹​​中:

加载SVG

实施共享模块

让我们设置我们的模块。 我们将在根AppModule导入共享模块( CoreModuleAuthModule )。

核心模块

首先,我们将实现我们的CoreModule 。 打开core.module.ts文件并更新为以下代码:

// src/app/core/core.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { DatePipe } from '@angular/common';
import { HeaderComponent } from './header/header.component';
import { ApiService } from './api.service';
import { LoadingComponent } from './loading.component';
import { ErrorComponent } from './error.component';
@NgModule({
imports: [
CommonModule,
RouterModule,
HttpClientModule, // AuthModule is a sibling and can use this without us exporting it
FormsModule
],
declarations: [
HeaderComponent,
LoadingComponent,
ErrorComponent
],
exports: [
FormsModule, // Export FormsModule so CommentsModule can use it
HeaderComponent,
LoadingComponent,
ErrorComponent
]
})
export class CoreModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
Title,
DatePipe,
ApiService
]
};
}
}

由于这是一个共享模块,因此我们将导入在整个应用程序中需要访问的其他模块,服务和组件。

注意: CommonModule导入到 不是根模块的所有模块中

在我们的imports数组中,我们将在CoreModule添加服务或组件可能需要的任何模块,或者将这些模块提供给应用程序中的其他模块。 CLI应该已经自动将所有生成的组件添加到了declarations数组。 exports数组应包含我们要提供给其他模块的任何模块或组件。

请注意,我们已从@angular/core导入ModuleWithProviders 。 使用此模块,我们可以创建一个forRoot()方法,当导入CoreModule时,可以在根app.module.ts中的导入时调用该方法。 这样,我们可以确保添加到由forRoot()方法返回的providers数组中的所有服务在我们的应用程序中保持单例 。 这样,如果我们应用中的其他模块也需要导入CoreModule ,我们可以避免意外的多个实例。

验证模块

接下来让我们添加一些代码,我们AuthModuleauth.module.ts文件:

// src/app/auth/auth.module.ts
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { AuthService } from './auth.service';
import { AuthGuard } from './auth.guard';
import { AngularFireAuthModule } from 'angularfire2/auth';
@NgModule({
imports: [
CommonModule,
AngularFireAuthModule
]
})
export class AuthModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: AuthModule,
providers: [
AuthService,
AuthGuard
]
};
}
}

我们将导入ModuleWithProviders以实现与CoreModuleforRoot()方法。 然后,我们将导入AuthServiceAuthGuard 。 我们还需要从angularfire2/auth导入AngularFireAuthModule ,以便我们可以在AuthService保护Firebase连接。 然后,应在forRoot()方法的providers数组中返回服务和防护。

评论模块

打开comments.module.ts文件以实现CommentsModule如下所示:

// src/app/comments/comments.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from '../core/core.module';
import { environment } from './../../environments/environment';
import { AngularFireModule } from 'angularfire2';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { CommentsComponent } from './comments/comments.component';
import { CommentFormComponent } from './comments/comment-form/comment-form.component';
@NgModule({
imports: [
CommonModule,
CoreModule, // Access FormsModule, Loading, and Error components
AngularFireModule.initializeApp(environment.firebase),
AngularFirestoreModule
],
declarations: [
CommentsComponent,
CommentFormComponent
],
exports: [
CommentsComponent
]
})
export class CommentsModule { }

我们需要导入CoreModule以便可以利用其导出的FormsModuleLoadingComponentErrorComponent 。 我们还需要从environment.ts文件访问我们的配置。 评论使用火力地堡的云数据库的FireStore,让我们导入AngularFireModuleAngularFirestoreModule以及我们的两个组成部分: CommentsComponentCommentFormComponent

当我们将AngularFireModule添加到@NgModule的imports数组时,我们将调用其initializeApp()方法,并传入Firebase配置。 我们的两个组件都应该已经在declarations数组中,并且CommentsComponent应该已经添加到了exports数组中,以便其他模块中的其他组件可以使用它。

注意:我们不需要导出CommentsFormComponent因为它是CommentsComponent的子级。

CommentsModule不提供任何服务,因此无需实现forRoot()方法。

应用模块

现在,我们的CoreModuleAuthModuleCommentsModule已经实现,我们需要导入我们的根模块中,上述AppModule地处app.module.ts文件:

// src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { CoreModule } from './core/core.module';
import { AuthModule } from './auth/auth.module';
import { CommentsModule } from './comments/comments.module';
import { AppComponent } from './app.component';
import { CallbackComponent } from './callback.component';
@NgModule({
declarations: [
AppComponent,
CallbackComponent
],
imports: [
BrowserModule,
AppRoutingModule,
CoreModule.forRoot(),
AuthModule.forRoot(),
CommentsModule
],
bootstrap: [AppComponent]
})
export class AppModule { }

CLI已自动添加了AppComponentCallbackComponent 。 当我们将CoreModuleAuthModule添加到imports数组时,我们将调用forRoot()方法以确保没有为其服务创建额外的实例。 CommentsModule不提供任何服务,因此该模块无需担心。

实现路由和延迟加载模块

我们有两个需要路由的模块: DogsModule用于狗的主要列表)和DogModule ,其中包含显示犬种详细信息页面的组件。

应用程序路由

首先,让我们实现应用程序的路由。 打开app-routing.module.ts文件并添加以下代码:

// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CallbackComponent } from './callback.component';
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
{
path: '',
loadChildren: './dogs/dogs.module#DogsModule',
pathMatch: 'full'
},
{
path: 'dog',
loadChildren: './dog/dog.module#DogModule',
canActivate: [
AuthGuard
]
},
{
path: 'callback',
component: CallbackComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

我们将导入CallbackComponentAuthGuard 。 其余路由将是对模块的字符串引用 ,而不是使用loadChildren属性导入的组件。

我们将设置默认的''路径来从DogsModule加载路由DogsModule ,而'dog'路径来从DogModule加载路由DogModule'dog'路径也应受到AuthGuard保护,我们使用canActivate属性对其进行了声明。 如果我们需要不止一个,这可以容纳一系列的路由卫士。 最后, 'callback'路由应仅指向CallbackComponent

狗模块

让我们向dogs.module.ts文件添加一些代码:

// src/app/dogs/dogs.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { CoreModule } from '../core/core.module';
import { CommentsModule } from '../comments/comments.module';
import { DogsComponent } from './dogs/dogs.component';
const DOGS_ROUTES: Routes = [
{
path: '',
component: DogsComponent
}
];
@NgModule({
imports: [
CommonModule,
CoreModule,
RouterModule.forChild(DOGS_ROUTES),
CommentsModule
],
declarations: [
DogsComponent
]
})
export class DogsModule { }

除了CoreModuleCommentsModule外,我们RouterModule导入RoutesRouterModule (注释将显示在主要的dogs列表页面上)。

这个模块有一个子路由,因此我们将创建一个常量,该常量包含一个数组来保存我们的路由对象。 独生子女的路线,我们需要继承''从路径app-routing.module.ts ,所以它的路径也应该是'' 。 它将加载DogsComponent 。 在imports数组中,我们将DOGS_ROUTES常量传递给RouterModuleforChild()方法。

狗模块

DogModule的工作方式与同样DogsModule以上。 打开dog.module.ts并添加以下内容:

// src/app/dog/dog.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Routes, RouterModule } from '@angular/router';
import { CoreModule } from '../core/core.module';
import { DogComponent } from './dog/dog.component';
const DOG_ROUTES: Routes = [
{
path: ':rank',
component: DogComponent
}
];
@NgModule({
imports: [
CommonModule,
CoreModule,
RouterModule.forChild(DOG_ROUTES)
],
declarations: [
DogComponent
]
})
export class DogModule { }

该模块与DogsModule之间的DogsModule是我们的DOG_ROUTES的路径为:rank 。 这样,任何特定狗的详细信息的路由都会作为URL段传递,该URL段与我们在十大犬种列表中的犬排名相匹配,如下所示:

http://localhost:4200/dog/3

另一个区别是,我们将不会导入CommentsModule 。 但是,如果需要,我们将来可以在狗的详细信息中添加评论。

我们的应用程序的架构和路由现已完成! 该应用程序应成功编译并显示在浏览器中,并且延迟加载功能可以正确加载共享代码和请求的特定路由的代码。

现在,我们准备实现应用程序的逻辑。

加载和错误组件

加载和错误组件是基本的核心UI元素,可以在我们的应用程序中的许多不同位置使用。 现在设置它们。

加载组件

LoadingComponent应该只显示一个加载图像。 (回想一下,我们已经保存一个,当我们建立我们的应用程序的体系结构)。但是,它应该是能够显示图像大和中心, 小和内联。

打开loading.component.ts文件并添加:

// src/app/core/loading.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-loading',
template: `
<div [ngClass]="{'inline': inline, 'text-center': !inline, 'py-2': !inline }">
<img src="/assets/images/loading.svg">
</div>
`,
styles: [`
.inline {
display: inline-block;
}
img {
height: 80px;
width: 80px;
}
.inline img {
height: 24px;
width: 24px;
}
`]
})
export class LoadingComponent {
@Input() inline: boolean;
}

使用@Input()装饰器 ,我们可以将信息从其父级传递到组件中,告诉它是否应该内联显示该组件。 我们将在模板中使用NgClass指令 ( [ngClass] )有条件地为我们想要的显示添加适当的样式。 在另一个模板中显示该组件将如下所示:

<!-- Large, full width, centered: -->
<app-loading></app-loading>
<!-- Inline: -->
<app-loading inline="true"></app-loading>

错误成分

接下来,让我们快速实现我们的ErrorComponent 。 如果显示该组件,将显示一条简单的错误消息。 打开error.component.ts文件并添加:

// src/app/core/error.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-error',
template: `
<p class="alert alert-danger">
<strong>Error:</strong> There was an error retrieving data.
</p>
`
})
export class ErrorComponent {
}

验证逻辑

现在,让我们实现使AuthModule的功能正常工作所需的代码。 为了在CoreModule构建标头,我们将需要身份验证服务,因此从这里开始是有意义的。 我们已经安装了必要的依赖项(Auth0和FirebaseAuth),所以让我们开始吧。

认证服务

在编写任何代码之前,我们将确定该服务的要求。 我们要:

  • 创建一个login()方法,该方法将允许用户使用Auth0进行身份验证
  • 如果提示用户通过尝试访问受保护的路由来登录,请确保在成功通过身份验证后将其重定向到该路由
  • 获取用户的个人资料信息并设置他们的会话
  • 建立让应用知道用户是否已登录的方法
  • 通过Auth0访问令牌的授权,从API请求Firebase自定义令牌
  • 如果成功获取Firebase令牌,请使用返回的令牌登录Firebase,并为应用建立一种方法来了解用户是否已登录Firebase
  • Firebase铸造的自定义令牌会在一小时后过期,因此我们应该设置一种方法来自动续订过期的令牌
  • 创建一个logout()方法以清除会话并logout() Firebase。

打开我们之前生成的auth.service.ts文件。

为了简化教程,请在GitHub repo的auth.service.ts文件中查看完整代码。

发生了很多事情,所以让我们逐步进行一下。

首先,与往常一样,我们将导入依赖项。 这包括我们environment配置之前设置为广大Auth0,火力地堡和API设置,以及auth0firebase库, AngularFireAuthHttpClient调用API来获取自定义火力地堡的令牌,以及必要的RxJS进口。

您可以参考代码注释,以获取有关AuthService类的私有和公共成员的AuthService

接下来是构造函数,在该函数中,我们可以在类中使用RouterAngularFireAuthHttpClient

login()方法如下所示:

login(redirect?: string) {
// Set redirect after login
const _redirect = redirect ? redirect : this.router.url;
localStorage.setItem('auth_redirect', _redirect);
// Auth0 authorize request
this._auth0.authorize();
}

如果将redirect URL段传递到方法中,我们会将其保存在本地存储中。 如果没有传递重定向,我们将只存储当前URL。 然后,我们将使用在成员中创建的_auth0实例,并调用Auth0的authorize()方法转到Auth0登录页面,以便我们的用户可以进行身份​​验证。

接下来的三个方法是handleLoginCallback()getUserInfo()_setSession()

handleLoginCallback() {
this.loading = true;
// When Auth0 hash parsed, get profile
this._auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken) {
window.location.hash = '';
// Store access token
this.accessToken = authResult.accessToken;
// Get user info: set up session, get Firebase token
this.getUserInfo(authResult);
} else if (err) {
this.router.navigate(['/']);
this.loading = false;
console.error(`Error authenticating: ${err.error}`);
}
});
}
getUserInfo(authResult) {
// Use access token to retrieve user's profile and set session
this._auth0.client.userInfo(this.accessToken, (err, profile) => {
if (profile) {
this._setSession(authResult, profile);
} else if (err) {
console.warn(`Error retrieving profile: ${err.error}`);
}
});
}
private _setSession(authResult, profile) {
// Set tokens and expiration in localStorage
const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + Date.now());
localStorage.setItem('expires_at', expiresAt);
this.userProfile = profile;
// Session set; set loggedIn and loading
this.loggedIn = true;
this.loading = false;
// Get Firebase token
this._getFirebaseToken();
// Redirect to desired route
this.router.navigateByUrl(localStorage.getItem('auth_redirect'));

这些方法是不言自明的:它们使用Auth0方法parseHash()userInfo()来提取身份验证结果并获取用户的个人资料 。 我们还将设置服务的属性以存储必要的状态(例如,用户的身份验证状态是否正在加载以及是否已登录),处理错误,将数据保存到我们的服务和本地存储中,以及重定向到适当的位置路线。

我们还将使用身份验证结果的访问令牌来向我们的API授权HTTP请求以获取Firebase令牌。 这是通过_getFirebaseToken()_firebaseAuth()方法完成的:

private _getFirebaseToken() {
// Prompt for login if no access token
if (!this.accessToken) {
this.login();
}
const getToken$ = () => {
return this.http
.get(`${environment.apiRoot}auth/firebase`, {
headers: new HttpHeaders().set('Authorization', `Bearer ${this.accessToken}`)
});
};
this.firebaseSub = getToken$().subscribe(
res => this._firebaseAuth(res),
err => console.error(`An error occurred fetching Firebase token: ${err.message}`)
);
}
private _firebaseAuth(tokenObj) {
this.afAuth.auth.signInWithCustomToken(tokenObj.firebaseToken)
.then(res => {
this.loggedInFirebase = true;
// Schedule token renewal
this.scheduleFirebaseRenewal();
console.log('Successfully authenticated with Firebase!');
})
.catch(err => {
const errorCode = err.code;
const errorMessage = err.message;
console.error(`${errorCode} Could not log into Firebase: ${errorMessage}`);
this.loggedInFirebase = false;
});
}

我们将创建一个从GET请求到我们的API的/auth/firebase端点的可观察到的getToken$并订阅它。 如果成功,我们会将带有自定义Firebase令牌的返回对象传递给_firebaseAuth()方法,该方法将使用Firebase的signInWithCustomToken()方法向Firebase进行身份验证。 此方法返回一个Promise,当Promise被解决后,我们可以告诉我们的应用程序Firebase登录成功。 我们还可以安排Firebase令牌续订(我们稍后会介绍)。 我们将适当地处理所有错误。

我们的自定义Firebase令牌将在3600秒(1小时)内到期。 这仅是我们默认的Auth0访问令牌生存期(即7200秒或2个小时)的一半 。 为避免用户在会话过程中意外失去对Firebase的访问权限,我们将使用以下两种方法设置Firebase令牌自动续订: scheduleFirebaseRenewal()unscheduleFirebaseRenewal()

注意:您也可以使用checkSession()方法以类似的方式使用checkSession()实现自动会话更新。 此外,如果用户离开应用程序,然后稍后返回,则可以使用checkSession()在构造函数中还原未到期的身份验证会话。 在本教程中,我们不会涉及到这一点,但是您应该自己尝试一下!

scheduleFirebaseRenewal() {
// If user isn't authenticated, check for Firebase subscription
// and unsubscribe, then return (don't schedule renewal)
if (!this.loggedInFirebase) {
if (this.firebaseSub) {
this.firebaseSub.unsubscribe();
}
return;
}
// Unsubscribe from previous expiration observable
this.unscheduleFirebaseRenewal();
// Create and subscribe to expiration observable
// Custom Firebase tokens minted by Firebase
// expire after 3600 seconds (1 hour)
const expiresAt = new Date().getTime() + (3600 * 1000);
const expiresIn$ = Observable.of(expiresAt)
.pipe(
mergeMap(
expires => {
const now = Date.now();
// Use timer to track delay until expiration
// to run the refresh at the proper time
return Observable.timer(Math.max(1, expires - now));
}
)
);
this.refreshFirebaseSub = expiresIn$
.subscribe(
() => {
console.log('Firebase token expired; fetching a new one');
this._getFirebaseToken();
}
);
}
unscheduleFirebaseRenewal() {
if (this.refreshFirebaseSub) {
this.refreshFirebaseSub.unsubscribe();
}
}

为了安排自动令牌更新,我们将创建一个可观察的计时器,该计时器可以倒计时到令牌的到期时间。 我们可以订阅expiresIn$ observable,然后再次调用_getFirebaseToken()方法以获取新令牌。 signInWithCustomToken() angularfire2 auth方法返回一个Promise。 当承诺解决后, scheduleFirebaseRenewal()调用scheduleFirebaseRenewal() ,这又确保了只要用户登录到我们的应用程序,令牌就将继续更新。

我们还需要能够取消订阅令牌续订,因此我们还将为此创建一个方法。

最后,我们的身份验证服务中的最后两个方法是logout()tokenValid()

logout() {
// Ensure all auth items removed
localStorage.removeItem('expires_at');
localStorage.removeItem('auth_redirect');
this.accessToken = undefined;
this.userProfile = undefined;
this.loggedIn = false;
// Sign out of Firebase
this.loggedInFirebase = false;
this.afAuth.auth.signOut();
// Return to homepage
this.router.navigate(['/']);
}
get tokenValid(): boolean {
// Check if current time is past access token's expiration
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return Date.now() < expiresAt;
}

logout()方法从本地存储和我们的服务中删除所有会话信息,退出Firebase Auth,然后将用户重定向回首页(我们应用程序中唯一的公共路由)。

tokenValid访问器方法通过将Auth0访问令牌的到期时间与当前日期时间进行比较来检查Auth0访问令牌是否到期。 这对于确定用户是否需要新的访问令牌很有用。 我们在本教程中不会对此进行介绍,但是您可能希望自己进一步探索Auth0会话续订。

这就是我们的AuthService

回调组件

回想一下,我们在根模块中创建了一个CallbackComponent 。 此外,我们将environment的Auth0 redirect设置为回调组件的路由。 这意味着,当用户使用Auth0登录时,他们将通过/callback路由返回我们的应用,并将身份验证哈希附加到URI。

我们使用方法来处理身份验证和设置会话,从而创建了AuthService ,但是目前尚未从任何地方调用这些方法。 回调组件是执行此代码的适当位置。

打开callback.component.ts文件并添加:

// src/app/callback.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth/auth.service';
@Component({
selector: 'app-callback',
template: `
<app-loading></app-loading>
`
})
export class CallbackComponent implements OnInit {
constructor(private auth: AuthService) { }
ngOnInit() {
this.auth.handleLoginCallback();
}
}

我们所有的回调组件所需要做的就是在AuthServicehandleAuth()方法执行时显示LoadingComponenthandleLoginCallback()方法将解析身份验证哈希,获取用户的个人资料信息,设置其会话,然后重定向到应用程序中的适当路由。

验证卫士

现在,我们已经实现了身份验证服务,我们可以访问在整个Angular应用程序中有效使用身份验证状态所需的属性和方法。 让我们使用此逻辑来实现AuthGuard以保护路由。

使用Angular CLI应该会生成一些有用的样板代码,并且我们只需要进行一些小的更改即可确保只有经过身份验证的用户才能访问我们的受保护路由。

注意:必须注意,路由防护器 本身不能提供足够的安全性。 就像我们在本教程中所做的那样,您应该始终保护自己的API端点,并且永远不要依赖客户端来授权对受保护数据的访问。

打开auth.guard.ts文件并进行以下更改:

// src/app/auth/auth.guard.ts
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (this.auth.loggedIn) {
return true;
} else {
// Send guarded route to redirect after logging in
this.auth.login(state.url);
return false;
}
}
}

我们将导入AuthService添加一个constructor()函数,以使该服务在我们的路由保护器中可用。 如果满足条件以授予对路由的访问权,则canActivate()方法应返回true否则返回false 。 在我们的情况下,如果用户经过身份验证,则应该能够访问该受保护的路由。 该loggedIn从我们的财产AuthService提供此信息。

如果用户没有有效的令牌,我们将提示他们登录。我们希望他们在通过身份验证后被重定向回受保护的路由,因此,我们将调用login()方法并传递受保护的路由( state.url )作为重定向参数。

注意:请记住,我们之前设置了整个应用程序的体系结构和路由。 我们已经在我们的狗详细信息路由中添加了AuthGuard ,因此,既然我们已经实现了警卫功能,则应该对其进行保护。

核心逻辑

The last thing we'll do in this section of our tutorial is build out the remaining components and services that belong to our CoreModule . We've already taken care of the LoadingComponent and ErrorComponent , so let's move on to the header.

Header Component

The header will use methods and logic from our authentication service to show login and logout buttons as well as display the user's name and picture if they're authenticated. Open the header.component.ts file and add:

// src/app/core/header/header.component.ts
import { Component } from '@angular/core';
import { AuthService } from '../../auth/auth.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styles: [`
img {
border-radius: 100px;
width: 30px;
}
.loading { line-height: 31px; }
.home-link { color: #212529; }
.home-link:hover { text-decoration: none; }
`]
})
export class HeaderComponent {
constructor(public auth: AuthService) {}
}

We'll add a few simple styles and import our AuthService to make its members publicly available to our header component's template.

Next open the header.component.html file and add:

<!-- src/app/core/header/header.component.html -->
<nav class="nav justify-content-between mt-2 mx-2 mb-3">
<div class="d-flex align-items-center">
<strong class="mr-1"><a routerLink="/" class="home-link">Popular Dogs ❤</a></strong>
</div>
<div class="ml-3">
<small *ngIf="auth.loading" class="loading">
Logging in...
</small>
<ng-template [ngIf]="!auth.loading">
<button
*ngIf="!auth.loggedIn"
class="btn btn-primary btn-sm"
(click)="auth.login()">Log In</button>
<span *ngIf="auth.loggedIn">
<img [src]="auth.userProfile.picture">
<small>{{ auth.userProfile.name }}</small>
<button
class="btn btn-danger btn-sm"
(click)="auth.logout()">Log Out</button>
</span>
</ng-template>
</div>
</nav>

The header now shows:

  • The name of our app (“Popular Dogs”) with a link to the / route
  • A login button if the user is not authenticated
  • A “Logging in…” message if the user is currently authenticating
  • The user's picture, name, and a logout button if the user is authenticated

Now that we have our header component built, we need to display it in our app.

Open the app.component.html file and add:

<!-- src/app/app.component.html -->
<app-header></app-header>
<div class="container">
<router-outlet></router-outlet>
</div>

The header component will now be displayed in our app with the current routed component showing beneath it. Check it out in the browser and try logging in!

Dog and DogDetail Models

Let's implement our dog.ts and dog-detail.ts interfaces . These are models that specify types for the shape of values that we'll use in our app. Using models ensures that our data has the structure that we expect.

We'll start with the dog.ts interface:

// src/app/core/dog.ts
export interface Dog {
breed: string;
rank: number;
image: string;
}

Next let's implement the dog-detail.ts interface:

// src/app/core/dog-detail.ts
export interface DogDetail {
breed: string;
rank: number;
description: string;
personality: string;
energy: string;
group: string;
image: string;
link: string;
}

API Service

With our Node API and models in place, we're ready to implement the service that will call our API in the Angular front end.

Open the api.service.ts file and add this code:

// src/app/core/api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { environment } from './../../environments/environment';
import { AuthService } from './../auth/auth.service';
import { Observable } from 'rxjs/Observable';
import { catchError } from 'rxjs/operators';
import 'rxjs/add/observable/throw';
import { Dog } from './../core/dog';
import { DogDetail } from './../core/dog-detail';
@Injectable()
export class ApiService {
private _API = `${environment.apiRoot}api`;
constructor(
private http: HttpClient,
private auth: AuthService) { }
getDogs$(): Observable<Dog[]> {
return this.http
.get(`${this._API}/dogs`)
.pipe(
catchError((err, caught) => this._onError(err, caught))
);
}
getDogByRank$(rank: number): Observable<DogDetail> {
return this.http
.get(`${this._API}/dog/${rank}`, {
headers: new HttpHeaders().set('Authorization', `Bearer ${this.auth.accessToken}`)
})
.pipe(
catchError((err, caught) => this._onError(err, caught))
);
}
private _onError(err, caught) {
let errorMsg = 'Error: Unable to complete request.';
if (err instanceof HttpErrorResponse) {
errorMsg = err.message;
if (err.status === 401 || errorMsg.indexOf('No JWT') > -1 || errorMsg.indexOf('Unauthorized') > -1) {
this.auth.login();
}
}
return Observable.throw(errorMsg);
}
}

We'll add the necessary imports to handle HTTP in Angular along with the environment configuration, AuthService , RxJS imports, and Dog and DogDetail models we just created. We'll set up private members for the _API and to store the _accessToken , then make the HttpClient and AuthService available privately to our API service.

Our API methods will return observables that emit one value when the API is either called successfully or an error is thrown. The getDogs$() stream returns an observable with an array of objects that are Dog -shaped. The getDogByRank$(rank) stream requires a numeric rank to be passed in, and will then call the API to retrieve the requested Dog 's data. This API call will send an Authorization header containing the authenticated user's access token.

Finally, we'll create an error handler that checks for errors and assesses if the user is not authenticated and prompts for login if so. The observable will then terminate with an error.

Note: We are using arrow functions to pass parameters to our handler functions for RxJS pipeable operators (such as catchError ). This is done to preserve the scope of the this keyword (see the “No separate this ” section of the MDN arrow functions documentation ).

下一步

We've already accomplished a lot in the first part of our tutorial series. In the next part, we'll finish our Popular Dogs application. In the meantime, here are some additional resources that you may want to check out:

Angular Testing Resources

If you're interested in learning more about testing in Angular, which we did not cover in this tutorial, please check out some of the following resources:

  • Angular – Testing
  • Angular Testing In Depth: Services
  • Angular Testing In Depth: HTTP Services
  • Angular Testing In Depth: Components
  • How to correctly test Angular 4 application with Auth0 integration

其他资源

You can find more resources on Firebase, Auth0, and Angular here:

  • Firebase documentation
  • Cloud Firestore documentation
  • angularfire2 documentation
  • Auth0 documentation
  • Auth0 pricing and features
  • Angular documentation
  • Angular CLI
  • Angular Cheatsheet

In the next installment of our Auth0 + Firebase + Angular tutorial, we'll display data from our dogs API and learn how to set up and implement realtime comments with Firebase ! Check out Authenticating Firebase and Angular with Auth0: Part 2 now.

翻译自: https://www.sitepoint.com/authenticating-firebase-angular-auth0-1/

firebase auth

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Angularjs Form表单验证机制

    个人总结 one: 一般来讲表单可能遇到的问题1.如何数据绑定2.验证表单3.显示出错信息4.整个form的验证5.避免提交没有验证通过的表单6.防止多次提交two: input表单验证需要的属性* name 名字(这里需要注意:不光是input元素需要,最外层的form标签也需要一个name属性)* ng-mode…...

    2024/4/19 7:49:11
  2. 近期1年来PHP面试题整理

    面试&#xff1a;冲击月薪18k(税后),你应该具备哪些技能? 1.熟悉设计模式,单例,工厂,策略,观察者能根据实际场景写出代码 2.熟悉框架tp,yii,larval,symfony,Phalcon7;至少读过其中之一的源码 3.熟悉memcache,redis的使用,特别是redis,熟悉redis的主从配置;熟悉mongodb 4.熟练掌…...

    2024/4/14 1:23:22
  3. angularjs指令_AngularJS指令实用指南–第二部分

    angularjs指令本教程的第一部分提供了AngularJS指令的基本概述。 在本教程的最后&#xff0c;我们还学习了如何隔离指令的范围。 本文将准确介绍第一部分的结尾。 首先&#xff0c;我们将看到如何在保持隔离的作用域的同时在指令内部访问父作用域的属性。 接下来&#xff0c;我…...

    2024/4/17 5:17:32
  4. angular 模块构建_使用Angular和Elasticsearch构建食谱搜索网站

    angular 模块构建您是否曾经想在应用程序中构建搜索功能&#xff1f; 在过去&#xff0c;如果您很幸运&#xff0c;您可能会发现自己与Solr纠缠不清 &#xff0c;或者在Lucene之上构建了自己的搜索服务。 但是&#xff0c;自2010年以来&#xff0c;有一种更简单的方法&#xff…...

    2024/4/14 1:23:22
  5. (转载)从Java角度理解Angular之入门篇:npm, yarn, Angular CLI

    本系列从Java程序员的角度&#xff0c;带大家理解前端Angular框架。 本文是入门篇。笔者认为亲自动手写代码做实验&#xff0c;是最有效最扎实的学习途径&#xff0c;而搭建开发环境是学习一门新技术最需要先学会的技能&#xff0c;是入门的前提。 作为入门篇&#xff0c;本文重…...

    2024/4/16 3:12:42
  6. 【repost】前端学习总结(二十三)——前端框架天下三分:Angular React 和 Vue的比较...

    目录(?)[] 前端这几年的技术发展很快&#xff0c;细分下来&#xff0c;主要可以分成四个方面&#xff1a; 1.开发语言技术&#xff0c;主要是ES6&7&#xff0c;coffeescript&#xff0c;typescript等&#xff1b; 2.开发框架&#xff0c;如Angular&#xff0c;React,Vue.j…...

    2024/4/14 1:23:37
  7. angularjs directive 实例 详解

    angularjs directive 实例 详解 张映 发表于 2014-03-13 前面提到了angularjs的factory,service,provider&#xff0c;这个可以理解成php的model&#xff0c;这种model是不带html的&#xff0c;今天所说的directive,也可以理解成php的model&#xff0c;也可以理解成插件&a…...

    2024/4/9 19:29:40
  8. PHP 面试知识点整理归纳

    全文已整理补充完毕&#xff0c;以后还会继续更新文章里面的错误&#xff0c;以及补充尚不完善的问题。 该篇文章是针对Github上wudi/PHP-Interview-Best-Practices-in-China资源的答案个人整理 lz也是初学者&#xff0c;以下知识点均为自己整理且保持不断更新&#xff0c;也希…...

    2024/4/18 2:01:54
  9. 利用基于angularjs的angular-file-upload.min.js控件实现上传文件

    参考博客: https://blog.csdn.net/zcl_love_wx/article/details/51692819 相关网址: 官网&#xff1a;http://www.bootcdn.cn/angular-file-upload/readme/ API &#xff1a; https://github.com/nervgh/angular-file-upload/wiki/Module-API https://segmentfault.…...

    2024/4/16 8:58:05
  10. 前端框架天下三分:Angular React 和 Vue的比较

    前端这几年的技术发展很快&#xff0c;细分下来&#xff0c;主要可以分成四个方面&#xff1a; 1.开发语言技术&#xff0c;主要是ES6&7&#xff0c;coffeescript&#xff0c;typescript等&#xff1b; 2.开发框架&#xff0c;如Angular&#xff0c;React,Vue.js&#xff…...

    2024/4/14 14:27:14
  11. angular依赖注入_依赖注入:Angular vs. RequireJS

    angular依赖注入如果您以前曾经构建过大型JavaScript应用程序&#xff0c;那么您将面临管理组件依赖项的任务。 您可以将组件视为功能块。 它可以是函数&#xff0c;对象或实例。 该块选择公开一个或多个公共方法。 它还可以选择隐藏非公共功能。 在本文中&#xff0c;我们将研…...

    2024/4/14 1:23:47
  12. angular、vue、react的区别

    前端这几年的技术发展很快&#xff0c;细分下来&#xff0c;主要可以分成四个方面&#xff1a; 1.开发语言技术&#xff0c;主要是ES6&7&#xff0c;coffeescript&#xff0c;typescript等&#xff1b; 2.开发框架&#xff0c;如Angular&#xff0c;React,Vue.js&#xff0…...

    2024/4/15 22:31:28
  13. angularjs中post请求进行跨域

    post请求进行跨域angularjs内置封装了类ajax的网络服务$http,所以实现了依赖外部插件来完成完整的前后端分离方案$scope.main {getData: function () {$http({method: POST,url: http://localhost:8000,headers: {Content-Type : application/x-www-form-urlencoded},data: {m…...

    2024/3/23 19:55:44
  14. angular用$http请求数据

    项目中用到$http请求数据接口像jQuery里的$ajax一样分为post请求和get请求. get请求: $http({ url:url1, //接口地址method: get }).success(function(data){ //请求成功console.log("成功"); }).error(function…...

    2024/4/8 4:22:21
  15. angular 接口请求不到数据的问题

    最近在用ng5升级ext的老项目&#xff0c;发现post请求一直请求不到数据&#xff0c;最终发现原因是数据格式的问题。 服务器接口接收的是form表单格式&#xff0c;而前端post请求发送的是json数据格式。比如下面这个请求&#xff1a; this.http.post(${this.uri}/data/list.jso…...

    2024/3/23 19:55:42
  16. Angular 请求数据

    Angular 请求数据 get post 以及 jsonp 请求数据 引入 HttpModule 、JsonpModule 普通的 HTTP 调用并不需要用到 JsonpModule&#xff0c;不过稍后我们就会延演示对 JSONP 的支持&#xff0c;所以现在就加载它&#xff0c;免得再回来浪费时间。 引入模块 import { HttpClientJs…...

    2024/4/18 23:19:20
  17. 【前端】10. Angular 中的数据交互(get jsonp post )

    参考视频&#xff1a;https://www.bilibili.com/video/av50917863?p12 Angular8视频教程-IT营配套笔记如下&#xff08;参考自大地老师&#xff09;&#xff1a; 一、Angular get 请求数据 Angular5.x 以后 get、post 和和服务器交互使用的是 HttpClientModule 模块。 1、…...

    2024/4/19 23:05:11
  18. Angular中页面传参获取参数

    今天使用html传参数&#xff0c;始终获取不到参数值。研究了半天&#xff0c;终于解决&#xff0c;以下是angular获取页面传参参数方法在angular中有一项服务为$location&#xff0c;使用这项服务可以获取页面参数&#xff0c;$location的方法不止这一个&#xff0c;还可以获取…...

    2024/4/15 5:09:29
  19. AngularJs 自定义指令中的Scope属性

    1.应用场景 了解AngularJs 自定义指令中的Scope属性的用法. 2.学习/操作 一、概念说明   可以是true、 false 、哈希对象 {}   1、true     新创建了一个作用域&#xff0c;且继承了父作用域&#xff1b;在初始化的时候&#xff0c;用了父作用域的属性和方法去填充我们…...

    2024/4/14 9:24:44
  20. Angular8 发布与订阅 解决子组件与父组件传值问题

    本文主要作用是解决子组件向父组件传值&#xff0c;Output()方法不适用得情况&#xff0c;也可以作为主流传值手段使用。 其他子父组件传值也可以通过浏览器的储存机制来处理&#xff0c;例如&#xff1a;sessionStorage, localstorage等来解决&#xff0c;不过比起订阅模式来说…...

    2024/4/15 9:38:08

最新文章

  1. uni-app中页面生命周期与vue生命周期的执行顺序对比

    应用生命周期 uni-app 支持如下应用生命周期函数&#xff1a; 函数名说明平台兼容onLaunch当uni-app 初始化完成时触发&#xff08;全局只触发一次&#xff09;&#xff0c;参数为应用启动参数&#xff0c;同 uni.getLaunchOptionsSync 的返回值onShow当 uni-app 启动&#x…...

    2024/4/20 6:24:07
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. MySQL 底层数据结构 聚簇索引以及二级索引 Explain的使用

    数据结构 我们知道MySQL的存储引擎Innodb默认底层是使用B树的变种来存储数据的 下面我们来复习一下B树存储 B树存储 哈希存储的区别 哈希存储,只能使用等值查询 B树与B树存储 我们知道B树实际上就是B树的变种 那么为啥使用B树而不是使用B树呢? 我们知道效率的高低主要取决于…...

    2024/4/18 20:00:53
  4. Linux中的shell脚本之流程控制循环遍历

    3 条件判断 4 流程控制语句 1&#xff09;if 语句 案例&#xff0c;用户输入用户名和密码&#xff0c;判断用户名是否是admin,密码是否是123,如果正确&#xff0c;则显示登录成功 首先我创建了shell文件&#xff0c;touch getpawer 其中getpawer 是我自己命的名 #!/bin/bas…...

    2024/4/20 1:17:27
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/19 14:24:02
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/19 18:20:22
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/19 11:57:31
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/19 11:57:31
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/19 11:57:52
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/19 11:57:53
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/19 11:58:14
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/19 11:58:20
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/19 23:45:49
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/19 11:58:39
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/19 11:58:51
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/20 3:12:02
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/19 11:59:15
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/19 11:59:23
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/19 11:59:44
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/19 11:59:48
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/19 12:00:06
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/19 16:57:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/19 12:00:25
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/19 12:00:40
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57