Angular 8 身份验证和授权


验证 是将 Web 应用程序的访问者与系统中预定义的一组用户身份进行匹配的过程。换句话说,它是识别用户身份的过程。就安全性而言,身份验证是系统中非常重要的过程。

授权 是授予用户访问系统中某些资源的权限的过程。只有经过身份验证的用户才能被授权访问资源。

让我们在本章学习如何在 Angular 应用程序中进行身份验证和授权。

路由中的守卫


在 Web 应用程序中,资源由 url 引用。系统中的每个用户都将被允许访问一组 url。例如,管理员可能会被分配到管理部分下的所有 url。

正如我们已经知道的那样,URL 由 Routing . Angular 路由可以根据编程逻辑保护和限制 url。因此,普通用户可能会拒绝 url,而管理员可能会允许 url。

Angular 提供了一个概念,称为 路由器卫士 它可以用来防止通过路由对应用程序的某些部分进行未经授权的访问。 Angular 提供了多个守卫,它们如下:

  • 可以激活 : 用于停止对某个路由的访问。

  • 可以激活孩子 : 用于停止对子路由的访问。

  • 可以停用 : 用于停止正在进行的进程从用户那里获取反馈。例如,如果用户回答是否定的,则可以停止删除过程。

  • Resolve : 用于在导航到路由前预取数据。

  • CanLoad : 用于加载资产。

工作示例

让我们尝试将登录功能添加到我们的应用程序并使用 CanActivate 保护来保护它。

打开命令提示符并转到项目根文件夹。

cd /go/to/expense-manager

启动应用程序。

ng serve

创建一个新服务 AuthService 来对用户进行身份验证。

ng generate service auth
CREATE src/app/auth.service.spec.ts (323 bytes)
CREATE src/app/auth.service.ts (133 bytes)

Open 身份验证服务 并包括以下代码。

import { Injectable } from '@angular/core';

import { Observable, of } from 'rxjs';
import { tap, delay } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
export class AuthService {

    isUserLoggedIn: boolean = false;

    login(userName: string, password: string): Observable {
        console.log(userName);
        console.log(password);
        this.isUserLoggedIn = userName == 'admin' && password == 'admin';
        localStorage.setItem('isUserLoggedIn', this.isUserLoggedIn ? "true" : "false");

    return of(this.isUserLoggedIn).pipe(
        delay(1000),
        tap(val => {
            console.log("Is User Authentication is successful: " + val);
        })
    );
    }

    logout(): void {
    this.isUserLoggedIn = false;
        localStorage.removeItem('isUserLoggedIn');
    }

    constructor() { }
}

Here,

  • 我们写了两个方法, login and logout .

  • 的目的 login 方法是验证用户,如果用户成功验证,它将信息存储在 本地存储 然后返回真。

  • 认证验证是用户名和密码应该是 admin.

  • 我们没有使用任何后端。相反,我们使用 Observables 模拟了 1 秒的延迟。

  • 的目的 logout 方法是使用户无效并删除存储在 本地存储。

Create a login 组件使用以下命令:

ng generate component login
CREATE src/app/login/login.component.html (20 bytes)
CREATE src/app/login/login.component.spec.ts (621 bytes)
CREATE src/app/login/login.component.ts (265 bytes)
CREATE src/app/login/login.component.css (0 bytes)
UPDATE src/app/app.module.ts (1207 bytes)

Open 登录组件 并包含以下代码:

import { Component, OnInit } from '@angular/core';

import { FormGroup, FormControl } from '@angular/forms';
import { AuthService } from '../auth.service';
import { Router } from '@angular/router';

