用Angular+GraphAPI漂亮地浏览由SharePoint创建的内部门户①
由于公司的门户网站是在Office365的SharePoint上建立的,但是由于看起来很难看,所以我决定用Angular8 + Graph API来自己建设。
我打算记录下使用Graph API的过程。
因为可能会很长,所以我会分几次写。
续⇒使用Angular+GraphAPI精美地浏览由SharePoint创建的内部门户网站。
此外,据说如果使用API进行诸多操作,将会按照以下流程进行,本次将会写出1、2两部分。

在Azure上注册应用程序
应用程序注册


多种设置
设置重定向URL
- 「認証」からリダイレクトURLを設定

暗黙的许可流程的授权
如果您想隐式执行登录或获取访问令牌,请勾选并保存。

用Angular构建前端
在Azure中有一个快速入门,但是为了Angular,有一个名为”@azure/msal-angular”的软件包可用,因此,以后我们将按照该软件包的安装步骤进行实施。
我们将按照以下Angular版本进行推进。

生成模板项目
请使用以下命令生成模板。
ng new sp-portal --routing
因为我想要路由功能,所以我加上了–routing选项。
安装 msal-angular
因为使用Express.js编写JavaScript代码非常繁琐,所以我们选择安装msal-angular作为快速启动的方案。
npm install @azure/msal-angular --save
安装所需的软件包。
因为msal-angular似乎使用rxjs-compat,所以我们将进行安装。
npm install --save rxjs-compat
在中国母语中转述如下:“嵌入代码”。
我们将参考@azure/msal-angular的页面来进行组合。
在AppModule中导入MsalModule。
在这里,指定Azure注册应用的客户端ID、租户ID和重定向URL。
※重定向URL是登录或登出完成后要重定向的URL
必须与Azure上设置的URL匹配。
import { MsalModule } from '@azure/msal-angular';
import { environment } from "../environments/environment";
@NgModule({
・・・
imports: [
・・・
MsalModule.forRoot({
clientID: environment.msal.clientID,
authority: environment.msal.authority,
redirectUri: environment.msal.redirectUrl,
})
]
})
export class AppModule { }
请参阅以下列出的可设定项目。
另外,这次我们将各种设置值拆分到environments.ts文件中。
export const environment = {
production: false,
msal: {
clientID: "{Your Client ID}",
authority: "https://login.microsoftonline.com/{Your Tenant ID}",
redirectUrl: "http://localhost:4200/",
}
};
路由设置
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { MsalGuard } from '@azure/msal-angular';
import { LoginComponent } from './login/login.component';
const routes: Routes = [
{ path: 'login', component: LoginComponent },
// Angular8のDynamic Imports
{ path: 'pages', canActivate: [MsalGuard], loadChildren: () => import('./pages/pages.module').then(m => m.PagesModule) },
{ path: '', redirectTo: 'login', pathMatch: 'full' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
在路径 ‘pages’ 中设置了 canActive:’MsalGuard’,因此在访问 /pages~ 的URL时,将运行MsalGuard进行检查,如果尚未进行身份验证,则会被强制重定向到Office365的登录页面。
顺便提一下,这次我们的目录结构如下所示。
└─src
│
├─app
│ │ app-routing.module.ts
│ │ app.component.html
│ │ app.component.ts
│ │ app.module.ts
│ │
│ ├─login
│ │ login.component.ts
│ │
│ └─pages
│ │ pages-routing.module.ts
│ │ pages.module.ts
│ │
│ ├─detail
│ │ detail.component.ts
│ │
│ └─top
│ top.component.ts
│
├─environments
│ environment.prod.ts
│ environment.ts
│
└─services
auth.service.ts
画面转换图像

各种组件 (Gè
app.component可以进行重构。
在这里,我们主要描述了头部控制的内容。
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { User } from 'msal';
import { AuthService } from 'src/services/auth.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
/** ログイン中ユーザー情報 */
user: User = null;
constructor(
private router: Router,
private authService: AuthService,
) { }
ngOnInit() {
// ユーザー取得
this.user = this.authService.getUser();
}
/**
* ログインチェック
*/
get isLogin() {
return this.user ? true: false;
}
/**
* タイトル押下
*/
OnClickTitle() {
if (this.isLogin) {
this.router.navigate(['/pages']);
}
}
/**
* ログイン押下
*/
async OnClickLogin() {
// ログインポップアップ表示
const token = await this.authService.showLoginPopup();
if (token) {
// ユーザー情報を取得
this.user = this.authService.getUser();
// TOP画面に遷移
this.router.navigate(['/pages']);
} else {
this.user = null;
alert('ログイン失敗');
}
}
/**
* ログアウト押下
*/
async OnClickLogout() {
this.user = null;
// ログアウト
this.authService.logout();
}
}
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between" color="primary">
<div fxFlexAlign="center">
<button mat-flat-button color="primary" (click)="OnClickTitle()"><h1>Title</h1></button>
</div>
<div fxFlexAlign="center"></div>
<div fxFlexAlign="center">
<button mat-raised-button color="accent" *ngIf="!isLogin" (click)="OnClickLogin()">ログイン</button>
<button [matMenuTriggerFor]="menu" *ngIf="isLogin" mat-raised-button color="primary">{{user.displayableId}}</button>
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="OnClickLogout()">
<mat-icon>exit_to_app</mat-icon>
<span>ログアウト</span>
</button>
</mat-menu>
</div>
</mat-toolbar>
<router-outlet></router-outlet>
登陆组件.ts
检查是否已登录,如果已登录,则导航到/pages页面。
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from 'src/services/auth.service';
@Component({
selector: 'app-login',
template: '<span>ログインしてください</span>',
})
export class LoginComponent implements OnInit {
constructor(
private router: Router,
private authService: AuthService,
) { }
ngOnInit() {
if (this.authService.getUser()) {
// ログイン済みの場合はTOPページに遷移
this.router.navigate(['/pages']);
}
}
}
src/app/pages下的组件
由于本次主要是关于登录的问题,所以这只是一个临时界面。
import { Component } from '@angular/core';
@Component({
selector: 'app-pages-top',
template: `
<h1>Top Component</h1>
<ul>
<li><a routerLink="/pages/detail/1">ID1のページを表示</a></li>
<li><a routerLink="/pages/detail/2">ID2のページを表示</a></li>
</ul>
`,
})
export class TopComponent {
}
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
@Component({
selector: 'app-pages-detail',
template: `
<h1>Detail Component</h1>
<span>ID:{{id}}のページです</span>
`,
})
export class DetailComponent implements OnInit {
id: number;
constructor(private ar: ActivatedRoute) {}
ngOnInit() {
// IDを取得
this.ar.params.subscribe((params: Params) => {
if (params['id']) {
this.id = params['id'];
} else {
alert('idを指定してください')
}
})
}
}
服務註冊
创建服务
定义了登录/注销和获取用户信息的处理。
此外,在示例中还通过检查用户信息的存在与否来判断是否已登录。
import { Injectable } from '@angular/core';
import { MsalService } from '@azure/msal-angular';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private msalService: MsalService) { }
/**
* ユーザー取得
*/
getUser() {
return this.msalService.getUser();
}
/**
* ログインポップアップ表示
*/
async showLoginPopup() {
try {
return await this.msalService.loginPopup();
} catch (err) {
console.log(err);
return null;
}
}
/**
* ログアウト
*/
logout() {
this.msalService.logout();
}
}
创建一个用于SharePointAPI的服务
※此时还是空的。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class SharePointApiService {
}
在供应商那里注册服务。
通过在TTP_INTERCEPTORS中指定MsalInterceptor,在调用指定的服务时会进行身份验证检查,如果无法通过验证,则会重定向到Office365的登录页面。
import { MsalModule, MsalInterceptor } from '@azure/msal-angular';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { SharePointApiService } from 'src/services/sharepoint-api.service';
@NgModule({
・・・
providers: [
SharePointApiService,
{ provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true }
],
})
export class AppModule { }
※AuthService只包含不需要进行认证检查的处理程序,因此并没有将其注册为providers。
如果将其注册,只需调用getUser函数就会自动跳转到登录页面…
启动服务器
准备工作已经完成!请使用以下指令启动。
ng serve

总结
我已经成功登录了,接下来我会另外写一篇文章来获取下一个访问令牌。
技巧 (jì
我在登录方面遇到了一些问题,所以我把它写下来。
※以上步骤已经反映在上面了。
AADSTS700054:应用程序未启用“id_token”的响应类型。

在Azure的「应用程序注册⇒认证」中勾选ID令牌。

※ 删除缓存,或在隐私模式下打开
AADSTS500113: 应用程序未注册回复地址。

在Azure的”应用程序注册->认证”中选择重定向URL。

只要在这里指定的URL和AppModule中指定的redirectUri不一致,就会产生错误。
AADSTS700051:应用程序未启用“token”响应类型。
在尝试使用acquireTokenSilent来获取访问令牌时发生了问题。根据文档,需要在清单中允许隐式令牌获取。参考链接:https://community.dynamics.com/crm/b/akmscrmblog/archive/2016/03/16/response-type-token-is-not-enabled-for-the-application
