使用Angular 11和Node.js的Json Web Auth入门教程

125 阅读6分钟

使用Angular 11和Node.js的Json Web Auth入门

JSON Web Token(JWT)是一个RFC标准,它可以确保客户端和服务器之间以JSON对象形式传输的数据是安全的。这些信息也是经过数字签名的。这意味着它可以被验证和签署。

JWTs是使用秘钥或使用RSA/ECDSA算法的公钥或私钥对签署的。在本指南中,我们将使用秘钥。

前提条件

要跟上本教程,你将需要。

  • 对[Node.js]、[Angular 11]和[JWT]有一定了解。
  • 一个文本编辑器。我将使用[Visual Studio Code]。
  • 安装有相关库的[Node.js]。
  • 一个网络浏览器。我将使用[谷歌浏览器]。
  • 在你的机器上安装[Postman]。

使用JSON网络令牌的原因

使用JWT的主要原因是,它可以确保各方之间交换的信息的完整性。在这种情况下,一个客户和一个服务器。然后,数据可以被验证和授权。会话在令牌验证不成功时根据时间过期。

该应用程序如何工作

该应用程序将使用Angular作为前端,Node.js作为服务器,即后端。在前端,我将创建一个拦截器。所有从前端发出的HTTP请求都会被拦截器分解和复制。然后,在发送前给它添加一个token。

所有收到的请求都会被分解、克隆,提取出一个令牌,并在后端进行验证。验证成功后,请求被发送到其处理程序,并发送一个响应。验证失败后,其他请求被拒绝,401 Unauthorized 状态被送回Angular。

在这里,所有的请求都被检查为401状态。如果存在这样的请求,存储的令牌将被删除。然后,用户被签出所有会话,并被送回登录页面。

创建Angular应用程序

创建一个基础文件夹并导航到该文件夹。

然后,使用终端运行以下命令。

$ ng new angFrontend

该命令将在你的基础文件夹中创建一个Angular应用程序,并有一个新的子文件夹,其名称与应用程序的名称相同。在这种情况下,angFrontend 。它带有一个典型的Angular应用所需的预装库。

创建一个拦截器

接下来,我们将创建拦截器。首先,浏览到创建angular应用程序的文件夹,运行以下命令来实现。

$ ng generate service AuthInterceptor

然后,浏览到src/app ,编辑文件app.module.ts ,使其看起来像下面的代码。我导入了用于HTTP调用的HTTP模块。我还把刚刚创建的拦截器变成了一个提供者,这样它就可以对所有的HTTP调用进行全局访问。

// the modules required by the app are imported here
import { BrowserModule } from '@angular/platform-browser'; // this ensures the application will run on the browser
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module'; // ensures the application have routing capabilities

import { AppComponent } from './app.component'; // made present for bootstrapping application on the launch

import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; // enables the application to communicate with the backend services
import { AuthInterceptorService } from './auth-interceptor.service'; // this will allow the app to automatically attach authorization information to requests
import { HomeComponent } from './home/home.component'; // implements the home route

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    
    HttpClientModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

接下来,编辑src/app/auth-interceptor.service.ts ,使其看起来像下图的代码。

import { Injectable } from '@angular/core'; // imports the class that provides local storage for token
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { catchError, filter, take, switchMap } from "rxjs/operators";
import { Observable, throwError } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class AuthInterceptorService implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    console.log("Interception In Progress"); // Interception Stage
    const token: string = localStorage.getItem('token'); // This retrieves a token from local storage
    req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });// This clones HttpRequest and Authorization header with Bearer token added
    req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
    req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
 
    return next.handle(req)
        .pipe(
           catchError((error: HttpErrorResponse) => {
                // Catching Error Stage
                if (error && error.status === 401) {
                    console.log("ERROR 401 UNAUTHORIZED") // in case of an error response the error message is displayed
                }
                const err = error.error.message || error.statusText;
                return throwError(error); // any further errors are returned to frontend                    
           })
        );
  }  
}

拦截阶段代码解释

在认证中,从服务器上获得的令牌被存储在本地。然后,它将从本地存储中检索,并将头信息httpRequest req ,一个克隆的和Authorisation, Bearer: token 头信息添加到其中。然后在httpRequest头中发送该令牌。

错误阶段代码解释

如果出现错误响应或401、402等错误状态,管道将帮助捕捉错误。由于错误的请求或未经授权的请求,用户将无法进行认证。在进一步的错误请求的情况下,调用中的错误会返回给前端。

创建后端

我将首先在基础文件夹中为服务器创建一个目录,并通过运行以下命令将其初始化为一个Node.js项目。

$ mkdir node_server
$ cd node_server
$ npm init –y

通过运行下面的命令来安装它们。

$ npm i -S express cors body-parser express-jwt jsonwebtoken

然后,创建一个新文件app.js ,并添加以下代码。

