使用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 路线,如下图所示。
到现在为止,我已经生成了第一个网络令牌。接下来,我将创建一个/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 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 ,成功信息显示如下所示。
回到Angular,我将通过运行以下命令创建一个新的组件home 。
$ ng generate component home
然后我将修改位于src/app 的home.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按钮来访问路径,如下图所示。
为了清除存储在本地存储器中的令牌,我将刷新浏览器。
然后我将尝试访问路径,如下图所示。
我们得到一个401 Unauthorized 错误。这表明我们的应用程序现在是安全的。
结论
当使用JSON网络令牌时,应用程序也变得安全,因为服务和服务器与应用程序之间的任何通信也是安全的。本教程指导你如何在Angular 11和Node.js中实现JSON Web Token。这将帮助任何人确保他们的应用程序在生产中的安全。