Учебник. Вход пользователей в систему и вызов API Microsoft Graph из одностраничного приложения (SPA) Angular с помощью потока кода проверки подлинности
В этом руководстве вы создадите одностраничное приложение Angular, которое входит в систему пользователей и вызывает API Microsoft Graph с помощью потока кода авторизации с PKCE. Создаваемое одностраничное приложение использует библиотеку проверки подлинности Майкрософт (MSAL) для Angular версии 2.
В этом руководстве рассматриваются следующие темы:
- Регистрация приложения в Центре администрирования Microsoft Entra
- создание проекта Angular с помощью
npm
; - добавление кода для поддержки входа и выхода пользователей;
- добавление кода для вызова API Microsoft Graph;
- Тестирование приложения
MSAL Angular версии 2 улучшает msAL Angular версии 1, поддерживая поток кода авторизации с PKCE в браузере вместо неявного потока предоставления. Мы рекомендуем использовать поток кода авторизации с PKCE для одностраничных приложений (SPAs), так как это более безопасно, чем неявный поток. MSAL Angular версии 2 НЕ поддерживает неявный поток.
Необходимые компоненты
- Node.js для запуска локального веб-сервера.
- Visual Studio Code или другой редактор для изменения файлов проекта.
Как работает пример приложения
Пример приложения, созданный по инструкциям из этого руководства, позволяет одностраничному приложению Angular выполнять запрос к API Microsoft Graph или веб-API, принимающему маркеры, выпущенные платформой удостоверений Майкрософт. В нем используется библиотека проверки подлинности Майкрософт (MSAL) для Angular версии 2, программа-оболочка для библиотеки MSAL.js версии 2. MSAL Angular позволяет приложениям Angular 9+ проверять подлинность корпоративных пользователей с помощью идентификатора Microsoft Entra, а также пользователей с учетными записями Майкрософт и социальными удостоверениями, такими как Facebook, Google и LinkedIn. Библиотека также позволяет приложениям получать доступ к облачным службам Майкрософт или Microsoft Graph.
В этом сценарии после входа пользователя в систему маркер доступа запрашивается и добавляется в HTTP-запросы с использованием заголовка авторизации. Получение и обновление маркера выполняются MSAL.
Библиотеки
В этом учебнике используются следующие библиотеки:
Библиотека | Description |
---|---|
MSAL Angular | Библиотека проверки подлинности Майкрософт для программы-оболочки JavaScript Angular |
Браузер MSAL | Библиотека проверки подлинности Майкрософт для пакета JavaScript версии 2 для браузера |
Исходный код для всех библиотек MSAL.js можно найти в microsoft-authentication-library-for-js
репозитории на сайте GitHub.
Получение готового примера кода
Вместо этого вы предпочитаете скачать готовый пример проекта для этого руководства? Клонирование ms-identity-javascript-angular-spa
git clone https://github.com/Azure-Samples/ms-identity-javascript-angular-spa.git
Чтобы продолжить работу с руководством и создать приложение самостоятельно, перейдите к следующему разделу, зарегистрируйте приложение и идентификаторы записей.
Регистрация идентификаторов приложения и записей
Совет
Действия, описанные в этой статье, могут немного отличаться на портале, с который вы начинаете работу.
Чтобы завершить регистрацию, укажите имя приложения, укажите поддерживаемые типы учетных записей и добавьте URI перенаправления. После регистрации в области обзора приложения отображаются идентификаторы, необходимые в исходном коде приложения.
- Войдите в Центр администрирования Microsoft Entra как минимум разработчик приложений.
- Если у вас есть доступ к нескольким клиентам, используйте значок Параметры в верхнем меню, чтобы переключиться на клиент, в котором вы хотите зарегистрировать приложение из меню каталогов и подписок.
- Перейдите к приложениям> удостоверений>Регистрация приложений.
- Выберите Создать регистрацию.
- Введите имя приложения, например Angular-SPA-auth-code.
- Для параметра Поддерживаемые типы учетных записей выберите Учетные записи только в этом каталоге организации. Для получения сведений о различных типах учетных записей выберите параметр "Справка ".
- В разделе URI перенаправления (необязательно) используйте раскрывающееся меню, чтобы выбрать одностраничные приложения (SPA) и ввести
http://localhost:4200
в текстовое поле. - Выберите Зарегистрировать.
- Панель обзора приложения отображается при завершении регистрации. Запишите идентификатор каталога (клиента) и идентификатор приложения (клиента), которые будут использоваться в исходном коде приложения.
Создание проекта
Откройте Visual Studio Code, выберите "Открыть папку">.... Перейдите к папке и выберите расположение, в котором нужно создать проект.
Откройте новый терминал, выбрав терминал>"Новый терминал".
- Может потребоваться переключить типы терминалов. Щелкните стрелку вниз рядом с значком + в терминале и выберите командную строку.
Выполните следующие команды, чтобы создать новый проект Angular с именем
msal-angular-tutorial
, установить библиотеки компонентов Angular Material, MSAL Browser, MSAL Angular и создать компоненты домашнего и профиля.npm install -g @angular/cli ng new msal-angular-tutorial --routing=true --style=css --strict=false cd msal-angular-tutorial npm install @angular/material @angular/cdk npm install @azure/msal-browser @azure/msal-angular ng generate component home ng generate component profile
Настройка приложения и изменение базового пользовательского интерфейса
Откройте src/app/app.module.ts.
MsalInterceptor
НеобходимоMsalModule
добавитьimports
константу вместе с константойisIE
. Вы также добавите модули материалов. Замените все содержимое файла следующим фрагментом кода:import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { NgModule } from "@angular/core"; import { MatButtonModule } from "@angular/material/button"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatListModule } from "@angular/material/list"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalModule, MsalRedirectComponent } from "@azure/msal-angular"; import { PublicClientApplication } from "@azure/msal-browser"; const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1; @NgModule({ declarations: [AppComponent, HomeComponent, ProfileComponent], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: "Enter_the_Application_Id_here", // Application (client) ID from the app registration authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", // The Azure cloud instance and the app's sign-in audience (tenant ID, common, organizations, or consumers) redirectUri: "Enter_the_Redirect_Uri_Here", // This is your redirect URI }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: isIE, // Set to true for Internet Explorer 11 }, }), null, null ), ], providers: [], bootstrap: [AppComponent, MsalRedirectComponent], }) export class AppModule {}
Замените следующие значения значениями, полученными из Центра администрирования Microsoft Entra. Подробные сведения о доступных настраиваемых параметрах см. в статье Initialize client applications using MSAL.js (Инициализация клиентских приложений с помощью MSAL.js).
clientId
— идентификатор приложения, который также называется клиентом. ЗаменитеEnter_the_Application_Id_Here
значение идентификатора приложения (клиента), записанное ранее на странице обзора зарегистрированного приложения.authority
— Это состоит из двух частей:- Экземпляр — конечная точка поставщика облачных служб. Для основного или глобального облака Azure введите
https://login.microsoftonline.com
. Ознакомьтесь с различными доступными конечными точками в национальных облаках. - Идентификатор клиента — это идентификатор клиента, в котором зарегистрировано приложение. Замените
_Enter_the_Tenant_Info_Here
значение идентификатора каталога (клиента), записанное ранее на странице обзора зарегистрированного приложения.
- Экземпляр — конечная точка поставщика облачных служб. Для основного или глобального облака Azure введите
redirectUri
— расположение, в котором сервер авторизации отправляет пользователя после успешного разрешения приложения и предоставления кода авторизации или маркера доступа. ЗаменитеEnter_the_Redirect_Uri_Here
наhttp://localhost:4200
.
Откройте src/app/app-routing.module.ts и добавьте маршруты в компоненты домашнегои профиля. Замените все содержимое файла следующим фрагментом кода:
import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { BrowserUtils } from "@azure/msal-browser"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; const routes: Routes = [ { path: "profile", component: ProfileComponent, }, { path: "", component: HomeComponent, }, ]; const isIframe = window !== window.parent && !window.opener; @NgModule({ imports: [ RouterModule.forRoot(routes, { // Don't perform initial navigation in iframes or popups initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? "enabledNonBlocking" : "disabled", // Set to enabledBlocking to use Angular Universal }), ], exports: [RouterModule], }) export class AppRoutingModule {}
Откройте src/app/app.component.html и замените существующий код следующим кодом:
<mat-toolbar color="primary"> <a class="title" href="/">{{ title }}</a> <div class="toolbar-spacer"></div> <a mat-button [routerLink]="['profile']">Profile</a> <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button> </mat-toolbar> <div class="container"> <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe --> <router-outlet *ngIf="!isIframe"></router-outlet> </div>
Откройте src/style.css , чтобы определить CSS:
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css"; html, body { height: 100%; } body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } .container { margin: 1%; }
Откройте src/app/app.component.css , чтобы добавить стили CSS в приложение:
.toolbar-spacer { flex: 1 1 auto; } a.title { color: white; }
Вход с помощью всплывающих окон
Откройте src/app/app.component.ts и замените содержимое файла следующим образом, чтобы войти пользователя с помощью всплывающего окна:
import { MsalService } from '@azure/msal-angular'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; constructor(private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; } login() { this.authService.loginPopup() .subscribe({ next: (result) => { console.log(result); this.setLoginDisplay(); }, error: (error) => console.log(error) }); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } }
Вход с использованием перенаправлений
Обновите src/app/app.module.ts, чтобы обеспечить начальную загрузку
MsalRedirectComponent
. Это выделенный компонент перенаправления, который обрабатывает перенаправления. Измените импорт иAppComponent
начальнуюMsalModule
загрузку следующим образом:... import { MsalModule, MsalRedirectComponent } from '@azure/msal-angular'; // Updated import ... bootstrap: [AppComponent, MsalRedirectComponent] // MsalRedirectComponent bootstrapped here ...
Откройте src/index.html и замените все содержимое файла следующим фрагментом кода, который добавляет
<app-redirect>
селектор:<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>msal-angular-tutorial</title> <base href="/"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" type="image/x-icon" href="favicon.ico"> </head> <body> <app-root></app-root> <app-redirect></app-redirect> </body> </html>
Откройте src/app/app.component.ts и замените код следующим образом, чтобы выполнить вход пользователя с помощью перенаправления с полным кадром:
import { MsalService } from '@azure/msal-angular'; import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; constructor(private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; } login() { this.authService.loginRedirect(); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } }
Перейдите к src/app/home/home.component.ts и замените все содержимое файла следующим фрагментом кода, чтобы подписаться на
LOGIN_SUCCESS
событие:import { Component, OnInit } from '@angular/core'; import { MsalBroadcastService, MsalService } from '@azure/msal-angular'; import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser'; import { filter } from 'rxjs/operators'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { } ngOnInit(): void { this.msalBroadcastService.msalSubject$ .pipe( filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS), ) .subscribe((result: EventMessage) => { console.log(result); }); } }
Условная отрисовка
Чтобы отобразить определенный пользовательский интерфейс (пользовательский интерфейс) только для прошедших проверку подлинности пользователей, компоненты должны подписаться на MsalBroadcastService
приложение, чтобы узнать, выполнили ли пользователи вход и взаимодействие завершено.
Добавьте
MsalBroadcastService
в файл src/app/app.component.ts и подпишитесь на наблюдаемый элементinProgress$
, чтобы перед отрисовкой пользовательского интерфейса проверить, завершено ли взаимодействие и выполнен ли вход в учетную запись. Код должен выглядеть следующим образом:import { Component, OnInit, OnDestroy } from '@angular/core'; import { MsalService, MsalBroadcastService } from '@azure/msal-angular'; import { InteractionStatus } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { this.authService.loginRedirect(); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
Обновите код в файле src/app/home/home.component.ts, чтобы также проверить наличие взаимодействия, которое должно быть завершено перед обновлением пользовательского интерфейса. Код должен выглядеть следующим образом:
import { Component, OnInit } from '@angular/core'; import { MsalBroadcastService, MsalService } from '@azure/msal-angular'; import { EventMessage, EventType, InteractionStatus } from '@azure/msal-browser'; import { filter } from 'rxjs/operators'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { loginDisplay = false; constructor(private authService: MsalService, private msalBroadcastService: MsalBroadcastService) { } ngOnInit(): void { this.msalBroadcastService.msalSubject$ .pipe( filter((msg: EventMessage) => msg.eventType === EventType.LOGIN_SUCCESS), ) .subscribe((result: EventMessage) => { console.log(result); }); this.msalBroadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None) ) .subscribe(() => { this.setLoginDisplay(); }) } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } }
Замените код в файле src/app/home/home.component.html на следующие условные инструкции Display.
<div *ngIf="!loginDisplay"> <p>Please sign-in to see your profile information.</p> </div> <div *ngIf="loginDisplay"> <p>Login successful!</p> <p>Request your profile information by clicking Profile above.</p> </div>
Реализация Angular Guard
Класс MsalGuard
— это один из них, который можно использовать для защиты маршрутов и проверки подлинности перед доступом к защищенному маршруту. Ниже описано, как добавить MsalGuard
Profile
маршрут. Защита Profile
маршрута означает, что даже если пользователь не войтет с помощью кнопки, если он пытается получить доступ к Profile
маршруту или выбрать Profile
кнопку, MsalGuard
предложит пользователю пройти проверку подлинности с помощью Login
всплывающего окна или перенаправления перед отображением Profile
страницы.
MsalGuard
— это удобный класс, который можно использовать для улучшения взаимодействия с пользователем, но он не должен полагаться на безопасность. Злоумышленники могут обойти защиту на стороне клиента, и вы должны убедиться, что сервер не возвращает какие-либо данные, к которым пользователь не должен получить доступ.
Добавьте в файл src/app/app.module.ts класс
MsalGuard
в качестве поставщика в приложении, а также добавьте конфигурации дляMsalGuard
. Области, необходимые для получения маркеров в дальнейшем, можно предоставить вauthRequest
, а в качестве типа взаимодействия для защиты можно установитьRedirect
илиPopup
. Код должен выглядеть так, как показано ниже.import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { NgModule } from "@angular/core"; import { MatButtonModule } from "@angular/material/button"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatListModule } from "@angular/material/list"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalModule, MsalRedirectComponent, MsalGuard, } from "@azure/msal-angular"; // MsalGuard added to imports import { PublicClientApplication, InteractionType, } from "@azure/msal-browser"; // InteractionType added to imports const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1; @NgModule({ declarations: [AppComponent, HomeComponent, ProfileComponent], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: "Enter_the_Application_Id_here", authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", redirectUri: "Enter_the_Redirect_Uri_Here", }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: isIE, }, }), { interactionType: InteractionType.Redirect, // MSAL Guard Configuration authRequest: { scopes: ["user.read"], }, }, null ), ], providers: [ MsalGuard, // MsalGuard added as provider here ], bootstrap: [AppComponent, MsalRedirectComponent], }) export class AppModule {}
В файле src/app/app-routing.module.ts задайте
MsalGuard
для маршрутов, которые необходимо защитить.import { NgModule } from "@angular/core"; import { Routes, RouterModule } from "@angular/router"; import { BrowserUtils } from "@azure/msal-browser"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalGuard } from "@azure/msal-angular"; const routes: Routes = [ { path: "profile", component: ProfileComponent, canActivate: [MsalGuard], }, { path: "", component: HomeComponent, }, ]; const isIframe = window !== window.parent && !window.opener; @NgModule({ imports: [ RouterModule.forRoot(routes, { // Don't perform initial navigation in iframes or popups initialNavigation: !BrowserUtils.isInIframe() && !BrowserUtils.isInPopup() ? "enabledNonBlocking" : "disabled", // Set to enabledBlocking to use Angular Universal }), ], exports: [RouterModule], }) export class AppRoutingModule {}
Настройте вызовы для входа в файле src/app/app.component.ts, чтобы перенести в учетную запись набор
authRequest
в конфигурации защиты. Теперь код должен выглядеть так, как показано ниже.import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; import { InteractionStatus, RedirectRequest } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { if (this.msalGuardConfig.authRequest){ this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest); } else { this.authService.loginRedirect(); } } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
Получение маркера
Перехватчик Angular
MSAL Angular предоставляет класс Interceptor
, который автоматически получает маркеры для исходящих запросов, которые используют клиент Angular http
для известных защищенных ресурсов.
Добавьте в файл src/app/app.module.ts класс
Interceptor
в качестве поставщика в приложении вместе с соответствующими конфигурациями. Теперь код должен выглядеть так, как показано ниже.import { BrowserModule } from "@angular/platform-browser"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { NgModule } from "@angular/core"; import { HTTP_INTERCEPTORS, HttpClientModule } from "@angular/common/http"; // Import import { MatButtonModule } from "@angular/material/button"; import { MatToolbarModule } from "@angular/material/toolbar"; import { MatListModule } from "@angular/material/list"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent } from "./app.component"; import { HomeComponent } from "./home/home.component"; import { ProfileComponent } from "./profile/profile.component"; import { MsalModule, MsalRedirectComponent, MsalGuard, MsalInterceptor, } from "@azure/msal-angular"; // Import MsalInterceptor import { InteractionType, PublicClientApplication, } from "@azure/msal-browser"; const isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 || window.navigator.userAgent.indexOf("Trident/") > -1; @NgModule({ declarations: [AppComponent, HomeComponent, ProfileComponent], imports: [ BrowserModule, BrowserAnimationsModule, AppRoutingModule, MatButtonModule, MatToolbarModule, MatListModule, HttpClientModule, MsalModule.forRoot( new PublicClientApplication({ auth: { clientId: "Enter_the_Application_Id_Here", authority: "Enter_the_Cloud_Instance_Id_Here/Enter_the_Tenant_Info_Here", redirectUri: "Enter_the_Redirect_Uri_Here", }, cache: { cacheLocation: "localStorage", storeAuthStateInCookie: isIE, }, }), { interactionType: InteractionType.Redirect, authRequest: { scopes: ["user.read"], }, }, { interactionType: InteractionType.Redirect, // MSAL Interceptor Configuration protectedResourceMap: new Map([ ["Enter_the_Graph_Endpoint_Here/v1.0/me", ["user.read"]], ]), } ), ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: MsalInterceptor, multi: true, }, MsalGuard, ], bootstrap: [AppComponent, MsalRedirectComponent], }) export class AppModule {}
Защищенные ресурсы предоставляются в виде
protectedResourceMap
. Для URL-адресов, которые вы указываете в коллекцииprotectedResourceMap
, учитывается регистр. Для каждого ресурса добавьте области, которые должны быть возвращены в маркере доступа.Например:
["user.read"]
для Microsoft Graph;["<Application ID URL>/scope"]
для настраиваемых веб-API (т. е.api://<Application ID>/access_as_user
).
Измените значения в
protectedResourceMap
, как описано далее.Enter_the_Graph_Endpoint_Here
экземпляр API Microsoft Graph, с которым должно взаимодействовать приложение. Для глобальной конечной точки API Microsoft Graph замените эту строкуhttps://graph.microsoft.com
на . Дополнительные сведения о конечных точках в национальных облачных развертываниях см. в статье Национальные облачные развертывания в документации по Microsoft Graph.
Замените код в src/app/profile/profile.component.ts , чтобы получить профиль пользователя http-запросом и заменить
GRAPH_ENDPOINT
ее конечной точкой Microsoft Graph:import { Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; const GRAPH_ENDPOINT = 'Enter_the_Graph_Endpoint_Here/v1.0/me'; type ProfileType = { givenName?: string, surname?: string, userPrincipalName?: string, id?: string }; @Component({ selector: 'app-profile', templateUrl: './profile.component.html', styleUrls: ['./profile.component.css'] }) export class ProfileComponent implements OnInit { profile!: ProfileType; constructor( private http: HttpClient ) { } ngOnInit() { this.getProfile(); } getProfile() { this.http.get(GRAPH_ENDPOINT) .subscribe(profile => { this.profile = profile; }); } }
Замените пользовательский интерфейс в файле src/app/profile/profile.component.html, чтобы отобразить сведения о профиле.
<div> <p><strong>First Name: </strong> {{profile?.givenName}}</p> <p><strong>Last Name: </strong> {{profile?.surname}}</p> <p><strong>Email: </strong> {{profile?.userPrincipalName}}</p> <p><strong>Id: </strong> {{profile?.id}}</p> </div>
Выйти
Обновите код в файле src/app/app.component.html, чтобы кнопка
Logout
отображалась только при выполнении условия.<mat-toolbar color="primary"> <a class="title" href="/">{{ title }}</a> <div class="toolbar-spacer"></div> <a mat-button [routerLink]="['profile']">Profile</a> <button mat-raised-button *ngIf="!loginDisplay" (click)="login()">Login</button> <button mat-raised-button *ngIf="loginDisplay" (click)="logout()">Logout</button> </mat-toolbar> <div class="container"> <!--This is to avoid reload during acquireTokenSilent() because of hidden iframe --> <router-outlet *ngIf="!isIframe"></router-outlet> </div>
Выход с помощью перенаправлений
Обновите код в файле src/app/app.component.ts, чтобы пользователь выходил из учетной записи с помощью перенаправлений.
import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; import { InteractionStatus, RedirectRequest } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { if (this.msalGuardConfig.authRequest){ this.authService.loginRedirect({...this.msalGuardConfig.authRequest} as RedirectRequest); } else { this.authService.loginRedirect(); } } logout() { // Add log out function here this.authService.logoutRedirect({ postLogoutRedirectUri: 'http://localhost:4200' }); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
Выход с помощью всплывающих окон
Обновите код в файле src/app/app.component.ts, чтобы пользователь выходил из учетной записи с помощью всплывающих окон.
import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; import { MsalService, MsalBroadcastService, MSAL_GUARD_CONFIG, MsalGuardConfiguration } from '@azure/msal-angular'; import { InteractionStatus, PopupRequest } from '@azure/msal-browser'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, OnDestroy { title = 'msal-angular-tutorial'; isIframe = false; loginDisplay = false; private readonly _destroying$ = new Subject<void>(); constructor(@Inject(MSAL_GUARD_CONFIG) private msalGuardConfig: MsalGuardConfiguration, private broadcastService: MsalBroadcastService, private authService: MsalService) { } ngOnInit() { this.isIframe = window !== window.parent && !window.opener; this.broadcastService.inProgress$ .pipe( filter((status: InteractionStatus) => status === InteractionStatus.None), takeUntil(this._destroying$) ) .subscribe(() => { this.setLoginDisplay(); }) } login() { if (this.msalGuardConfig.authRequest){ this.authService.loginPopup({...this.msalGuardConfig.authRequest} as PopupRequest) .subscribe({ next: (result) => { console.log(result); this.setLoginDisplay(); }, error: (error) => console.log(error) }); } else { this.authService.loginPopup() .subscribe({ next: (result) => { console.log(result); this.setLoginDisplay(); }, error: (error) => console.log(error) }); } } logout() { // Add log out function here this.authService.logoutPopup({ mainWindowRedirectUri: "/" }); } setLoginDisplay() { this.loginDisplay = this.authService.instance.getAllAccounts().length > 0; } ngOnDestroy(): void { this._destroying$.next(undefined); this._destroying$.complete(); } }
Тестирование кода
Запустите веб-сервер для прослушивания порта, выполнив приведенные ниже команды в командной строке из папки приложения:
npm install npm start
В браузере введите
http://localhost:4200
и увидите страницу, которая выглядит следующим образом.Нажмите кнопку "Принять" , чтобы предоставить приложению разрешения для профиля. Это произойдет при первом входе.
После предоставления согласия в следующем случае при согласии на запрошенные разрешения веб-приложение отображает страницу успешного входа.
Выберите профиль , чтобы просмотреть сведения о профиле пользователя, возвращенные в ответе на вызов API Microsoft Graph:
Добавление областей и делегированных разрешений
Для чтения профиля пользователя API Microsoft Graph требуется область User.Read. Область User.Read добавляется автоматически в каждую регистрацию приложения. Другие API для Microsoft Graph и пользовательские API для внутреннего сервера могут потребовать других область. Например, для отображения сообщений электронной почты пользователя API Microsoft Graph требуется область Mail.Read.
При добавлении область пользователям может потребоваться предоставить дополнительное согласие для добавленных область.
Примечание.
При увеличении количества областей от пользователя могут потребоваться дополнительные согласия.
Справка и поддержка
Если вам нужна помощь, если вы хотите сообщить о проблеме или узнать о доступных вариантах поддержки, воспользуйтесь статьей Возможности получения поддержки и справки для разработчиков.
Следующие шаги
- Дополнительные сведения см. в статье о создании одностраничного приложения React, которое входит в систему пользователей в следующей серии руководств по нескольким частьм.