@Component({
    selector: 'app-login',
    templateUrl: './login.component.html',
    styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

    userName: string;
    password: string;
    formData: FormGroup;

    constructor(private authService : AuthService, private router : Router) { }

    ngOnInit() {
        this.formData = new FormGroup({
            userName: new FormControl("admin"),
            password: new FormControl("admin"),
        });
    }

    onClickSubmit(data: any) {
        this.userName = data.userName;
        this.password = data.password;

        console.log("Login page: " + this.userName);
        console.log("Login page: " + this.password);

        this.authService.login(this.userName, this.password)
            .subscribe( data => {
                console.log("Is Login Success: " + data);
      
           if(data) this.router.navigate(['/expenses']); 
        });
    }
}

Here,

  • 使用反应形式。

  • 导入 AuthService 和 Router 并在构造函数中配置它。

  • 创建了一个 FormGroup 实例并包含了两个 FormControl 实例,一个用于用户名,另一个用于密码。

  • 创建了一个 onClickSubmit 以使用 authService 验证用户,如果成功,则导航到费用列表。

Open 登录组件 模板并包含以下模板代码。

<!-- Page Content -->
<div class="container">
    <div class="row">
        <div class="col-lg-12 text-center" style="padding-top: 20px;">
            <div class="container box" style="margin-top: 10px; padding-left: 0px; padding-right: 0px;">
                <div class="row">
                    <div class="col-12" style="text-align: center;">
                                                <form [formGroup]="formData" (ngSubmit)="onClickSubmit(formData.value)"
                                                        class="form-signin">
                                                <h2 class="form-signin-heading">Please sign in</h2>
                                                <label for="inputEmail" class="sr-only">Email address</label>
                                                <input type="text" id="username" class="form-control"
                                                        formControlName="userName" placeholder="Username" required autofocus>
                                                <label for="inputPassword" class="sr-only">Password</label>
                                                <input type="password" id="inputPassword" class="form-control"
                                                        formControlName="password" placeholder="Password" required>
                                                <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
                                                </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Here,

创建了一个反应式表单并设计了一个登录表单。

附上 点击提交 表单提交操作的方法。

Open 登录组件 样式并包含以下 CSS 代码。

.form-signin {
    max-width: 330px;

    padding: 15px;
    margin: 0 auto;
}

input {
    margin-bottom: 20px;
}

在这里,添加了一些样式来设计登录表单。

使用以下命令创建注销组件:

ng generate component logout
CREATE src/app/logout/logout.component.html (21 bytes)
CREATE src/app/logout/logout.component.spec.ts (628 bytes)
CREATE src/app/logout/logout.component.ts (269 bytes)
CREATE src/app/logout/logout.component.css (0 bytes)
UPDATE src/app/app.module.ts (1368 bytes)

Open 注销组件 并包括以下代码。

import { Component, OnInit } from '@angular/core';

import { AuthService } from '../auth.service';
import { Router } from '@angular/router';

@Component({
    selector: 'app-logout',
    templateUrl: './logout.component.html',
    styleUrls: ['./logout.component.css']
})
export class LogoutComponent implements OnInit {

    constructor(private authService : AuthService, private router: Router) { }

    ngOnInit() {
        this.authService.logout();
        this.router.navigate(['/']);
    }

}

Here,

  • 使用 AuthService 的注销方法。
  • 用户注销后,页面将重定向到主页 (/)。

使用以下命令创建守卫:

ng generate guard expense
CREATE src/app/expense.guard.spec.ts (364 bytes)
CREATE src/app/expense.guard.ts (459 bytes)

打开 ExpenseGuard 并包含以下代码:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

import { AuthService } from './auth.service';

@Injectable({
    providedIn: 'root'
})
export class ExpenseGuard implements CanActivate {

    constructor(private authService: AuthService, private router: Router) {}

    canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): boolean | UrlTree {
        let url: string = state.url;

          return this.checkLogin(url);
        }

        checkLogin(url: string): true | UrlTree {
            console.log("Url: " + url)
            let val: string = localStorage.getItem('isUserLoggedIn');

            if(val != null && val == "true"){
                if(url == "/login")
                    this.router.parseUrl('/expenses');
                else
                    return true;
            } else {
                return this.router.parseUrl('/login');
            }
        }
}

Here,

  • checkLogin 会检查localStorage 是否有用户信息,如果可用则返回true。
  • 如果用户登录并进入登录页面,它会将用户重定向到费用页面
  • 如果用户未登录,则用户将被重定向到登录页面。

Open AppRoutingModule (src/app/app-routing.module.ts) 并更新以下代码:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ExpenseEntryComponent } from './expense-entry/expense-entry.component';
import { ExpenseEntryListComponent } from './expense-entry-list/expense-entry-list.component';
import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component';

import { ExpenseGuard } from './expense.guard';

const routes: Routes = [
    { path: 'login', component: LoginComponent },
    { path: 'logout', component: LogoutComponent },
    { path: 'expenses', component: ExpenseEntryListComponent, canActivate: [ExpenseGuard]},
    { path: 'expenses/detail/:id', component: ExpenseEntryComponent, canActivate: [ExpenseGuard]},
    { path: '', redirectTo: 'expenses', pathMatch: 'full' }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Here,

  • 导入 LoginComponent 和 LogoutComponent。
  • 进口费用卫士。
  • 创建了两个新路由 login 和 logout 来分别访问 LoginComponent 和 LogoutComponent。
  • 为 ExpenseEntryComponent 和 ExpenseEntryListComponent 添加新选项 canActivate。

Open 应用组件 模板并添加两个登录和注销链接。

<div class="collapse navbar-collapse" id="navbarResponsive">
    <ul class="navbar-nav ml-auto">
        <li class="nav-item active">
            <a class="nav-link" href="#">Home
                <span class="sr-only" routerLink="/">(current)</span>

            </a>
        </li>
        <li class="nav-item">
            <a class="nav-link" routerLink="/expenses">Report</a>
        </li>
        <li class="nav-item">
            <a class="nav-link" href="#">Add Expense</a>
        </li>
        <li class="nav-item">

            <a class="nav-link" href="#">About</a>
        </li>
        <li class="nav-item">
                        <div *ngIf="isUserLoggedIn; else isLogOut">
                                <a class="nav-link" routerLink="/logout">Logout</a>
                        </div>

                        <ng-template #isLogOut>
                                        <a class="nav-link" routerLink="/login">Login</a>
                        </ng-template>
        </li>
    </ul>
</div>

Open 应用组件 并更新以下代码:

import { Component } from '@angular/core';

import { AuthService } from './auth.service';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {

    title = 'Expense Manager';
    isUserLoggedIn = false;

    constructor(private authService: AuthService) {}

    ngOnInit() {
        let storeData = localStorage.getItem("isUserLoggedIn");
        console.log("StoreData: " + storeData);

        if( storeData != null && storeData == "true")
            this.isUserLoggedIn = true;
        else


            this.isUserLoggedIn = false;
    }
}

在这里,我们添加了识别用户状态的逻辑,以便我们可以显示登录/注销功能。

Open AppModule (src/app/app.module.ts) 并配置 反应式表单模块

import { ReactiveFormsModule } from '@angular/forms'; 
imports: [ 
    ReactiveFormsModule
]

现在,运行应用程序,应用程序将打开登录页面。

ReactiveFormsModule

输入 admin 和 admin 作为用户名和密码,然后单击提交。应用程序处理登录并将用户重定向到费用列表页面,如下所示:

FormsModule

最后,你可以单击注销并退出应用程序。