65.9K
CodeProject 正在变化。 阅读更多。
Home

MEAN 栈结合 Angular 4、Auth0 认证和 JWT 授权 - 第三部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017年9月10日

CPOL

17分钟阅读

viewsIcon

27150

downloadIcon

360

使用 Auth0 和 JWT 进行身份验证和授权。

文章系列

引言

在之前的文章中,我们开发了使用 mLab 网站上的 MongoDB 数据库进行用户管理的 Expressjs API,以及与 Express API 通信并提供加载、添加、更新和删除用户视图的 Angular 客户端应用程序。

在本文中,我们将增强我们的应用程序,并使用 Auth0 客户端和 JWT 添加认证和授权。也就是说,用户需要登录才能在用户管理页面执行 CRUD 操作,并且 Expressjs API 只能由我们通过 JWT 实现的可信源访问。Angular 客户端将发送一些哈希字符串与所有 HTTP 请求一起,这将在 Expressjs 端进行验证。

背景

本文是 MEAN 栈结合 Angular 4、Auth0 认证和 JWT 授权 - 第二部分 的第三部分,强烈建议您在开始阅读本文之前阅读 第一部分第二部分。如果不阅读前两部分,您可能无法完全理解本文。

设置运行项目

(请按照相同步骤使附件中的解决方案可运行)

  1. MEAN 栈结合 Angular 4、Auth0 认证和 JWT 授权 - 第二部分 下载附件的项目,并按照以下步骤使其可运行
    1. 下载附件的源项目。将其解压到您的计算机,然后在 Visual Studio Code 中打开 mean-example 文件夹。
    2. 在 EXPLORER 面板中,右键单击 client - > UserManagement 文件夹,然后选择 在终端中打开 选项。
    3. 在终端中,运行命令:npm install,等待命令完成以完全下载所有包。
    4. 接下来,在同一个终端中,运行命令:ng build 来构建 Angular 项目,并将构建结果保存在 dist 文件夹中。
    5. 客户端应用程序准备就绪后,在 EXPLORER 面板中右键单击 server 文件夹。选择 在终端中打开 选项。在终端中,运行命令:npm install
    6. 成功下载包后,编辑文件 server -> app.js。 更新 mLab 网站上的 MongoDB URL(在第一篇 文章 中创建)。
    7. 在同一个终端中,运行命令:node app
    8. 打开浏览器(Firefox 或 Chrome),输入 URL https://:3000,您的应用程序就可以使用了。

创建 Auth0 客户端

  1. 访问 Auth0 网站,并使用我们在第一篇 文章 中创建的凭据登录。我们需要做的第一步是创建 Client。简单来说,Client 就是我们即将开发的应用程序的账户。它包含了实现 Auth0 认证在应用程序中所需的所有设置和配置。

  2. 登录 Auth0 网站后,您会在左侧看到 Clients 链接,点击它

  3. 接下来,点击右侧的 + NEW CLIENT 按钮,您将进入 Create Client 页面,输入应用程序名称 Mean Example,然后选择客户端类型 Single Page Web Applications。点击 Create 按钮。

  4. 在下一个屏幕中,选择 Angular 2+,因为我们的 Web 应用程序是用 Angular 4 开发的。

  5. 下一页很重要。有四个标签页 - Quick StartsSettingsAddonsConnections。目前,我们只使用前两个标签页。Quick Start 标签页非常棒,它提供了可直接使用的 Angular 项目,其中包含已配置好 clientIDdomaincallbackURLauth 服务,以及关于下载和配置内容的简要教程。下载附件的项目。

  6. 点击第二个标签页 Settings,此标签页包含我们需要正确配置我们的应用程序与 Auth0 客户端所需的信息。我们将在本文后续所需的地方使用这些设置。

  7. 滚动到 Settings 标签页,并在 Allowed Callback URLs 文本框中添加 https://:3000/login。我们在此指定登录成功后控制权应该去往哪里,您将在后续步骤中了解更多。

  8. 将下载的项目 01- login.zip 解压到您的计算机。打开 Visual Studio Code,然后打开包含项目文件的解压后的文件夹 01- login。在 src -> app -> auth 文件夹中,您会找到 auth.service.ts 文件,其中包含 LoginLogoutisAuthenticated 函数所需的所有代码。我们将从这里复制所需的函数到我们的应用程序中,稍作修改并尝试理解它。

