【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的Web客户端,所以我们选择”客户端应用程序”。名称随意即可。
然后,在创建完成后将应用程序ID复制并保存。

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

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

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

在确认后,只要状态栏上显示了绿色的勾号,就表示一切正常。
接下来,从AD B2C的顶部菜单中,点击”用户流程(策略)”,然后选择上一篇文章中添加的”B2C_1_susi”流程。

点击“执行用户流程”,复制并打开…/.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中,存储服务并非分散的,而是汇集在”账户”下。
因此,首先要创建一个”存储账户”,但是…

好的,移动到专门用于AD B2C的目录中分配订阅的目录后才可以进行。
在切换目录之后,请通过”添加新资源”找到并选择”存储帐户”。

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

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

在创建后,将继续进行部署操作。部署完成后,请点击“移动到资源”。
3-2. 创建静态网站
当从”移动到资源”到达存储帐户概述时,请从左侧菜单中点击”静态网站”。首次进入页面时,此选项可能不会显示,需要滚动查找。(以及存储中菜单选项有多少啊。。。)

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

这个“主要终端点”指的是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 应用,并在应用菜单中点击”身份验证”。

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

在WEB的配置界面上添加URL,并通过”暗黙的授权”添加访问令牌和身份令牌(或者Quarkus可能只需要访问令牌?),通过点击”配置”来保存。
4-2. 功能的CORS配置
接下来是关于函数的 CORS。这是最后一个了!
首先,在AD B2C的目录中切换到函数应用的资源目录,然后从函数应用列表中选择适用于Quarkus的应用程序。

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

请打开”CORS”设置对话框,将”Access-Allow…”设置为”启用”,然后将Angular网站的URL添加到列表中,并点击”保存”。
这个就完成了!
从浏览器访问
让我们从浏览器中打开已部署的 Angular 应用。

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

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

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

是的!从Quarkus的Functions功能返回了hello jaxrs的响应!!由于Functions的API有时可能会休眠,所以如果没有任何响应,请再次点击。
辛苦了!
总结
由于有些陷阱,我们在 Quarkus 和 OIDC 结合的功能中确认了SSO。总而言之,由于AD B2C以及Azure等方面的设置比较复杂,操作会变得困难。也许该考虑引入Terraform了……哈哈。
本次,我们将创建的 Angular 客户端添加到之前的 Quarkus API 相同的代码库中。供您参考~
- quarkus-examples branch azure-functions
这次就到这里吧。