angular 异步验证
此Angular身份验证文章最初发布在Auth0.com博客上 ,并经许可在此处重新发布。
在本教程中,我们将构建一个Angular应用程序,并使用基于令牌的身份验证和Auth0添加登录功能。
您可以从我们的GitHub存储库中查看完整的代码示例。
角度生态系统
AngularJS 1.x被认为是构建单页应用程序(SPA)的强大框架。 它做得很好,有些还不够,但总体上允许开发人员快速构建功能强大的应用程序。
AngularJS(1.x)是框架,而Angular是构建现代应用程序的完整平台。 除核心Angular库外,该平台还附带了一个名为Angular CLI的强大命令行界面(CLI),使开发人员可以轻松地搭建其应用程序并控制构建系统。 Angular Platform Server将服务器端渲染带入Angular应用程序。 Angular Material是Google Material Design的官方实现,它使开发人员可以轻松构建漂亮的应用程序。
我们的应用:每日特惠
我们今天正在构建的应用称为“每日优惠”。 每日交易应用程序显示各种产品的交易和折扣列表。 我们将提供任何人都可以看到的公开交易列表以及仅适用于注册成员的私人交易列表。 私人交易是注册会员专有的,希望会更好。
提供每日优惠
我们必须从某个地方获取日常交易。 让我们构建一个非常简单的Node.js后端来服务交易。 我们将提供一条可公开访问的路线,为公共交易提供服务,一条受保护的路线只能由经过身份验证的用户调用。 现在,我们将公开这两个路由,稍后再担心身份验证。 在下面看看我们的实现:
'use strict';
// Load dependencies
const express = require('express');
const app = express();
const cors = require('cors');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
// Public route
app.get('/api/deals/public', (req, res)=>{
let deals = [
// Array of public deals here
];
res.json(deals);
})
// Private route
app.get('/api/deals/private', (req,res)=>{
let deals = [
// Array of Private Deals here
];
res.json(deals);
})
app.listen(3001);
console.log('Serving deals on localhost:3001');
我们的服务器和正在构建的Angular应用程序都将需要Node.js和NPM ,因此请确保在继续之前已安装了它们。 查看GitHub存储库以获取我们的每日交易列表或创建自己的交易。 每笔交易的模型如下:
{
id: 1234,
name: 'Name of Product',
description: 'Description of Product',
originalPrice: 19.99, // Original price of product
salePrice: 9.99 // Sale price of product
}
当您对公共和私人交易感到满意时,请通过运行node server
启动node server
,然后导航到localhost:3001/api/deals/public
和localhost:3001/api/deals/private
以确保您可以看到列表您添加的交易。 接下来,让我们设置Angular前端。
角度前端设置
正式的Angular CLI是开始构建新Angular应用程序的最佳方法之一。 CLI可以处理初始应用程序的脚手架,添加其他组件,维护构建系统等等。 在本教程中,我们将使用CLI来构建初始应用程序。
如果尚未安装,请运行:
npm install @angular/cli -g
这将全局安装Angular CLI。 我们将使用ng
命令与CLI进行交互。 要创建一个新的应用程序,请选择一个目录并运行:
ng new ng2auth --routing --skip-tests
这将创建一个新的Angular应用程序,该应用程序具有路由功能,并且没有用于根组件的初始测试文件。 该应用程序将在当前目录中其自己的文件夹中创建,CLI将下载所有必需的npm软件包,并基本上为我们设置了所有内容。
一旦ng new
完成,请输入新目录并运行ng serve
命令,基于Webpack的构建系统将负责将我们的应用程序从TypeScript编译为JavaScript,并将在localhost:4200
上提供服务。 ng serve
命令还将启动实时同步过程,因此,每当我们进行更改时,我们的应用程序都会自动重新编译。
现在让我们进入localhost:4200
,以确保到目前为止一切正常。 如果您看到提示“应用程序正常运行!”的消息,那您就很高兴。 接下来,让我们检查一下Angular应用程序是如何搭建的。
ng new
命令搭建了我们的Angular应用并添加了许多文件。 其中许多我们现在可以忽略,例如e2e
文件夹,其中将包含我们的端到端测试。 打开src
目录。 在src
目录中,我们可以看到一些熟悉的文件,例如index.html
, styles.css
等。 打开app
目录。
app
目录包含我们的大部分应用程序。 默认情况下,向我们提供以下文件:
-
app.component.css
–包含我们的根组件CSS样式 -
app.component.html
–保留我们的根组件HTML视图 -
app.component.ts
–保留我们的根组件类的TypeScript逻辑 -
app.module.ts
–定义我们的全局应用程序依赖项 -
app-routing.module.ts
–定义我们的应用程序的路由。
我们编写的每个Angular组件至少都有一个*.component.ts
文件,其他都是可选的。 我们的应用程序将包含三个组件。 主组件或根组件,显示公共交易的组件和显示私人交易的组件。 对于我们的根组件,我们将内联模板和样式。 让我们进行以下编辑并运行以下CLI命令:
- 删除
app.component.css
和app.component.html
文件。 我们将在app.component.ts
文件中定义我们的根组件所需的app.component.ts
内容。 - 通过运行
ng gc public-deals --no-spec
创建public-deals
组件。 该组件将负责获取和显示公共交易数据。 - 通过运行
ng gc private-deals --no-spec
创建一个private-deals
组件。 该组件将负责获取和显示私人交易数据。 - 通过运行
ng gc callback --it --is --flat --no-spec
创建一个callback.component.ts
文件。 - 通过运行
ng g class deal --no-spec
创建deal
文件。 该文件将保存我们的deal
类,该类将使Angular知道deal
的结构。 - 通过运行
ng gs deal --no-spec
创建一个deal.service.ts
文件。 在这里,我们将添加从API获取和检索交易数据的功能。
![](https://i-blog.csdnimg.cn/blog_migrate/194ecc5f4460f331882d8cda975a6ff6.png)
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
注意: g
是generate
的快捷方式, c
和s
分别是component
和service
快捷方式。 因此, ng gc
等效于ng generate component
。 --no-spec
标志指示不应生成*.spec.ts
文件。 --it
和--is
标志代表“内联模板”和“内联样式”,而--flat
指示不应创建包含文件夹。
添加HTTP客户端模块
我们将在Angular应用中向我们的API发出HTTP请求。 为此,我们需要将正确的模块添加到我们的app.module.ts
文件中。 现在,通过导入HttpClientModule
并将其添加到我们的@NgModule的imports
数组中,如下所示:
// app.module.ts
...
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
...
],
imports: [
...,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
添加Bootstrap CSS
我们将使用Bootstrap来设置应用程序的样式,因此,将CSS包含在index.html
文件的<head>
,如下所示:
<!-- src/index.html -->
...
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
...
构建根组件
每个Angular应用程序都必须具有一个根组件。 我们可以随便命名,但重要的是我们有一个。 在我们的应用程序中, app.component.ts
文件将是我们的根组件。 让我们看一下该组件的实现。
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<div class="container">
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" routerLink="/dashboard">{{ title }}</a>
</div>
<ul class="nav navbar-nav">
<li>
<a routerLink="/deals" routerLinkActive="active">Deals</a>
</li>
<li>
<a routerLink="/special" routerLinkActive="active">Private Deals</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a>Log In</a>
</li>
<li>
<a>Log Out</a>
</li>
</ul>
</nav>
<div class="col-sm-12">
<router-outlet></router-outlet>
</div>
</div>
`,
styles: [
`.navbar-right { margin-right: 0px !important}`
]
})
export class AppComponent {
title = 'Daily Deals';
constructor() {}
}
我们已经创建了根组件。 我们添加了一个内联模板和一些内联样式。 我们尚未添加所有功能,因此每个用户都可以看到所有链接以及登录和注销按钮。 我们将等一下实现这些。 我们还将显示<router-outlet>
元素。 这是我们的路由组件将显示的位置。
路由
由于我们使用--routing
标志初始化了应用程序,因此已经为我们设置了路由体系结构。 让我们对其进行更新,以便默认情况下显示“交易”组件。 我们还将设置应用程序所需的所有路由。
打开app-routing.module.ts
文件并添加以下内容:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CallbackComponent } from './callback.component';
import { PublicDealsComponent } from './public-deals/public-deals.component';
import { PrivateDealsComponent } from './private-deals/private-deals.component';
const routes: Routes = [
{
path: '',
redirectTo: 'deals',
pathMatch: 'full'
},
{
path: 'deals',
component: PublicDealsComponent
},
{
path: 'special',
component: PrivateDealsComponent
},
{
path: 'callback',
component: CallbackComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
我们可以在浏览器中导航到localhost:4200
并查看显示的应用程序。 我们只会看到最上面的导航栏,并显示一条消息,提示“交易”组件有效,我们还不会看到太多。
交易类型
TypeScript允许我们定义对象的结构或类型。 这有很多有用的目的。 首先,如果定义对象的结构,则可以通过IntelliSense获取对象的所有数据。 通过了解我们正在处理的对象的数据结构或类型,我们还可以更轻松地测试我们的组件。
对于我们的应用程序,我们将创建一种这样的类型。 在deal.ts
文件中,我们将定义一种Deal类型。 让我们看看如何完成此任务。
// deal.ts
export class Deal {
id: number;
name: string;
description: string;
originalPrice: number;
salePrice: number;
}
现在我们可以在Angular应用程序中将对象声明为deal
类型。 这些对象将获得交易类型的所有属性和方法。 我们仅在此处定义属性; 我们没有任何方法。
公开和私人交易组件
公共交易和私人交易的组成部分非常相似。 实际上,两种实现之间的唯一区别是,一个实现将显示来自公共API的交易,另一个实现将显示来自私有API的交易。 为简便起见,我们仅显示组件实现之一。 让我们实现public-deals.component.ts
:
// public-deals.component.ts
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Deal } from '../deal';
// We haven't defined these services yet
import { AuthService } from '../auth.service';
import { DealService } from '../deal.service';
@Component({
selector: 'app-public-deals',
// We'll use an external file for both the CSS styles and HTML view
templateUrl: 'public-deals.component.html',
styleUrls: ['public-deals.component.css']
})
export class PublicDealsComponent implements OnInit, OnDestroy {
dealsSub: Subscription;
publicDeals: Deal[];
error: any;
// Note: We haven't implemented the Deal or Auth Services yet.
constructor(
public dealService: DealService,
public authService: AuthService) {
}
// When this component is loaded, we'll call the dealService and get our public deals.
ngOnInit() {
this.dealsSub = this.dealService
.getPublicDeals()
.subscribe(
deals => this.publicDeals = deals,
err => this.error = err
);
}
ngOnDestroy() {
this.dealsSub.unsubscribe();
}
}
我们将使用RxJS 订阅来订阅由HTTP请求创建的可观察对象(将在我们即将创建的Deal Service中定义),并在可用于设置publicDeals
成员的值后采取一些措施,或定义一个error
。 我们需要使用ngOnDestroy()
方法添加OnDestroy
生命周期挂钩,该方法在销毁组件时取消订阅,以防止内存泄漏。
接下来,让我们构建公共交易组件的视图。 我们将在public-deals.component.html
文件中进行此操作。 我们的观点将是HTML和Angular sugar的混合。 让我们看一下我们的实现。
<h3 class="text-center">Daily Deals</h3>
<!-- We are going to get an array of deals stored in the publicDeals variable. We'll loop over that variable here using the ngFor directive -->
<div class="col-sm-4" *ngFor="let deal of publicDeals">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ deal.name }}</h3>
</div>
<div class="panel-body">
{{ deal.description }}
</div>
<div class="panel-footer">
<ul class="list-inline">
<li>Original</li>
<li class="pull-right">Sale</li>
</ul>
<ul class="list-inline">
<li><a class="btn btn-danger">${{ deal.originalPrice | number }}</a></li>
<li class="pull-right"><a class="btn btn-success" (click)="dealService.purchase(deal)">${{ deal.salePrice | number }}</a></li>
</ul>
</div>
</div>
</div>
<!-- We are going to use the authService.isLoggedIn method to see if the user is logged in or not. If they are not logged in we'll encourage them to login, otherwise if they are authenticated, we'll provide a handy link to private deals. We haven't implemented the authService yet, so don't worry about the functionality just yet -->
<div class="col-sm-12" *ngIf="!authService.isLoggedIn">
<div class="jumbotron text-center">
<h2>Get More Deals By Logging In</h2>
</div>
</div>
<div class="col-sm-12" *ngIf="authService.isLoggedIn">
<div class="jumbotron text-center">
<h2>View Private Deals</h2>
<a class="btn btn-lg btn-success" routerLink="/special">Private Deals</a>
</div>
</div>
<!-- If an error occurs, we'll show an error message -->
<div class="col-sm-12 alert alert-danger" *ngIf="error">
<strong>Oops!</strong> An error occurred fetching data. Please try again.
</div>
最后,让我们添加一个自定义样式。 在public-deals.component.css
文件中添加以下内容:
.panel-body {
min-height: 100px;
}
这样可以确保每个产品在我们的页面上都能很好地显示。
我们的私人交易部分看起来非常相似。 为简便起见,我们将不显示脚手架。 稍后我们将介绍这些更改。 如果您想查看它的外观,可以从我们的GitHub repo中查看它。
访问我们的Deals API
在本教程的前面,我们编写了一个非常简单的API,它公开了两条路由。 现在,让我们编写一个将与这两个端点交互的Angular服务。 我们将在deal.service.ts
文件中进行此操作。 实现如下:
// deal.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { catchError } from 'rxjs/operators';
import 'rxjs/add/observable/throw';
@Injectable()
export class DealService {
// Define the routes we are going to interact with
private publicDealsUrl = 'http://localhost:3001/api/deals/public';
private privateDealsUrl = 'http://localhost:3001/api/deals/private';
constructor(private http: HttpClient) { }
// Implement a method to get the public deals
getPublicDeals() {
return this.http
.get(this.publicDealsUrl)
.pipe(
catchError(this.handleError)
);
}
// Implement a method to get the private deals
getPrivateDeals() {
return this.http
.get(this.privateDealsUrl)
.pipe(
catchError(this.handleError)
);
}
// Implement a method to handle errors if any
private handleError(err: HttpErrorResponse | any) {
console.error('An error occurred', err);
return Observable.throw(err.message || err);
}
// Create a shared method that shows an alert when someone buys a deal
purchase(item) {
alert(`You bought the: ${item.name}`);
}
}
现在,您可以从我们的public-deals.component.ts
文件中看到getPublicDeals()
方法适合public-deals.component.ts
。 我们还编写了一个getPrivateDeals()
方法,该方法将获取我们的私人交易列表。 在private-deals.component.ts
文件中实现此方法。 最后,我们处理错误并实现在两个交易组件中使用的purchase()
方法。
创建此服务后,我们需要将其导入到我们的app.module.ts
文件中,并提供以下内容:
// app.module.ts
import { DealService } from './deal.service';
...
@NgModule({
...
providers: [
DealService
],
...
现在可以在我们的整个应用程序中使用该服务。
向您的Angular应用添加身份验证
导航到localhost:4200
,您应该会看到自动重定向到交易页面。 请注意,您可以自由导航至/special
路线并查看独家交易。 您可以执行此操作,因为我们尚未添加用户身份验证。 现在开始吧。
大多数应用程序需要某种类型的身份验证。 今天我们的应用程序没有什么不同。 在下一节中,我将向您展示如何以正确的方式向Angular应用程序添加身份验证。 我们将使用Auth0作为我们的身份平台。 我们将使用Auth0,因为它允许我们轻松地发布JSON Web令牌(JWT) ,但是我们将介绍的概念可以应用于任何基于令牌的身份验证系统。 如果您还没有Auth0帐户,请立即注册一个免费帐户。
在此处,单击“ API”菜单项 ,然后单击“ 创建API”按钮。 您需要为您的API提供一个名称和一个标识符。 名称可以是您选择的任何名称,因此请使其具有描述性。 该标识符将用于识别您的API,并且一旦设置此字段就无法更改。 在我们的示例中,我将命名该API Daily Deals API,并将其标识符设置为http://localhost:3001
。 我们将签名算法保留为RS256,然后单击“ 创建API”按钮。
这就是我们现在需要做的。 让我们使用我们创建的新API保护服务器的安全。
保护我们的服务器
在我们的Angular应用程序中的前端实现身份验证之前,让我们保护后端服务器。
首先,我们将安装依赖项:
npm install express-jwt jwks-rsa --save
打开位于server
目录中的server.js
文件,并进行以下编辑:
// server.js
'use strict';
const express = require('express');
const app = express();
// Import the required dependencies
const jwt = require('express-jwt');
const jwks = require('jwks-rsa');
const cors = require('cors');
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
// We're going to implement a JWT middleware that will ensure the validity of our token. We'll require each protected route to have a valid access_token sent in the Authorization header
const authCheck = jwt({
secret: jwks.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: "https://{YOUR-AUTH0-DOMAIN}.auth0.com/.well-known/jwks.json"
}),
// This is the identifier we set when we created the API
audience: '{YOUR-API-AUDIENCE-ATTRIBUTE}',
issuer: "{YOUR-AUTH0-DOMAIN}", // e.g., you.auth0.com
algorithms: ['RS256']
});
app.get('/api/deals/public', (req, res)=>{
let deals = [
// Array of public deals
];
res.json(deals);
})
// For the private route, we'll add this authCheck middleware
app.get('/api/deals/private', authCheck, (req,res)=>{
let deals = [
// Array of private deals
];
res.json(deals);
})
app.listen(3001);
console.log('Listening on localhost:3001');
这就是我们需要在服务器上完成的所有操作。 重新启动服务器,尝试导航到localhost:3001/api/deals/private
,您将看到一条错误消息,提示缺少授权标头。 我们的私有API路由现已安全。 让我们在Angular应用中实现身份验证。
向前端添加身份验证
登录到Auth0 管理仪表板,然后通过单击侧边栏中的“ 客户端”项对我们的客户端进行一些更新。 查找我们制作API时自动创建的测试客户端。 应该将其称为“ Daily Deals (Test Client)
。
将客户端类型更改为“ Single Page Application
。 然后将http://localhost:4200/callback
到Allowed Callback URLs字段。
最后,点击底部的高级设置链接,然后选择OAuth标签。 确保将JsonWebToken签名算法设置为RS256
。
记下客户ID ; 我们将需要它来设置用于Angular应用程序身份验证的配置。
Auth0.js库
现在我们需要安装auth0-js
库。 我们可以在Angular应用的根文件夹中这样做:
npm install auth0-js --save
Auth0环境配置
打开src/environments/environment.ts
文件,并使用以下信息将auth
属性添加到常量中:
// environment.ts
export const environment = {
production: false,
auth: {
clientID: 'YOUR-AUTH0-CLIENT-ID',
domain: 'YOUR-AUTH0-DOMAIN', // e.g., you.auth0.com
audience: 'YOUR-AUTH0-API-IDENTIFIER', // e.g., http://localhost:3001
redirect: 'http://localhost:4200/callback',
scope: 'openid profile email'
}
};
该文件提供了身份验证配置变量,因此我们可以使用Auth0保护前端。 确保从Auth0客户端和API设置中将YOUR-AUTH0-CLIENT-ID
, YOUR-AUTH0-DOMAIN
和YOUR-AUTH0-API-IDENTIFIER
为您自己的信息。
认证服务
接下来,我们将创建一个可在整个应用程序中使用的身份验证服务:
ng g s auth/auth --no-spec
这将在src/app/auth
创建一个新文件夹,其中包含auth.service.ts
文件。
打开此文件并将其修改为以下内容:
// auth.service.ts
import { Injectable } from '@angular/core';
import * as auth0 from 'auth0-js';
import { environment } from './../../environments/environment';
import { Router } from '@angular/router';
@Injectable()
export class AuthService {
// Create Auth0 web auth instance
auth0 = new auth0.WebAuth({
clientID: environment.auth.clientID,
domain: environment.auth.domain,
responseType: 'token',
redirectUri: environment.auth.redirect,
audience: environment.auth.audience,
scope: environment.auth.scope
});
// Store authentication data
userProfile: any;
accessToken: string;
authenticated: boolean;
constructor(private router: Router) {
// Check session to restore login if not expired
this.getAccessToken();
}
login() {
// Auth0 authorize request
this.auth0.authorize();
}
handleLoginCallback() {
// When Auth0 hash parsed, get profile
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken) {
window.location.hash = '';
this.getUserInfo(authResult);
} else if (err) {
console.error(`Error: ${err.error}`);
}
this.router.navigate(['/']);
});
}
getAccessToken() {
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken) {
this.getUserInfo(authResult);
} else if (err) {
console.log(err);
this.logout();
this.authenticated = false;
}
});
}
getUserInfo(authResult) {
// Use access token to retrieve user's profile and set session
this.auth0.client.userInfo(authResult.accessToken, (err, profile) => {
if (profile) {
this._setSession(authResult, profile);
}
});
}
private _setSession(authResult, profile) {
const expTime = authResult.expiresIn * 1000 + Date.now();
// Save authentication data and update login status subject
localStorage.setItem('expires_at', JSON.stringify(expTime));
this.accessToken = authResult.accessToken;
this.userProfile = profile;
this.authenticated = true;
}
logout() {
// Remove auth data and update login status
localStorage.removeItem('expires_at');
this.userProfile = undefined;
this.accessToken = undefined;
this.authenticated = false;
}
get isLoggedIn(): boolean {
// Check if current date is before token
// expiration and user is signed in locally
const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
return Date.now() < expiresAt && this.authenticated;
}
}
创建身份验证服务后,我们需要将其导入到我们的app.module.ts
文件中,并提供以下内容:
// app.module.ts
import { AuthService } from './auth/auth.service';
...
@NgModule({
...
providers: [
...,
AuthService
],
...
现在可以在我们的整个应用程序中使用该服务。
我们将使用Auth0登录页面来验证我们的用户。 这是验证用户身份并以OAuth兼容方式获取访问令牌的最安全方法。 创建了身份验证服务后,让我们继续构建身份验证工作流。
全方位角度认证
Angular路由器具有称为路由防护的强大功能,该功能使我们能够以编程方式确定用户是否可以访问路由。 例如,可以将Angular中的路由保护器与Express.js中的中间件进行比较。
我们将创建一个身份验证路由防护,它将在显示路由之前检查用户是否已登录。 通过运行以下CLI命令来创建新的防护:
ng g guard auth/auth --no-spec
打开生成的auth.guard.ts
文件并进行以下更改:
// 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';
import { Router } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private authService: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
if (!this.authService.isLoggedIn) {
this.router.navigate(['/']);
return false;
}
return true;
}
}
要在我们的路线中实现此路线防护,让我们继续打开app-routing.module.ts
文件。 在这里,我们将包括我们的身份验证保护服务,并在我们的秘密路线上启用它。 让我们看一下实现。
// app-routing.module.ts
...
// Import the AuthGuard
import { AuthGuard } from './auth/auth.guard';
const routes: Routes = [
...,
{
path: 'special',
component: PrivateDealsComponent,
// Add this to guard this route
canActivate: [
AuthGuard
]
},
...
];
@NgModule({
...,
// Add AuthGuard to the providers array
providers: [AuthGuard],
...
})
export class AppRoutingModule { }
这里的所有都是它的。 现在,我们的路由在路由级别受到保护。
您还记得吗,我们在交易组件中包含了AuthService
的存根。 由于现在已实施身份验证服务,因此我们的占位符功能将正常运行。 我们将看到根据用户状态显示的正确行为。
不过,由于我们没有在其中包含特定于身份验证的功能,因此我们需要更新根组件。 我是故意这样做的,所以我们可以逐行浏览示例。 接下来,让我们开始。
// app.component.ts
import { Component } from '@angular/core';
import { AuthService } from './auth/auth.service';
@Component({
selector: 'app-root',
template: `
<div class="container">
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" routerLink="/">{{ title }}</a>
</div>
<ul class="nav navbar-nav">
<li>
<a routerLink="/deals" routerLinkActive="active">Deals</a>
</li>
<li>
<a routerLink="/special" *ngIf="authService.isLoggedIn" routerLinkActive="active">Private Deals</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a *ngIf="!authService.isLoggedIn" (click)="authService.login()">Log In</a>
</li>
<li>
<a (click)="authService.logout()" *ngIf="authService.isLoggedIn">Log Out</a>
</li>
</ul>
</nav>
<div class="col-sm-12">
<router-outlet></router-outlet>
</div>
</div>
`,
styles: [
`.navbar-right { margin-right: 0px !important}`
]
})
export class AppComponent {
title = 'Daily Deals';
constructor(public authService: AuthService) {}
}
我们导入了AuthService
并将其在构造函数中公开可用(为了使模板使用其方法,它必须是public
)。
我们将*ngIf="authService.isLoggedIn
添加到我们的私人交易链接中,因此如果用户未登录,则不会呈现该链接。我们还向登录和注销链接中添加了*ngIf
逻辑,以根据用户的情况显示相应的链接身份验证状态:当用户现在单击登录链接时,他们将被带到Auth0域上的集中式登录页面,他们将在此处输入其凭据,如果正确,它们将被重定向回应用程序。
回调组件
现在,我们将对本教程开始时生成的回调组件进行编码。 当localhost:4200/callback
路由时,此组件将被激活,它将处理来自Auth0的重定向,并确保在成功通过身份验证后,我们在哈希中返回了正确的数据。 为此,该组件将利用我们之前创建的AuthService
。 让我们看一下实现:
// callback.component.ts
import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth/auth.service';
@Component({
selector: 'app-callback',
template: `
<p>
Loading...
</p>
`,
styles: []
})
export class CallbackComponent implements OnInit {
constructor(private authService: AuthService) { }
ngOnInit() {
this.authService.handleLoginCallback();
}
}
验证用户身份后,Auth0将重定向回我们的应用程序并调用/callback
路由。 Auth0还将访问令牌附加到此请求,并且我们的CallbackComponent将确保正确处理和存储令牌和配置文件。 如果一切正常,这意味着我们已收到访问令牌,我们将被重定向回主页,并处于登录状态。
更新交易服务
我们需要做一个最后的更新。 如果您现在尝试访问/special
路由,即使您已登录,也不会获得秘密交易列表。 这是因为我们没有将访问令牌传递给后端。 我们将不得不更新交易服务。
我们需要将调用更新为/api/deals/private
以包括我们的访问令牌。 我们需要导入HttpHeaders以将带有承载方案的authorization
标头附加到我们的请求。 我们还需要导入AuthService
以获得对accessToken
访问权限。 让我们看看如何在应用程序中实现这一点。
// deal.service.ts
...
// Import HttpHeaders
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
// Import AuthService
import { AuthService } from './auth/auth.service';
...
constructor(
private http: HttpClient,
private authService: AuthService
) { }
...
// Implement a method to get the private deals
getPrivateDeals() {
return this.http
.get(this.privateDealsUrl, {
headers: new HttpHeaders().set('Authorization', `Bearer ${this.authService.accessToken}`)
})
.pipe(
catchError(this.handleError)
);
}
我们将使用来自身份验证服务的令牌向我们的getPrivateDeals()
请求添加一个Authorization
标头。 现在,当在我们的API中对私有路由进行调用时,我们会自动将authService.accessToken
附加到该调用中。 让我们在下一部分中进行尝试,以确保它可以正常工作。
全部放在一起
而已。 现在,我们准备测试我们的应用程序。 如果您的Node.js服务器未运行,请确保先启动它。 转到localhost:4200
,您应该自动重定向到localhost:4200/deals
并查看公共交易列表。
接下来,单击登录屏幕,您将被重定向到Auth0域,并且将显示登录小部件。 登录或注册,您将被重定向回回调路由,然后再重定向到交易页面,但是现在用户界面看起来会稍有不同。 主菜单将为“私人交易”提供一个新选项,底部的消息还将向您显示私人交易的链接。 除了导航栏上的“登录”链接以外,还将显示“注销”链接。 最后,单击“私人交易”链接以查看我们的专有私人交易列表。
注意:由于我们在域中使用localhost
,因此,一旦用户首次登录,或者将来范围发生变化,就会显示一个同意对话框,询问用户是否希望授予对API的访问权限。 如果您使用的是非localhost域,并且该客户端是第一方客户端,则不会显示此同意对话框。
您刚刚编写并验证了Angular应用。 恭喜!
结论
在本教程中,我们研究了一些您可以编写Angular组件和服务的方法。 我们使用Auth0实现了基于令牌的身份验证。 但这只是表面。
Angular提供了许多出色的功能,例如管道,i18n等。 Auth0不仅可以提供最先进的身份验证,还可以提供增强的功能(如多因素身份验证 , 异常检测 , 企业联合 , 单点登录(SSO)等)来帮助保护Angular应用程序。 立即注册,以便您专注于构建应用程序独有的功能。
翻译自: https://www.sitepoint.com/authentication-angular-auth0/
angular 异步验证