认证实现

  1. 配置好 Auth0 客户端后,我们回到我们的用户管理应用程序。根据 Quick Start 教程配置 Auth0 客户端的第一步是安装 auth0-js,所以右键单击 client -> UserManagement 文件夹并选择 在终端中打开 选项(或者直接在终端中 cdUserManagement 文件夹)。

  2. 在终端中输入命令:npm install --save auth0-js,然后按 Enter 键。

  3. 在同一个终端中,输入命令:ng g service auth/auth --module app.module.ts 来在 auth 文件夹中生成 auth 服务,并将其添加到 AppModule 中以进行依赖注入。

  4. 接下来,让我们创建 auth0-variables.ts 文件来存储 AuthConfig interface,以根据 Quick Start 指南中的附件项目存储 Auth 配置。运行命令:ng g interface auth/auth0-variables 来在 auth 文件夹中创建接口。

  5. Quick Start 下载的项目 01-login 中,复制文件 src -> app -> auth -> auth0-variables.ts 的内容,并替换我们新创建的 auth0- variables.tsUserManagement 应用程序中。

    interface AuthConfig 
    {
      clientID: string;
      domain: string;
      callbackURL: string;
    }
    
    //Replace XXXXXXXXXXXXXXXX with your credentials, 
    //the downloaded project would have original 
    //Client ID & Domain, just copy paste it and do not make any change
    export const AUTH_CONFIG: AuthConfig = {
      clientID: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
      domain: 'XXXXX.auth0.com',
      callbackURL: 'https://:3000/login'
    };
  6. 我做了一个小的更新,即将 callbackURLhttps://:4200/callback 改为 https://:3000/login,原因非常明显。我们正在使用端口 3000 运行我们的 Expressjs node 应用程序,并且我们希望在成功登录后,应用程序能重定向到 login 视图。我们将在 login 组件中放入逻辑来处理已登录和未登录的用户,所以请耐心等待。

  7. 接下来,从 Quick Start 下载的项目 01-login 中复制 auth.service.ts 的内容,并用我们 UserManagement 应用程序的 src -> app -> auth -> auth.service.ts 文件替换其内容。将 redirectUri 更新为 https://:3000/login. 转到 Visual Studio Code 的 File 菜单,然后选择 Save all 选项。最终的 AuthService 应该如下所示。

    import { Injectable } from '@angular/core';
    import { AUTH_CONFIG } from './auth0-variables';
    import { Router } from '@angular/router';
    import * as auth0 from 'auth0-js';
    
    @Injectable()
    export class AuthService {
    
      auth0 = new auth0.WebAuth({
       clientID: AUTH_CONFIG.clientID,
       domain: AUTH_CONFIG.domain,
       responseType: 'token id_token',
       audience: `https://${AUTH_CONFIG.domain}/userinfo`,
       redirectUri: 'https://:3000/login',
       scope: 'openid'
      });
    
      constructor(public router: Router) {}
    
      public login(): void {
       this.auth0.authorize();
      }
    
      public handleAuthentication(): void {
       this.auth0.parseHash((err, authResult) => {
        if (authResult && authResult.accessToken && authResult.idToken) {
         window.location.hash = '';
         this.setSession(authResult);
         this.router.navigate(['/home']);
        } else if (err) {
         this.router.navigate(['/home']);
         console.log(err);
         alert(`Error: ${err.error}. Check the console for further details.`);
        }
       });
      }
    
      private setSession(authResult): void {
       // Set the time that the access token will expire at
       const expiresAt = JSON.stringify((authResult.expiresIn * 1000) + 
                          new Date().getTime());
       localStorage.setItem('access_token', authResult.accessToken);
       localStorage.setItem('id_token', authResult.idToken);
       localStorage.setItem('expires_at', expiresAt);
      }
    
      public logout(): void {
       // Remove tokens and expiry time from localStorage
       localStorage.removeItem('access_token');
       localStorage.removeItem('id_token');
       localStorage.removeItem('expires_at');
       // Go back to the home route
       this.router.navigate(['/']);
      }
    
      public isAuthenticated(): boolean {
       // Check whether the current time is past the
       // access token's expiry time
       const expiresAt = JSON.parse(localStorage.getItem('expires_at'));
       return new Date().getTime() < expiresAt;
      }
    }
  8. 接下来,我们需要更新我们的 AppComponent,在构造函数中调用 AuthServicehandleAuthentication 函数,这与下载的 01-login 项目相同。您可以理解 handleAuthentication 函数的自解释代码,但我只想解释一下从 handleAuthentication 函数调用的 setSession 函数。在 setSession 函数中,我们将 Auth0 成功登录后返回的 access_tokenid_tokenexpires_at 值保存在 localStorage 中。localStorage 是 HTML 5 的一个特性,可以存储比 cookie 更大的数据,而且更安全。请查阅 localStorage 来浏览其存储和删除值的函数。我们将在 Auth0 登录过程中在浏览器中检查 localStorage 的值。所以,回到 AppComponent,根据以下内容更新 client -> UserManagement -> src -> app -> app.component.ts

    import { Component } from '@angular/core';
    import { AuthService } from "./auth/auth.service";
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    
    export class AppComponent {
      constructor(public auth: AuthService) {
        auth.handleAuthentication();
      }
      title = 'app';
    }
  9. 在更新 AppComponent 的同时,让我们也在 app.component.html 中添加登录和登出链接。用以下代码更新 client -> UserManagement -> src -> app -> app.component.html

    <div>
      <nav class='navbar navbar-inverse'>
          <div class='container-fluid'>
              <ul class='nav navbar-nav'>
                  <li><a [routerLink]="['home']">Home</a></li>
                  <li><a [routerLink]="['user']">Users Management</a></li>
                  <li>
                   <h4 *ngIf="auth.isAuthenticated() ; else nologin">
                     <a (click)="auth.logout()" class="btn btn-warning">Log Out</a>
                   </h4>
                  <ng-template #nologin>
                   <h4>
                     <a (click)="auth.login()" class="btn btn-success">Log In</a>
                   </h4>
                  </ng-template>
               </li>
              </ul>
          </div>
      </nav>
      <div class='container'>
          <router-outlet></router-outlet>
      </div>
    </div>
    
  10. 在上面的代码中,我们添加了 Log InLog Out 按钮,并根据 isAuthenticated 函数返回的布尔值来显示它们。转到 AuthService,在底部您会看到 isAuthenticated 函数,其中我们只检查当前时间是否仍小于我们在 setSession 函数中存储在 localStorage 中的 token 过期时间。if-else 块是我在我之前的 Angular 2 到 Angular 4 文章中已经解释过的 Angular 4 特性。

  11. 接下来,让我们创建 Angular 路由守卫,关于 router guard 简单介绍一下;guard 会中断路由并执行我们需要的操作,例如认证、授权、一些待处理的任务等等,这些都需要我们编码。要创建 guard,我们只需要实现 CanActivate 接口及其 canActivate() 方法,该方法返回 true/false 值以允许导航到我们应用该 guard 的视图。

  12. 右键单击 client->UserManagement 并选择 在终端中打开 选项,输入命令:ng g guard/auth --module app.moduel 来在 app->guard 文件夹中生成 guard 并将其引用添加到 AppModule

  13. 接下来,编辑从 guard 文件夹中新创建的 auth.guard.ts,并用以下代码替换其内容

    import { Injectable } from '@angular/core';
    import { CanActivate, ActivatedRouteSnapshot, 
             RouterStateSnapshot, Router } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    import { AuthService } from "../auth/auth.service";
    
    @Injectable()
    export class AuthGuard implements CanActivate {
      constructor(private router: Router,public _authService:AuthService){
      }
    
      canActivate(): boolean {
         let isAuth = this._authService.isAuthenticated();
           if(isAuth)
             return true;
           else {
             this.router.navigate(['/login']);
             return false;
            }  
         }
    }
  14. 在上面的代码中,我们添加了一个 AuthService 引用,实现了 CanActive 接口(如 guard 文档所述),并实现了 canActive 方法。在 canActive 函数中,我们调用 AuthServiceisAuthenticated 方法,该方法会告诉我们用户当前是否已登录或未登录。我们已经在上一步中学过 isAuthenticated 函数。

  15. 您可以看到,在用户未登录的情况下,我们会重定向到登录视图,所以让我们创建 login 组件,在该组件中我们将添加 Log InLog Out 按钮的链接以及用户友好的消息。提醒一下,当用户成功登录 Auth0 后,此登录视图也将作为 RedirectURL

  16. 右键单击 client- >UserManagement 文件夹并运行命令:ng g component login

  17. 编辑刚刚创建的 login.component.ts 文件(位于 client -> UserManagement -> src -> app -> ->login -> login.component.ts),并用以下代码替换其内容

    import { Component, OnInit } from '@angular/core';
    import { AuthService } from "../auth/auth.service";
    
    @Component({
    selector: 'app-login',
      templateUrl: './login.component.html',
      styleUrls: ['./login.component.css']
    })
    
    export class LoginComponent implements OnInit {
    
      constructor(public _authService:AuthService) {}
    
      ngOnInit() {}
    }
  18. 上面的代码没什么特别的,只是获取 AuthService 引用以供 login.component.html 调用。

  19. 接下来,编辑 client -> UserManagement -> src -> app - > ->login -> login.component.html 并用以下内容替换

    <h4 *ngIf="_authService.isAuthenticated() ; else nologin">
      You are logged in! <a (click)="_authService.logout()" 
      class="btn btn-warning">Log Out</a>, Go to <a href="/user" 
      class="btn btn-info">User Management</a>
    </h4>
    <ng-template #nologin>
     <h4>
      You are not logged in! Please <a (click)="_authService.login()" 
      class="btn btn-success">Log In</a> to continue.
     </h4>
    </ng-template>
  20. 这段代码与我们在 app.component.html 中添加的代码几乎相同。我认为不需要进一步解释。您可以随意使其更具吸引力。

  21. 快完成了,接下来,让我们去 Expressjs 代码,并添加一段代码来处理所有未知路由。编辑 server -> app.js 并进行如下更新

    var express = require('express');
    var path = require('path');
    var bodyParser = require('body-parser');
    var users = require('./routes/user');
    var index = require('./routes/index');
    var mongojs = require('mongojs');
    var db = mongojs('mongodb://XXXX:XXXXX@ds149353.mlab.com:XXXXXX/userdb001', 
             ['UserInfo']);
    
    var port = 3000;
    var app = express();
    
    app.engine('html', require('ejs').renderFile);
    app.use(express.static(path.join(__dirname,'../client/UserManagement/dist')));
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({extended: false}));
    
    app.set("userdb",db);
    app.use('/', index); 
    app.use("/api",users);
    
    app.get('*', function(req, res) {
         res.render(path.join(__dirname, 
         '../client/UserManagement/dist/index.html')); //load our public/index.html file
    });
    
    app.listen(port, function(){
        console.log('Server started on port '+port);
    });
  22. 最后,我们添加了 * 路由来处理所有类型的请求,并将它们重定向到 Angular 的 index.html。转到 File 菜单并选择 Save All 选项。

