Эта статья была первоначально опубликована в блоге разработчиков OKTA . Спасибо за поддержку партнеров, которые делают возможным использование SitePoint.
Angular (ранее называвшийся Angular 2.0) быстро становится одним из самых мощных способов создания современного одностраничного приложения. Основным преимуществом Angular является создание многократно используемых компонентов, которые помогут вам отделить различные проблемы в вашем приложении. Возьмем, к примеру, аутентификацию: это может быть болезненно для сборки, но как только вы поместите ее в компонент, логика аутентификации может быть повторно использована в вашем приложении.
Angular CLI позволяет легко создавать новые компоненты и даже целые проекты. Если вы не использовали Angular CLI для быстрой генерации Angular-кода, вас ждет угощение!
В этом примере вы создадите простое веб-приложение с Angular CLI, инструментом для разработки Angular. Вы создадите приложение с функциями поиска и редактирования, а затем добавите аутентификацию.
Создать угловое приложение
СОВЕТ: Если вы хотите пропустить создание приложения Angular и получить право на добавление аутентификации, вы можете клонировать мой проект ng-demo
, а затем перейти к разделу Создание приложения OpenID Connect в Okta .
git clone https://github.com/mraible/ng-demo.git
Что вам нужно
- Около 20 минут
- Любимый текстовый редактор или IDE. Я рекомендую IntelliJ IDEA
- Node.js и npm установлены. Я рекомендую использовать nvm
- Угловой CLI установлен. Если у вас не установлен Angular CLI, установите его с помощью
npm install -g @angular/cli
Создайте новый проект с помощью команды ng new
:
ng new ng-demo
Это создаст ng-demo
проект ng-demo
и запустит в нем npm install
. Это может занять около минуты, но это может варьироваться в зависимости от скорости вашего соединения.
[mraible:~/dev] $ ng new ng-demo installing ng create .editorconfig create README.md create src/app/app.component.css create src/app/app.component.html create src/app/app.component.spec.ts create src/app/app.component.ts create src/app/app.module.ts create src/assets/.gitkeep create src/environments/environment.prod.ts create src/environments/environment.ts create src/favicon.ico create src/index.html create src/main.ts create src/polyfills.ts create src/styles.css create src/test.ts create src/tsconfig.app.json create src/tsconfig.spec.json create src/typings.d.ts create .angular-cli.json create e2e/app.e2e-spec.ts create e2e/app.po.ts create e2e/tsconfig.e2e.json create .gitignore create karma.conf.js create package.json create protractor.conf.js create tsconfig.json create tslint.json Successfully initialized git. Installing packages for tooling via npm. Installed packages for tooling via npm. You can `ng set --global packageManager=yarn`. Project 'ng-demo' successfully created. [mraible:~] 46s $
Вы можете увидеть, какую версию Angular CLI вы используете с помощью ng --version
.
$ ng --version _ _ ____ _ ___ / \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | | / ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ @angular/cli: 1.3.2 node: 8.4.0 os: darwin x64
Запустите ваше угловое приложение
Проект настроен с помощью веб-пакета dev server . Чтобы начать, убедитесь, что вы находитесь в каталоге ng-demo
, затем запустите:
ng serve
Вы должны увидеть экран, подобный приведенному ниже, по адресу http: // localhost: 4200 .
Вы можете убедиться, что тесты вашего нового проекта пройдены, запустите ng test
:
$ ng test ... Chrome 60.0.3112 (Mac OS X 10.12.6): Executed 3 of 3 SUCCESS (0.239 secs / 0.213 secs)
Добавить функцию поиска
Чтобы добавить функцию поиска, откройте проект в IDE или в своем любимом текстовом редакторе. Для IntelliJ IDEA используйте Файл> Новый проект> Статическая сеть и укажите каталог ng-demo
.
В окне терминала перейдите в каталог вашего проекта и выполните следующую команду. Это создаст поисковый компонент.
$ ng g component search installing component create src/app/search/search.component.css create src/app/search/search.component.html create src/app/search/search.component.spec.ts create src/app/search/search.component.ts update src/app/app.module.ts
Откройте src/app/search/search.component.html
и замените его HTML- src/app/search/search.component.html
умолчанию следующим:
<h2>Search</h2> <form> <input type="search" name="query" [(ngModel)]="query" (keyup.enter)="search()"> <button type="button" (click)="search()">Search</button> </form> <pre>{{searchResults | json}}</pre>
Документация по Router для Angular предоставляет информацию, необходимую для настройки маршрута к только что сгенерированному SearchComponent
. Вот краткое резюме:
В src/app/app.module.ts
добавьте константу appRoutes
и импортируйте ее в @NgModule
:
import { Routes, RouterModule } from '@angular/router'; const appRoutes: Routes = [ {path: 'search', component: SearchComponent}, {path: '', redirectTo: '/search', pathMatch: 'full'} ]; @NgModule({ ... imports: [ ... RouterModule.forRoot(appRoutes) ] ... }) export class AppModule { }
В src/app/app.component.html
настройте содержимое заполнителя и добавьте <router-outlet>
для отображения маршрутов.
<h1>Welcome to {{title}}!</h1> <!-- Routed views go here --> <router-outlet></router-outlet>
Теперь, когда у вас есть настройка маршрутизации, вы можете продолжить писать функцию поиска.
Если вы по-прежнему работаете, ваш браузер должен автоматически обновиться. Если нет, перейдите по адресу http: // localhost: 4200. Скорее всего, вы увидите пустой экран. Откройте консоль JavaScript, и вы увидите проблему.
Чтобы решить эту проблему, откройте src/app/app.module.ts
и добавьте FormsModule
в качестве импорта в @NgModule
:
import { FormsModule } from '@angular/forms'; @NgModule({ ... imports: [ ... FormsModule ] ... }) export class AppModule { }
Теперь вы должны увидеть форму поиска.
Если вы хотите добавить CSS для этих компонентов, откройте src/app/search/search.component.css
и добавьте немного CSS. Например:
:host { display: block; padding: 0 20px; }
В этом разделе показано, как создать новый компонент для базового приложения Angular с помощью Angular CLI. В следующем разделе будет показано, как создать и использовать файл JSON и localStorage
для создания поддельного API.
Чтобы получить результаты поиска, создайте SearchService
который отправляет HTTP-запросы в файл JSON. Начните с создания нового сервиса.
$ ng g service search installing service create src/app/search.service.spec.ts create src/app/search.service.ts WARNING Service is generated but not provided, it must be provided to be used
Переместите сгенерированный search.service.ts
и его тест в app/shared/search
. Вам нужно будет создать этот каталог.
mkdir -p src/app/shared/search mv src/app/search.service.* src/app/shared/search/.
Создайте src/assets/data/people.json
для хранения ваших данных.
[ { "id": 1, "name": "Peyton Manning", "phone": "(303) 567-8910", "address": { "street": "1234 Main Street", "city": "Greenwood Village", "state": "CO", "zip": "80111" } }, { "id": 2, "name": "Demaryius Thomas", "phone": "(720) 213-9876", "address": { "street": "5555 Marion Street", "city": "Denver", "state": "CO", "zip": "80202" } }, { "id": 3, "name": "Von Miller", "phone": "(917) 323-2333", "address": { "street": "14 Mountain Way", "city": "Vail", "state": "CO", "zip": "81657" } } ]
Измените src/app/shared/search/search.service.ts
и предоставьте Http
в качестве зависимости в своем конструкторе. В этом же файле создайте метод getAll()
чтобы собрать всех людей. Кроме того, определите классы Address
и Person
которые будет перенаправляться JSON.
import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import 'rxjs/add/operator/map'; @Injectable() export class SearchService { constructor(private http: Http) {} getAll() { return this.http.get('assets/data/people.json') .map((res: Response) => res.json()); } } export class Address { street: string; city: string; state: string; zip: string; constructor(obj?: any) { this.street = obj && obj.street || null; this.city = obj && obj.city || null; this.state = obj && obj.state || null; this.zip = obj && obj.zip || null; } } export class Person { id: number; name: string; phone: string; address: Address; constructor(obj?: any) { this.id = obj && Number(obj.id) || null; this.name = obj && obj.name || null; this.phone = obj && obj.phone || null; this.address = obj && obj.address || null; } }
Чтобы сделать эти классы доступными для использования вашими компонентами, отредактируйте src/app/shared/index.ts
и добавьте следующее:
export * from './search/search.service';
Причиной создания этого файла является то, что вы можете импортировать несколько классов в одной строке, а не импортировать каждый отдельный класс в отдельных строках.
В src/app/search/search.component.ts
добавьте импорт для этих классов.
import { Person, SearchService } from '../shared';
Теперь вы можете добавить переменные query
и searchResults
. Пока вы там, измените конструктор, чтобы добавить SearchService
.
export class SearchComponent implements OnInit { query: string; searchResults: Array<Person>; constructor(private searchService: SearchService) {}
Затем реализуйте метод search()
для вызова метода getAll()
службы.
search(): void { this.searchService.getAll().subscribe( data => { this.searchResults = data; }, error => console.log(error) ); }
В этот момент вы, скорее всего, увидите следующее сообщение в консоли вашего браузера.
ORIGINAL EXCEPTION: No provider for SearchService!
Чтобы исправить ошибку «Нет поставщика», обновите app.module.ts
чтобы импортировать SearchService
и добавить службу в список поставщиков. Поскольку SearchService
зависит от Http
, вам также необходимо импортировать HttpModule
.
import { SearchService } from './shared'; import { HttpModule } from '@angular/http'; @NgModule({ ... imports: [ ... HttpModule ], providers: [SearchService], bootstrap: [AppComponent] })
Теперь нажатие кнопки поиска должно работать. Чтобы результаты выглядели лучше, удалите <pre>
и замените его на <table>
в src/app/search/search.component.html
.
<table *ngIf="searchResults"> <thead> <tr> <th>Name</th> <th>Phone</th> <th>Address</th> </tr> </thead> <tbody> <tr *ngFor="let person of searchResults; let i=index"> <td>{{person.name}}</td> <td>{{person.phone}}</td> <td>{{person.address.street}}<br/> {{person.address.city}}, {{person.address.state}} {{person.address.zip}} </td> </tr> </tbody> </table>
Затем добавьте несколько дополнительных CSS в src/app/search/search.component.css
чтобы улучшить макет таблицы.
table { margin-top: 10px; border-collapse: collapse; } th { text-align: left; border-bottom: 2px solid #ddd; padding: 8px; } td { border-top: 1px solid #ddd; padding: 8px; }
Теперь результаты поиска выглядят лучше.
Но подождите, у вас все еще нет функции поиска! Чтобы добавить функцию поиска, добавьте метод search()
в SearchService
.
import { Observable } from 'rxjs'; search(q: string): Observable<any> { if (!q || q === '*') { q = ''; } else { q = q.toLowerCase(); } return this.getAll().map(data => data.filter(item => JSON.stringify(item).toLowerCase().includes(q))); }
Затем рефакторинг SearchComponent
для вызова этого метода с его переменной query
.
search(): void { this.searchService.search(this.query).subscribe( data => { this.searchResults = data; }, error => console.log(error) ); }
Теперь результаты поиска будут отфильтрованы по введенному вами значению запроса.
В этом разделе показано, как получить и отобразить результаты поиска. Следующий раздел основан на этом и показывает, как редактировать и сохранять записи.
Добавить функцию редактирования
Измените src/app/search/search.component.html
чтобы добавить ссылку для редактирования человека.
<td><a [routerLink]="['/edit', person.id]">{{person.name}}</a></td>
Выполните следующую команду, чтобы сгенерировать EditComponent
.
$ ng g component edit installing component create src/app/edit/edit.component.css create src/app/edit/edit.component.html create src/app/edit/edit.component.spec.ts create src/app/edit/edit.component.ts update src/app/app.module.ts
Добавьте маршрут для этого компонента в src/app/app.module.ts
:
const appRoutes: Routes = [ {path: 'search', component: SearchComponent}, {path: 'edit/:id', component: EditComponent}, {path: '', redirectTo: '/search', pathMatch: 'full'} ];
Обновите src/app/edit/edit.component.html
для отображения редактируемой формы. Вы можете заметить, что я добавил атрибуты id
для большинства элементов. Это должно упростить процесс написания интеграционных тестов с помощью Protractor.
<div *ngIf="person"> <h3>{{editName}}</h3> <div> <label>Id:</label> {{person.id}} </div> <div> <label>Name:</label> <input [(ngModel)]="editName" name="name" id="name" placeholder="name"/> </div> <div> <label>Phone:</label> <input [(ngModel)]="editPhone" name="phone" id="phone" placeholder="Phone"/> </div> <fieldset> <legend>Address:</legend> <address> <input [(ngModel)]="editAddress.street" id="street"><br/> <input [(ngModel)]="editAddress.city" id="city">, <input [(ngModel)]="editAddress.state" id="state" size="2"> <input [(ngModel)]="editAddress.zip" id="zip" size="5"> </address> </fieldset> <button (click)="save()" id="save">Save</button> <button (click)="cancel()" id="cancel">Cancel</button> </div>
Измените EditComponent
чтобы импортировать модель и классы обслуживания, а также использовать SearchService
для получения данных.
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Address, Person, SearchService } from '../shared'; import { Subscription } from 'rxjs'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'app-edit', templateUrl: './edit.component.html', styleUrls: ['./edit.component.css'] }) export class EditComponent implements OnInit, OnDestroy { person: Person; editName: string; editPhone: string; editAddress: Address; sub: Subscription; constructor(private route: ActivatedRoute, private router: Router, private service: SearchService) { } ngOnInit() { this.sub = this.route.params.subscribe(params => { const id = + params['id']; // (+) converts string 'id' to a number this.service.get(id).subscribe(person => { if (person) { this.editName = person.name; this.editPhone = person.phone; this.editAddress = person.address; this.person = person; } else { this.gotoList(); } }); }); } ngOnDestroy() { this.sub.unsubscribe(); } cancel() { this.router.navigate(['/search']); } save() { this.person.name = this.editName; this.person.phone = this.editPhone; this.person.address = this.editAddress; this.service.save(this.person); this.gotoList(); } gotoList() { if (this.person) { this.router.navigate(['/search', {term: this.person.name} ]); } else { this.router.navigate(['/search']); } } }
Измените SearchService
чтобы он содержал функции для поиска человека по его идентификатору и сохранения его. Пока вы там, измените метод search()
чтобы быть в курсе обновленных объектов в localStorage
.
search(q: string): Observable<any> { if (!q || q === '*') { q = ''; } else { q = q.toLowerCase(); } return this.getAll().map(data => { const results: any = []; data.map(item => { // check for item in localStorage if (localStorage['person' + item.id]) { item = JSON.parse(localStorage['person' + item.id]); } if (JSON.stringify(item).toLowerCase().includes(q)) { results.push(item); } }); return results; }); } get(id: number) { return this.getAll().map(all => { if (localStorage['person' + id]) { return JSON.parse(localStorage['person' + id]); } return all.find(e => e.id === id); }); } save(person: Person) { localStorage['person' + person.id] = JSON.stringify(person); }
Вы можете добавить CSS в src/app/edit/edit.component.css
если хотите, чтобы форма выглядела немного лучше.
:host { display: block; padding: 0 20px; } button { margin-top: 10px; }
На этом этапе вы сможете найти человека и обновить его информацию.
<form>
src/app/edit/edit.component.html
<form>
в src/app/edit/edit.component.html
вызывает функцию save()
для обновления данных человека. Вы уже реализовали это выше.
Функция вызывает функцию gotoList()
которая добавляет имя человека в URL при отправке пользователя обратно на экран поиска.
gotoList() { if (this.person) { this.router.navigate(['/search', {term: this.person.name} ]); } else { this.router.navigate(['/search']); } }
Поскольку SearchComponent
не выполняет поиск автоматически при выполнении этого URL, добавьте следующую логику, чтобы сделать это в своем конструкторе.
import { ActivatedRoute } from '@angular/router'; import { Subscription } from 'rxjs'; ... sub: Subscription; constructor(private searchService: SearchService, private route: ActivatedRoute) { this.sub = this.route.params.subscribe(params => { if (params['term']) { this.query = decodeURIComponent(params['term']); this.search(); } }); }
Вы захотите реализовать OnDestroy
и определить метод ngOnDestroy
для очистки этой подписки.
import { Component, OnInit, OnDestroy } from '@angular/core'; export class SearchComponent implements OnInit, OnDestroy { ... ngOnDestroy() { this.sub.unsubscribe(); } }
После внесения всех этих изменений вы сможете искать / редактировать / обновлять информацию о человеке. Если это работает — хорошая работа!
Проверка формы
Вы можете заметить, что вы можете очистить любой элемент ввода в форме и сохранить его. По крайней мере, поле name
должно быть обязательным. Иначе нечего нажимать в результатах поиска.
Чтобы сделать имя обязательным, измените edit.component.html
чтобы добавить required
атрибут к имени <input>
.
<input [(ngModel)]="editName" name="name" id="name" placeholder="name" required/>
Вам также нужно будет обернуть все в элемент <form>
. Добавьте <form>
после <h3>
и закройте его до последнего </div>
. Вам также необходимо добавить обработчик (ngSubmit)
в форму и изменить кнопку сохранения на обычную кнопку отправки.
<h3>{{editName}}</h3> <form (ngSubmit)="save()" ngNativeValidate> ... <button type="submit" id="save">Save</button> <button (click)="cancel()" id="cancel">Cancel</button> </form>
После внесения этих изменений любое поле с required
атрибутом будет обязательным.
На этом снимке экрана вы можете заметить, что поля адреса пусты. Это объясняется ошибкой в вашей консоли.
If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions. Example 1: <input [(ngModel)]="person.firstName" name="first"> Example 2: <input [(ngModel)]="person.firstName" [ngModelOptions]="{standalone: true}">
Чтобы исправить, добавьте атрибут name
ко всем адресным полям. Например:
<address> <input [(ngModel)]="editAddress.street" name="street" id="street"><br/> <input [(ngModel)]="editAddress.city" name="city" id="city">, <input [(ngModel)]="editAddress.state" name="state" id="state" size="2"> <input [(ngModel)]="editAddress.zip" name="zip" id="zip" size="5"> </address>
Теперь значения должны отображаться во всех полях и name
должно быть обязательным.
Если вы хотите предоставить свои собственные сообщения проверки, а не полагаться на браузер, выполните следующие шаги:
- Удалите
ngNativeValidate
и добавьте#editForm="ngForm"
к элементу<form>
. - Добавьте
#name="ngModel"
к#name="ngModel"
<input id="name">
. - Добавьте
[disabled]="!editForm.form.valid"
к кнопке Сохранить . - Добавьте следующее в поле
name
чтобы отобразить ошибку проверки.
<div [hidden]="name.valid || name.pristine" style="color: red"> Name is required </div>
Чтобы узнать больше о формах и проверке, смотрите документацию угловых форм .
Создать приложение OpenID Connect в Okta
OpenID Connect (OIDC) построен на основе протокола OAuth 2.0. Это позволяет клиентам проверять личность пользователя, а также получать информацию об их базовом профиле. Чтобы узнать больше, см. Https://openid.net/connect .
Чтобы интегрировать Okta для аутентификации пользователя, вам сначала нужно зарегистрироваться и создать приложение OIDC.
Войдите в свою учетную запись Okta или создайте ее, если у вас ее еще нет. Перейдите в Приложения и нажмите кнопку Добавить приложение . Выберите SPA и нажмите Далее . На следующей странице укажите http://localhost:4200
в качестве базового URI, URI перенаправления входа в систему и URI перенаправления выхода из системы. Нажмите Готово, и вы должны увидеть настройки, подобные следующим.
Установите проект Manfred Steyer, чтобы добавить поддержку OAuth 2 и OpenID Connect, используя npm.
npm install --save angular-oauth2-oidc
Измените src/app/app.component.ts
чтобы импортировать OAuthService
и настройте ваше приложение на использование параметров приложения Okta.
import { OAuthService, JwksValidationHandler } from 'angular-oauth2-oidc'; ... constructor(private oauthService: OAuthService) { this.oauthService.redirectUri = window.location.origin; this.oauthService.clientId = '{client-id}'; this.oauthService.scope = 'openid profile email'; this.oauthService.issuer = 'https://dev-{dev-id}.oktapreview.com'; this.oauthService.tokenValidationHandler = new JwksValidationHandler(); // Load Discovery Document and then try to login the user this.oauthService.loadDiscoveryDocument().then(() => { this.oauthService.tryLogin(); }); } ...
Создайте src/app/home/home.component.ts
и настройте его так, чтобы в нем были кнопки входа и src/app/home/home.component.ts
import { Component } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; @Component({ template: ` <div *ngIf="givenName"> <h2>Welcome, {{givenName}}!</h2> <button (click)="logout()">Logout</button> <p><a routerLink="/search" routerLinkActive="active">Search</a></p> </div> <div *ngIf="!givenName"> <button (click)="login()">Login</button> </div>` }) export class HomeComponent { constructor(private oauthService: OAuthService) { } login() { this.oauthService.initImplicitFlow(); } logout() { this.oauthService.logOut(); } get givenName() { const claims = this.oauthService.getIdentityClaims(); if (!claims) { return null; } return claims['name']; } }
Создайте src/app/shared/auth/auth.guard.service.ts
чтобы перейти к HomeComponent
если пользователь не аутентифицирован.
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; import { OAuthService } from 'angular-oauth2-oidc'; @Injectable() export class AuthGuard implements CanActivate { constructor(private oauthService: OAuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { if (this.oauthService.hasValidIdToken()) { return true; } this.router.navigate(['/home']); return false; } }
Экспортируйте AuthGuard
в src/shared/index.ts
:
export * from './auth/auth.guard.service';
Импортируйте OAuthModule
в src/app/app.module.ts
, настройте новый HomeComponent
и заблокируйте маршруты /search
и /edit
с помощью AuthGuard
.
import { OAuthModule } from 'angular-oauth2-oidc'; import { HomeComponent } from './home/home.component'; import { SearchService, AuthGuard } from './shared'; const appRoutes: Routes = [ {path: 'search', component: SearchComponent, canActivate: [AuthGuard]}, {path: 'edit/:id', component: EditComponent, canActivate: [AuthGuard]}, {path: 'home', component: HomeComponent}, {path: '', redirectTo: 'home', pathMatch: 'full'}, {path: '**', redirectTo: 'home'} ]; @NgModule({ declarations: [ ... HomeComponent ], imports: [ ... OAuthModule.forRoot() ], providers: [ AuthGuard, SearchService ], bootstrap: [AppComponent] }) export class AppModule { }
После внесения этих изменений вы сможете запустить ng serve
и увидеть кнопку входа.
Нажмите кнопку « Вход» и войдите в систему с помощью одного из людей, настроенных в вашем приложении Okta.
После входа вы сможете нажать « Поиск» и просмотреть информацию о людях.
Если это работает — отлично! Если вы хотите создать собственную форму входа в свое приложение, продолжайте чтение, чтобы узнать, как использовать Okta Auth SDK с OAuthService
.
Аутентификация с помощью Okta Auth SDK
Пакет Okta Auth SDK построен на основе API аутентификации Otka и API OAuth 2.0, что позволяет вам создавать полноценные возможности входа с использованием JavaScript.
Установите его, используя npm:
npm install @okta/okta-auth-js --save
Добавьте ссылку на основной файл JavaScript этой библиотеки в .angular-cli.json
:
"scripts": [ "../node_modules/@okta/okta-auth-js/dist/okta-auth-js.min.js" ],
Компоненты в этом разделе используют классы Bootstrap CSS. Установите Bootstrap 4.
npm install [email protected] --save
Измените src/styles.css
чтобы добавить ссылку на CSS-файл Bootstrap.
@import "~bootstrap/dist/css/bootstrap.css";
Обновите src/app/app.component.html
чтобы использовать классы Bootstrap для своей панели навигации и грид-системы.
<nav class="navbar navbar-light bg-secondary"> <a class="navbar-brand text-light" href="#">Welcome to {{title}}!</a> </nav> <div class="container-fluid"> <router-outlet></router-outlet> </div>
Создайте src/app/shared/auth/okta.auth.wrapper.ts
чтобы обернуть Okta Auth SDK и интегрировать его с OAuthService
. Его метод login()
использует OktaAuth
для получения токена сеанса и обмена его на ID и токены доступа.
import { OAuthService } from 'angular-oauth2-oidc'; import { Injectable } from '@angular/core'; declare const OktaAuth: any; @Injectable() export class OktaAuthWrapper { private authClient: any; constructor(private oauthService: OAuthService) { this.authClient = new OktaAuth({ url: this.oauthService.issuer }); } login(username: string, password: string): Promise<any> { return this.oauthService.createAndSaveNonce().then(nonce => { return this.authClient.signIn({ username: username, password: password }).then((response) => { if (response.status === 'SUCCESS') { return this.authClient.token.getWithoutPrompt({ clientId: this.oauthService.clientId, responseType: ['id_token', 'token'], scopes: ['openid', 'profile', 'email'], sessionToken: response.sessionToken, nonce: nonce, redirectUri: window.location.origin }) .then((tokens) => { const idToken = tokens[0].idToken; const accessToken = tokens[1].accessToken; const keyValuePair = `#id_token=${encodeURIComponent(idToken)}&access_token=${encodeURIComponent(accessToken)}`; return this.oauthService.tryLogin({ <1> customHashFragment: keyValuePair, disableOAuth2StateCheck: true }); }); } else { return Promise.reject('We cannot handle the ' + response.status + ' status'); } }); }); } }
В приведенном выше коде oauthService.tryLogin()
анализирует и сохраняет idToken
и idToken
чтобы их можно было извлечь с помощью OAuthService.getIdToken()
и OAuthService.getAccessToken()
.
Экспорт OktaAuthWrapper
в src/shared/index.ts
:
export * from './auth/okta.auth.wrapper';
Добавьте OktaAuthWrapper
в качестве поставщика в app.module.ts
.
import { SearchService, AuthGuard, OktaAuthWrapper } from './shared'; @NgModule({ ... providers: [ ... OktaAuthWrapper ], bootstrap: [AppComponent] })
Измените HomeComponent
для объявления OktaAuth
и измените его template
чтобы он имел кнопку для входа в систему, а также форму для входа.
@Component({ template: ` <div *ngIf="givenName" class="col-12 mt-2"> <button (click)="logout()" class="btn btn-sm btn-outline-primary float-right">Logout</button> <h2>Welcome, {{givenName}}!</h2> <p><a routerLink="/search" routerLinkActive="active">Search</a></p> </div> <div class="card mt-2" *ngIf="!givenName"> <div class="card-body"> <h4 class="card-title">Login with Authorization Server</h4> <button class="btn btn-primary" (click)="login()">Login</button> </div> </div> <div class="card mt-2" *ngIf="!givenName"> <div class="card-body"> <h4 class="card-title">Login with Username/Password</h4> <p class="alert alert-error" *ngIf="loginFailed"> Login wasn't successful. </p> <div class="form-group"> <label>Username</label> <input class="form-control" [(ngModel)]="username"> </div> <div class="form-group"> <label>Password</label> <input class="form-control" type="password" [(ngModel)]="password"> </div> <div class="form-group"> <button class="btn btn-primary" (click)="loginWithPassword()">Login</button> </div> </div> </div>` })
После внесения этих изменений HomeComponent
должен отобразиться следующим образом.
Добавьте локальные переменные для полей имени пользователя и пароля, импортируйте OktaAuthWrapper
и loginWithPassword()
метод loginWithPassword()
в HomeComponent
.
import { OktaAuthWrapper } from '../shared'; ... username; password; constructor(private oauthService: OAuthService, private oktaAuthWrapper: OktaAuthWrapper) { } loginWithPassword() { this.oktaAuthWrapper.login(this.username, this.password) .then(_ => console.debug('logged in')) .catch(err => console.error('error logging in', err)); }
Вы должны иметь возможность войти в систему, используя форму, используя одного из зарегистрированных пользователей вашего приложения. После входа в систему вы сможете щелкнуть ссылку Поиск и просмотреть информацию о людях.
Angular + Okta
Если все работает — поздравляю! Если у вас возникли проблемы, пожалуйста, опубликуйте вопрос в Stack Overflow с тегом okta или напишите мне на Twitter @mraible .
Вы можете найти законченную версию приложения, созданного в этой записи блога на GitHub . Чтобы узнать больше о безопасности в Angular, см . Документацию по безопасности Angular . Если вы хотите узнать больше об OpenID Connect, я рекомендую посмотреть успокаивающее видео ниже.