用Angular+GraphAPI漂亮地浏览由SharePoint创建的内部门户①

由于公司的门户网站是在Office365的SharePoint上建立的,但是由于看起来很难看,所以我决定用Angular8 + Graph API来自己建设。
我打算记录下使用Graph API的过程。

因为可能会很长,所以我会分几次写。
续⇒使用Angular+GraphAPI精美地浏览由SharePoint创建的内部门户网站。

此外,据说如果使用API进行诸多操作,将会按照以下流程进行,本次将会写出1、2两部分。

image.png

在Azure上注册应用程序

应用程序注册

image.png
image.png

多种设置

设置重定向URL

    「認証」からリダイレクトURLを設定
image.png

暗黙的许可流程的授权

如果您想隐式执行登录或获取访问令牌,请勾选并保存。

image.png

用Angular构建前端

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

我们将按照以下Angular版本进行推进。

image.png

生成模板项目

请使用以下命令生成模板。

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

画面转换图像

image.png

各种组件 (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
image.png

总结

我已经成功登录了,接下来我会另外写一篇文章来获取下一个访问令牌。

技巧 (jì

我在登录方面遇到了一些问题,所以我把它写下来。
※以上步骤已经反映在上面了。

AADSTS700054:应用程序未启用“id_token”的响应类型。

image.png

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

image.png

※ 删除缓存,或在隐私模式下打开

AADSTS500113: 应用程序未注册回复地址。

image.png

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

image.png

只要在这里指定的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

image.png
广告
将在 10 秒后关闭
bannerAds