const express       = require('express')
const bodyParser    = require('body-parser');
const cors          = require('cors');
const jwt           = require('jsonwebtoken');
var expressJWT      = require('express-jwt');

const app           = express();
const port          = 3000;

app.use(cors());
app.options('*', cors());
app.use(bodyParser.json({limit: '10mb', extended: true}));
app.use(bodyParser.urlencoded({limit: '10mb', extended: true}));

app.get('/', (req, res) => {
    res.json("Hello World");
});

/* more code to be added later */

/* the listen function */
app.listen(port, function() {
    console.log("Listening to " + port);
});

接下来,我将创建一个生成令牌的路由。这就是用户认证的地方。一旦认证成功,就会发送一个令牌。在app.js 中的listen函数之前添加下面的代码。

// SECRET FOR JWT
let secret = 'some_secret'; // a secret key is set here

/* Create token to be used */
app.get('/token/sign', (req, res) => {
    var userData = {
        "name": "My Name",
        "id": "1234"
    }
    let token = jwt.sign(userData, secret, { expiresIn: '15s'})
    res.status(200).json({"token": token});
});

一旦路由被创建,它将存储用户数据,并对数据进行编码。解码后,它将返回用户数据。在这种情况下,只有用户名和ID,因为存储密码不是一个好的做法。

然后,运行应用程序,通过运行检查生成的令牌。

$ node app.js

我将使用Postman来测试/token/sign 路线,如下图所示。

first web token

到现在为止,我已经生成了第一个网络令牌。接下来,我将创建一个/path1 ,并使用我的JSON网络令牌来保护它。

对于这项任务,我将使用express-jwt 函数。

将下面的代码添加到app.js

app.use(expressJWT({ secret: secret, algorithms: ['HS256']})
    .unless( // This allows access to /token/sign without token authentication
        { path: [
            '/token/sign'
        ]}
    ));

接下来,我将使用Postman测试/path1 ,在头中没有发送令牌。

401 Error

该应用程序不允许我访问路径。访问路径会如期返回一个401 Unauthorized 。下面的代码将允许访问/path1 ,如果从/token/sign 获得的令牌被一起发送的话。

将这段代码添加到app.js

// upon successful token authentication, access to path1 is granted
app.get('/path1', (req, res) => {
    res.status(200)
        .json({
            "success": true,
            "msg": "Secret Access Granted"
        });
});

在添加承载令牌并访问/path1 ,成功信息显示如下所示。

success message

回到Angular,我将通过运行以下命令创建一个新的组件home

$ ng generate component home

然后我将修改位于src/apphome.component.ts ,使其看起来像下面的代码。

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

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

  readonly API_URL = 'http://localhost:3000';

  constructor(
    private http: HttpClient
  ) { }

  ngOnInit() {
  }

signIn() {
    this.http.get(this.API_URL + '/token/sign')
      .subscribe(
        (res) => {
          console.log(res);
          if (res['token']) {
            localStorage.setItem('token', res['token']); //token here is stored in a local storage
          }
        },
        (err) => {
          console.log(err);
        }
      );    
  }

  getPath() {
    this.http.get(this.API_URL + '/path1') //path1 is then requested    
      .subscribe(
        (res) => {
          console.log(res);
        },
        (err) => {
          console.log(err);
        }
      );       
  }
}

上面代码中的signIn() 函数,创建一个令牌并将其存储在本地存储中。getPath() 函数请求我们的Node.js应用程序的路径path1

然后,我将写一个简单的HTML页面来测试上面的两个函数。

同时,修改src/app/app-routing.module.ts ,使其看起来像下面的代码。这将把home 组件映射到主页路径(/)。

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
 { path: '', component: HomeComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

打开src/app/home/home.component.html ,添加下面的代码。

<h3>JSON Web Test</h3>

<button (click)="signIn()">Sign In</button>
<button (click)="getPath()">Get Path 1</button>

同时,用下面的代码替换模板文件src/app/home/app.component.html 的内容。这将加载我们的Html页面而不是默认的模板。

<router-outlet></router-outlet>

要运行Angular应用程序,请浏览项目文件夹并在终端上运行以下命令。

$ ng serve

该命令将编译整个Angular项目并在浏览器上打开页面。

F12 ,打开开发者控制台。然后,点击Sign In按钮,获得一个令牌。然后点击Get Path1按钮来访问路径,如下图所示。

web test success

为了清除存储在本地存储器中的令牌,我将刷新浏览器。

然后我将尝试访问路径,如下图所示。

web test error

我们得到一个401 Unauthorized 错误。这表明我们的应用程序现在是安全的。

结论

当使用JSON网络令牌时,应用程序也变得安全,因为服务和服务器与应用程序之间的任何通信也是安全的。本教程指导你如何在Angular 11和Node.js中实现JSON Web Token。这将帮助任何人确保他们的应用程序在生产中的安全。