【2019年12月版本】 在Angular中使用Blob Storage与AD B2C的OIDC集成相对来说并无太大问题

在 AD B2C 中使用 Angular 和 Quarkus 进行 OpenID Connect。

在上一篇文章中(【2019年12月版】Quarkus、Azure Functionsに載せるは天国。AD B2C で OIDC は地獄。),我们尝试了AD B2C和Quarkus的OIDC。
当时我们使用了Postman作为客户端,但这次我们想尝试使用Angular进行真正的客户端端实现。
巧合的是,我发现了以下文章,让我们参考一下,在Angular中进行客户端实现吧!

    Using Azure AD B2C with Angular 8

目标环境

Angular: 8.2.14
Azure CLI: 2.0.77

Angular版本:8.2.14
Azure CLI版本:2.0.77

这次使用的是 Angular 8。

1. AD B2C 的设定:注册应用程序用于 Angular。

首先,我最想开始实施…但为了方便说明,我先进行 AD B2C 应用程序的注册。对不起…

请通过菜单选择”注册应用程序(预览版)”,然后添加一个新的应用程序,以便访问由 AD B2C 创建的目录的 AD B2C 服务概述页面。(直接访问可能有些困难。)

Angular アプリケーションの登録 - Microsoft Azure.png

由于这次是关于Angular的Web客户端,所以我们选择”客户端应用程序”。名称随意即可。
然后,在创建完成后将应用程序ID复制并保存。

Angular-Client 概要 - Microsoft Azure.png

在菜单中,依次点击”API访问权限”和”添加许可”。

Angular API アクセス許可の要求 - Microsoft Azure.png

在您的API中,点击上一篇文章中添加的”File.Read”权限的范围,然后点击”添加权限”来授权。

Angular-Client - 管理者の同意 - Microsoft Azure.png

当收到追加通知后,请点击“管理员同意”。登录对话框和批准对话框将会弹出,请确保批准。

Angular-Client - 管理者の同意後 - Microsoft Azure.png

在确认后,只要状态栏上显示了绿色的勾号,就表示一切正常。

接下来,从AD B2C的顶部菜单中,点击”用户流程(策略)”,然后选择上一篇文章中添加的”B2C_1_susi”流程。

ユーザー フローを実行 - Microsoft Azure.png

点击“执行用户流程”,复制并打开…/.well-known/openid-configuration的URL,然后复制粘贴 issuer 和 token 的端点URL。稍后会需要…/.well-known/openid-configuration、issuer和token这3个端点。

那么,现在终于我想要开始进行 Angular 的实现。

2. Angular的实现

Angular 将几乎完全按照参考文章的内容进行操作。

2-1. 创建和准备项目

首先,我们将通过CLI生成一个项目。
这次我们将以ng-quarkus-adb2c作为项目名称创建它。

$ ng new ng-quarkus-adb2c
...
$ cd ng-quarkus-adb2c

由于今天的样本只有一页,所以不需要路由器。

接下来,我们要添加 angular-oauth2-oidc 包。

$ npm i angular-oauth2-oidc

2-2. 准备 OIDC