测试应用程序

  1. 好的,我们准备测试应用程序了,首先,让我们构建我们的 Angular 客户端应用程序。右键单击 client -> UserManagement 文件夹并选择 在终端中打开 选项。在终端中,输入命令:ng build。让它构建直到成功完成。

  2. 接下来,右键单击 server 文件夹并选择 在终端中打开 选项。在终端中,输入命令:node app。如果您看到 Server started on port 3000 消息,请打开浏览器(Chrome 或 Firefox)并输入 URL https://:3000。您应该会看到以下页面

  3. 有两个链接:HomeUser Management,还有一个 Log In 按钮。现在尝试通过点击 User Management 链接来进入 User Management 页面。您会看到以下页面

  4. 太棒了,它不允许我们查看 User Management 页面,还记得 Auth Guard 吗,它在这里起作用了。当我们点击 User Management 时,Auth guard 会通过调用 AuthSerivceisAuthenticated 方法来检查我们是否已登录,由于我们尚未登录,该方法返回 false

  5. 点击顶部菜单中的 Log In 按钮或 login 页面(两者相同)。您将被重定向到 Auth0 登录页面

  6. 现在,**使用 Google 登录** 或使用新用户注册,然后点击 Log In 按钮。按 F12/Ctrl+F12 或使用 Menu 前往 Chrome 浏览器中的 Developer tools。如果登录成功,您将看到以下屏幕。(在 Developer tools 中,转到 Application 标签页,然后从左侧菜单 Storage -> LocalStorage -> https://:3000

  7. 您可以在上面的屏幕截图中看到,**Log In** 按钮被替换为 ***Log Out***,我们还可以看到在 Local Storage 中保存的参数,这些参数是由 AppComponent 构造函数调用的 handleAuthentication 方法中的 setSession 方法设置的。希望现在对大家来说已经清楚了。由于我们将 redirectUri/callbackURL 改为了 https://:3000/login( wherever it was applicable),所以它回到了登录视图。

  8. 现在点击顶部菜单中的 User Management 链接,您应该会看到用户列表以及 **Add**/**Update** 和 **Delete** 按钮。

  9. 点击 Log Out 按钮,然后检查应用程序的行为。

实现 JWT 授权

  1. 首先,让我们通过示例理解为什么要实现 JWT。您在前面的步骤中看到,我们实现了身份验证,并将用户管理页面的访问限制为仅限已登录用户。如果您阅读了本系列文章的 第一部分,在文章的最后,我们使用了 Postman 工具来检查我们的所有 API。再次使用 Postman 并按照步骤检查 API,即右键单击 server 文件夹并选择 Open the Terminal 选项,在终端中输入命令:node app。转到浏览器并输入 URL https://:3000/api/users 或使用 Postman。您仍然可以在浏览器和 Postman 中看到所有用户,用户仍然可以被添加、删除或更新。

  2. 考虑到上述情况,如果我们的 API 仍然可以通过浏览器、Postman 或任何其他工具(如 Fiddler 或 SoapUI)访问,那么我们的身份验证就没有用了。我们需要通过限制我们的 API 只能由可信源访问来解决这个问题。通过 JWT,我们将从 Angular 客户端发送某种哈希字符串到所有 API 请求的标头中,然后在 Expressjs 服务器端进行验证,并在匹配成功时返回响应。

  3. 要在 Angular 中实现 JWT,我们将使用 angular2-jwt 包。请通过此 链接 阅读其文档,它非常简洁,我不需要将其复制粘贴到我的文章中。

  4. angular2-jwt 会自动将 token 与所有 HTTP 请求的 authorization 标头一起发送,但我们将使用 AuthHttp 而不是 HTTP 类来处理我们的 API 请求。让我们看看如何做。

  5. 首先,让我们安装 angular2-jwt,右键单击 client -> UserManagement 文件夹并选择 在终端中打开 选项,输入命令:npm install angular2-jwt@0.2.0 -- save(最新版本不起作用,所以我使用的是 0.2.0。)

  6. 接下来,参考 Github 页面,让我们更新 AppModule。编辑 client -> UserManagement -> src -> app -> app.module. ts 文件。如下更新 AppModule

    import { BrowserModule } from '@angular/platform-browser';
    import { NgModule } from '@angular/core';
    import { HttpModule, Http, RequestOptions } from '@angular/http';
    import { AppRoutingModule } from './app-routing.module';
    import { AppComponent } from './app.component';
    import { HomeComponent } from './home/home.component';
    import { UserComponent } from './user/user.component';
    import { ReactiveFormsModule } from '@angular/forms';
    import { Ng2Bs3Mo]\\dalModule } from 'ng2-bs3-modal/ng2-bs3-modal';
    import { UserService } from './service/user.service';
    import { AuthService } from './auth/auth.service';
    import { AuthGuard } from './guard/auth.guard';
    import { LoginComponent } from './login/login.component';
    import { CallbackComponent } from './callback/callback.component';
    
    //angular2-jwt config
    import { AuthHttp, AuthConfig } from 'angular2-jwt';
    
    export function authHttpServiceFactory(http: Http, options: RequestOptions) {
      return new AuthHttp(new AuthConfig(), http, options);
    }
    
    @NgModule({
      
    declarations: [
        AppComponent,
        HomeComponent,
        UserComponent,
        LoginComponent,
        CallbackComponent
      ],
      imports: [
        BrowserModule,
        AppRoutingModule,
        ReactiveFormsModule,
        Ng2Bs3ModalModule,
        HttpModule
      ],
      providers: [UserService, AuthService, AuthGuard,
        //angular2-jwt config
        {
          provide: AuthHttp,
          useFactory: authHttpServiceFactory,
          deps: [Http, RequestOptions]
        }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
  7. 您可以在同一 Github 页面上了解上述更新。

  8. 根据文档,“如果您只想在特定的 HTTP 请求上发送 JWT,您可以使用 AuthHttp 类。此类是 Angular 2 的 Http 的包装器,因此支持所有相同的 HTTP 方法。”所以,让我们在 UserService 中使用 AuthHttp 来处理我们的 API(GETPOSTPUTDELETE)请求,而不是 Http

  9. 编辑 client -> UserManagement -> src -> app -> service -> user.service.ts 文件,导入 angular2-jwt 并在构造函数中用 AuthHttp 替换 Http。更新后的 UserService 应该如下

    import { Injectable } from '@angular/core';
    import { IUser } from "../model/user";
    import { Observable } from "rxjs/Observable";
    import { Http, Response, Headers, RequestOptions} from '@angular/http';
    import 'rxjs/add/operator/map';
    import 'rxjs/add/operator/do';
    import 'rxjs/add/operator/catch';
    import { AuthHttp } from "angular2-jwt";
    
    @Injectable()
    export class UserService {
      users: IUser[];
      constructor(public _http: AuthHttp) { }
      get(): Observable<any> {
       let url="/api/users";
        return this._http.get(url)
        .map((response: Response) => <any>response.json())
        .catch(this.handleError);
      }
    
      post(model: any): Observable<any> {
       let url="/api/user";
        let body = JSON.stringify(model);
        console.log(body);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
        return this._http.post(url, body, options)
          .map((response: Response) => <any>response.json())
          .catch(this.handleError);
      }
    
      put(id: string, model: IUser): Observable<any> {
        let url="/api/user/"+id;
        delete model._id;
        let body = JSON.stringify(model);
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
        return this._http.put(url, body, options)
          .map((response: Response) => <any>response.json())
          .catch(this.handleError);
      }
    
      delete(id: string): Observable<any> {
       let url="/api/user/"+id;
        let headers = new Headers({ 'Content-Type': 'application/json' });
        let options = new RequestOptions({ headers: headers });
        return this._http.delete(url,options)
          .map((response: Response) => <any>response.json())
          .catch(this.handleError);
      }
    
      private handleError(error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
      }
    }
  10. 现在我们已经配置了 Angular 客户端应用程序以在所有 HTTP 调用中包含 JWT,我们还需要更新我们的 Expressjs 服务器 API 以在接收端处理该 JWT 并进行验证。

  11. Auth0 文档提供了可直接使用的示例代码和分步指南,说明如何更新我们的 Expressjs 应用程序以在服务器端验证 JWT。请登录您的 Auth0 帐户,然后浏览 https://auth0.com/docs/quickstart/backend/nodejs/01-authorization URL。您可以下载示例项目或按照步骤更新 server -> app.js 文件。请仔细阅读指南。我在这里不发布它,因为它会不必要地增加文章的篇幅。

  12. 让我们更新 Expressjs 服务器端,但在添加代码之前,我们需要一些包,即 express-jwtjwks-rsaexpress-jwt-authz
    1. express-jwt: 中间件,用于验证 JsonWebTokens 并设置 req.user
    2. jwks-rsa: jwks-rsa 库可以与 express-jwt 一起使用,以获取您的 Auth0 公钥并完成验证过程。
    3. express-jwt-authz: express-jwt-authz 库可用于向您的端点添加授权中间件。
  13. 右键单击 server 并选择 在终端中打开 选项,输入命令:npm install -- save express-jwt jwks-rsa express-jwt-authz

  14. 成功安装所有包后,编辑 client -> app.js 文件并进行如下更新

    var express = require('express');
    var path = require('path');
    var bodyParser = require('body-parser');
    var users = require('./routes/user');
    var index = require('./routes/index');
    var mongojs = require('mongojs');
    var db = mongojs('mongodb://XXXX:XXXX@ds149353.mlab.com:XXXXX/userdb001', 
             ['UserInfo']);
    
    //JWT config
    const jwt = require('express-jwt');
    const jwksRsa = require('jwks-rsa');
    // Authentication middleware. When used, the
    // access token must exist and be verified against
    // the Auth0 JSON Web Key Set
    const checkJwt = jwt({
       // Dynamically provide a signing key
       // based on the kid in the header and 
       // the singing keys provided by the JWKS endpoint.
       secret: jwksRsa.expressJwtSecret({
         cache: true,
         rateLimit: true,
         jwksRequestsPerMinute: 5,
         jwksUri: 'https://fullstackcircle.auth0.com/.well-known/jwks.json'
        }),
       
        // Validate the audience and the issuer.
        audience: process.env.AUTH0_AUDIENCE,
        issuer: 'https://fullstackcircle.auth0.com/',
        algorithms: ['RS256']
       });
    
    var port = 3000;
    var app = express();
    
    app.engine('html', require('ejs').renderFile);
    app.use(express.static(path.join(__dirname, '../client/UserManagement/dist')));
    
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({extended: false}));
    
    app.set("userdb",db);
    app.use('/', index); 
    app.use("/api",checkJwt,users);
    
    app.get('*', function(req, res) {
       res.render(path.join(__dirname, 
        '../client/UserManagement/dist/index.html')); // load our public/index.html file
    });
    
    app.listen(port, function(){
       console.log('Server started on port '+port);
    });
  15. 在上面的代码中,从 https://auth0.com/docs/quickstart/backend/nodejs/01-authorization#configuration(确保您已登录您的 Auth0 帐户)部分获取 jwksUriissuer

  16. 这就是 Angular 和 Expressjs 应用程序上的 JWT 实现。JWT 实现的简要说明在这里,在创建 checkJwt 后,您可以看到,我们将其添加到路由中:app.use ("/api",checkJwt,users); 这将在提供任何用户 API 请求之前检查 JWT token 并进行验证。

  17. 通过右键单击 client -> UserManagement 文件夹并选择 在终端中打开 选项,输入命令:ng build 来构建 Angular 客户端应用程序

  18. 通过右键单击 server 文件夹并选择 在终端中打开 选项来运行服务器,输入命令:node app(在 Angular 客户端成功构建之后)。

  19. 打开浏览器,输入 URL:https://:3000 并进行测试。

  20. 我还建议您转到我的 第一部分,并从 step 40 开始,在 Postman 中测试此应用程序。您应该会收到一个未授权的错误,因为 HTTP 请求标头中没有 JWT token。这意味着我们的应用程序无法从外部访问,并且是安全的。

摘要

所以,基本上,我们在 Auth0 网站上创建了 client,它相当于我们应用程序的账户,并包含连接我们的应用程序到 Auth0 和管理用户的基本信息。Auth0 为 Angular 2+ 应用程序提供了现成的代码和简要教程,用于用户登录和注册,我们下载了示例代码,它已经根据我们的客户端设置进行了基本配置。我们使用了示例代码中的 AuthService,修改了 AppComponents,并使用了下载示例应用程序中的 auth0-variables.ts。在 AuthService 中,有一个登录代码,它会将用户重定向到 Auth0 登录页面,并返回 id_tokenexpires_ataccess_token,我们将其保存在 localStorage 中。AuthService 中的其他辅助方法用于登出和检查用户是否仍已登录。我们在 Auth Guard 中使用了 isAuthenticated 方法来限制用户管理页面的访问。接下来,我们使用 angular2-jwt 包实现了用户 API 的 JWT 身份验证,它几乎为我们完成了所有工作。它从 localStorage 获取 access_token,并通过其包装器方法 AuthHttp,将其附加到所有 HTTP 请求标头中。这就是我们在客户端所做的工作。

在 Expressjs 服务器端,我们使用 express-jwt 包添加了 JWT 实现的代码。感谢 Auth0,我们还有一个示例项目和简要教程,说明如何在 Expressjs 应用程序中配置它。我们正在使用不同的包来获取 HTTP 请求中的 JWT token 并进行验证。由于我们为签名密钥使用了 RS256 算法,它工作方式是公钥/私钥对。从 Angular 发送的 token 可以与 Auth0 账户上的公钥进行验证。我们在创建 checkjwt 对象时将公钥 URL 指定为 jwksUri,它会验证请求中包含的 access_token 是否有效。只需调试 Angular 客户端代码,您就会发现在登录到 Auth0 账户时,会调用相同的 jwksUri 来获取私钥。所以,忽略复杂性,仅为理解起见,Auth0 账户在登录时会创建两个密钥,一个由 Angular 客户端发送,该密钥与从 Auth0 账户获取的服务器端的另一个密钥进行验证,如果组合正确,则请求有效,否则无效。您还可以添加一个 scope(访问级别,例如读、写等)到请求中。请查看 Auth0 文档了解如何操作。

现在,通过验证 JWT token,我们的应用程序就知道请求来自可信源。

历史

  • 2017年9月9日:创建
  • 2017年9月10日:修复附件项目
© . All rights reserved.