在 “app.module.ts” 中,添加 OIDC 模块的初始化。

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';
import { OAuthModule } from 'angular-oauth2-oidc';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    OAuthModule.forRoot({
      resourceServer: {
        allowedUrls: ['https://addressToFunctionsApp.azurewebsites.net/api/'],
        sendAccessToken: true
      }
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

在OAuth模块的配置中,您可以列举发送访问令牌的URL前缀。本例中,我们将指定Quarkus API的地址,该API已部署在Azure Functions上。

接下来我们来定义连接设置。在与app.module.ts文件相同的文件夹中创建一个名为auth.config.ts的文件。

import { AuthConfig } from 'angular-oauth2-oidc';

export const DiscoveryDocumentConfig = {
  url: "https://xxxxxxxxxxxx.b2clogin.com/tfp/xxxxxxxxxxx.onmicrosoft.com/b2c_1_susi/v2.0/.well-known/openid-configuration"
}

export const authConfig: AuthConfig = {
  redirectUri: window.location.origin,
  responseType: 'token id_token',
  issuer: 'https://xxxxxx.b2clogin.com/tfp/xxxxxxxxxxxx/b2c_1_susi/v2.0/',
  strictDiscoveryDocumentValidation: false,
  tokenEndpoint: 'https://xxxxxxxxxx.b2clogin.com/xxxxxxxxxxxxx.onmicrosoft.com/b2c_1_susi/oauth2/v2.0/token',
  loginUrl: 'https://xxxxxxxxxxxxxx.b2clogin.com/xxxxxxxxxxxxx.onmicrosoft.com/b2c_1_susi/oauth2/v2.0/token',
  clientId: 'xxxxxxxxxxxxxxxxxxxxxxxx',
  scope: 'openid profile https://xxxxxxxxxxxxxxxx.onmicrosoft.com/xxxxxxxxxxxxxx/File.Read',
  skipIssuerCheck: true,
  clearHashAfterLogin: true,
  oidc: true,

设定值如下所示。

DiscoveryDocumentConfig に .well-known/openid-configuration のURL

issuer に issuer のエンドポイント URL

tokenEndpoint、loginUrl に token のエンドポイント URL

clientId にアプリケーションID

scope に “openid profile ” と例のスコープのURI

在设置responseType时,会导致各种行为的变化。请各位自行尝试实验。暂时先使用token id_token。

2-3. 图像的执行

将app.componet.ts文件按照以下的示例+ α进行修改。

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

import { OAuthService, NullValidationHandler } from 'angular-oauth2-oidc';
import { authConfig, DiscoveryDocumentConfig } from './auth.config';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'ng-quarkus-adb2c';
  constructor(private http: HttpClient, private oauthService: OAuthService) {
    this.configure();
    this.oauthService.tryLoginImplicitFlow();
  }

  message: string;

  public getMessage() {
    this.http.get("https://addressToFunctionsApp.azurewebsites.net/api/hello", { responseType: 'text' })
      .subscribe(r => {
        this.message = r
        console.log("message: ", this.message);
      });
  }

  public login() {
    this.oauthService.initLoginFlow();
  }

  public logout() {
    this.oauthService.logOut();
  }

  public get claims() {
    let claims = this.oauthService.getIdentityClaims();
    return claims;

  }

  public get accToken() {
    return this.oauthService.getAccessToken();
  }

  public get idToken() {
    return this.oauthService.getIdToken();
  }

  public get scopes() {
    return this.oauthService.getGrantedScopes();
  }

  private configure() {
    this.oauthService.configure(authConfig);
    this.oauthService.tokenValidationHandler = new NullValidationHandler();
    this.oauthService.loadDiscoveryDocument(DiscoveryDocumentConfig.url);
  }
}

在getMessage()方法中调用函数应用程序。这是关键所在。
此外,我还添加了一个get方法,可以在屏幕上显示访问令牌和ID令牌。这完全是为了实验!

好的,现在我们来看一下 app.component.html 的部分,我们将使用以下的示例+α来进行修改。

<h1 *ngIf="!claims">
  Hi!
</h1>

<h1 *ngIf="claims">
  Hi, {{claims.given_name}}!
</h1>

<h2 *ngIf="claims">Your Claims:</h2>

<pre *ngIf="claims">
  {{claims | json}}
  </pre>
<br />

<h2 *ngIf="accToken">Your AccessToken:</h2>

<pre *ngIf="accToken">
  {{accToken | json}}
  </pre>
<br />

<h2 *ngIf="idToken">Your idToken:</h2>

<pre *ngIf="idToken">
  {{idToken | json}}
  </pre>
<br />

<div *ngIf="!claims">
  <button (click)="login()">Login</button>
</div>

<div *ngIf="claims || accToken || idToken || scopes">
  <button (click)="logout()">Logout</button>
  <button (click)="getMessage()">API Call</button>
  <div *ngIf="message">
    Response:
    {{message | json}}
  </div>
</div>

为了方便在连接设置的responseType中进行各种实验,我尝试在屏幕上显示个人资料和访问令牌。像往常一样,通过https://jwt.io/查看可以是很有学习价值的。

好了,Angular 的实现就到这里了!

将数据上传至 Blob Storage

接下来,我们来创建一个用于部署 Angular 源码的目标位置。

创建“存储账户”。

在Azure中,存储服务并非分散的,而是汇集在”账户”下。
因此,首先要创建一个”存储账户”,但是…

ディレクトリ切り替え - Microsoft Azure.png

好的,移动到专门用于AD B2C的目录中分配订阅的目录后才可以进行。

在切换目录之后,请通过”添加新资源”找到并选择”存储帐户”。

ストレージ アカウントの作成 - Microsoft Azure.png

请点击这里的”创建”按钮。

ストレージ アカウントの新規作成 - Microsoft Azure (1).png

可以保持默认设置,但请确保”帐户类型”设置为”StorageV2″。如果不是V2版本,菜单结构将会有所不同。。。

ストレージ アカウントの作成後 - Microsoft Azure.png

在创建后,将继续进行部署操作。部署完成后,请点击“移动到资源”。

3-2. 创建静态网站

当从”移动到资源”到达存储帐户概述时,请从左侧菜单中点击”静态网站”。首次进入页面时,此选项可能不会显示,需要滚动查找。(以及存储中菜单选项有多少啊。。。)

静的な Web サイトの作成 - Microsoft Azure.png

将”静态网站”设置为”启用”,并指定”索引文档名称”为”index.html”。
完成设置后,点击”保存”,将创建名为$web的容器,用作网站内容的容器。

静的な Web サイト作成結果 - Microsoft Azure.png

这个“主要终端点”指的是Angular的网站。

部署

让我们现在封装和部署Angular源代码!下面是相应的命令。

$ npm run build --prod
...
$ az storage blob upload-batch -d '$web' --account-name xxxxxxxxxxxxxxx -s ./dist/ng-quarkus-adb2c
Finished[#############################################################]  100.0000%
...

请明确地使用单引号 ‘ $web ‘ 来避免其可疑,且与 Shell 完美兼容最差的名字。(因为它容易被误解)

    storage blob upload-batch fails with InvalidQueryParameterValue on Ubuntu

一開始,我自己也中了圈套哈哈。

4. 准备进行动作确认

好吧,让我们开始访问吧!…但在此之前,还需要进行一些初次调整。

4-1. AD B2C 的设置

我在 AD B2C 中注册了一个 Angular 应用,但是此时网站的 URL 还未确定。
请按以下步骤添加网站的 URL。

首先,切换到 AD B2C 的”目录”,然后进入 AD B2C 资源的概述页面。
从”应用注册(预览)”中选择 Angular 应用,并在应用菜单中点击”身份验证”。

プラットフォームの追加Microsoft Azure.png

从认证画面中选择”添加平台”,然后点击”Web”。

Web の構成 - Microsoft Azure.png

在WEB的配置界面上添加URL,并通过”暗黙的授权”添加访问令牌和身份令牌(或者Quarkus可能只需要访问令牌?),通过点击”配置”来保存。

4-2. 功能的CORS配置

接下来是关于函数的 CORS。这是最后一个了!

首先,在AD B2C的目录中切换到函数应用的资源目录,然后从函数应用列表中选择适用于Quarkus的应用程序。

関数アプリ概要 - Microsoft Azure.png

一、打开函数应用的概览页面,点击”平台功能”选项卡,再点击”CORS”。

CORS - Microsoft Azure.png

请打开”CORS”设置对话框,将”Access-Allow…”设置为”启用”,然后将Angular网站的URL添加到列表中,并点击”保存”。

这个就完成了!

从浏览器访问

让我们从浏览器中打开已部署的 Angular 应用。

NgQuarkusAdb2c Start.png

一開始很簡單…請點擊「登錄」。

Sign up or sign in.png

Azure AD B2C 出现了登录界面。OIDC插件正常工作了呢。。。
您可以选择在这里登录,或者在sute.jp上创建一个随意的地址并进行注册,然后成功地登录…

NgQuarkusAdb2c Login.png

在 AD B2C 上,您可以看到获取的用户信息和令牌等内容。虽然页面上有很多模糊处理,变得有些难以理解,但现在您可以点击”API Call”进行访问令牌获取!

NgQuarkusAdb2c API Response.png

是的!从Quarkus的Functions功能返回了hello jaxrs的响应!!由于Functions的API有时可能会休眠,所以如果没有任何响应,请再次点击。

辛苦了!

总结

由于有些陷阱,我们在 Quarkus 和 OIDC 结合的功能中确认了SSO。总而言之,由于AD B2C以及Azure等方面的设置比较复杂,操作会变得困难。也许该考虑引入Terraform了……哈哈。

本次,我们将创建的 Angular 客户端添加到之前的 Quarkus API 相同的代码库中。供您参考~

    quarkus-examples branch azure-functions

这次就到这里吧。

bannerAds