小程序的开发费用,赣州优化公司,公司宣传册模板,500套wordpress模板下载在前面两篇文章中#xff0c;我介绍了基于IdentityServer4的一个Identity Service的实现#xff0c;并且实现了一个Weather API和基于Ocelot的API网关#xff0c;然后实现了通过Ocelot API网关整合Identity Service做身份认证的API请求。今天#xff0c;我们进入前端开发我介绍了基于IdentityServer4的一个Identity Service的实现并且实现了一个Weather API和基于Ocelot的API网关然后实现了通过Ocelot API网关整合Identity Service做身份认证的API请求。今天我们进入前端开发设计一个简单的Angular SPA并在Angular SPA上调用受Ocelot API网关和Identity Service保护的Weather API。回顾《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权一》《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权二》Angular SPA的实现我们搭建一个Angular SPA的应用程序第一步先实现一些基础功能比如页面布局和客户端路由第二步先将Ocelot API网关中设置的身份认证功能关闭并设计一个Component在Component中调用未受保护的Weather API此时可以毫无阻拦地在Angular SPA中调用Weather API并将结果显示在页面上第三步我们在Ocelot API网关上开启身份认证然后修改Angular SPA使其提供登录按钮以实现用户登录与身份认证进而访问受保护的Weather API。在进行接下来的实操演练之前请确保已经安装Angular 8 CLI。基础功能的实现在文件系统中使用ng new命令新建一个Angular 8的单页面应用为了有比较好的界面布局我使用了Bootstrap。方法很简单在项目目录下执行npm install –save bootstrap然后打开angular.json文件将bootstrap的js和css添加到配置中styles: [ src/styles.css, node_modules/bootstrap/dist/css/bootstrap.min.css],scripts: [ node_modules/bootstrap/dist/js/bootstrap.min.js]然后修改app.component.html使用下面代码覆盖nav classnavbar navbar-expand-md navbar-dark bg-dark a classnavbar-brand href#Identity Demo/a button classnavbar-toggler typebutton data-togglecollapse data-target#navbarSupportedContent aria-controlsnavbarSupportedContent aria-expandedfalse aria-labelToggle navigation span classnavbar-toggler-icon/span /button div classcollapse navbar-collapse idnavbarSupportedContent ul classnavbar-nav mr-auto li classnav-item active a classnav-link href#首页 span classsr-only(current)/span/a /li li classnav-item a classnav-link href#API/a /li li classnav-item a classnav-link href#关于/a /li /ul form classform-inline my-2 my-md-0 ul classnavbar-nav mr-auto a classnav-link hrefjavascript:void(0)登录/a /ul /form /div/navng serve跑起来得到一个具有标题栏的空页面接下来使用ng g c命令创建3个component分别是HomeComponentApiComponent和AboutComponent并且修改app.modules.ts文件将这三个components加入到router中import { BrowserModule } from angular/platform-browser;import { NgModule } from angular/core;import { Routes, RouterModule } from angular/router; import { AppComponent } from ./app.component;import { HomeComponent } from ./home/home.component;import { ApiComponent } from ./api/api.component;import { AboutComponent } from ./about/about.component; const appRoutes: Routes [ { path: about, component: AboutComponent }, { path: home, component: HomeComponent }, { path: api, component: ApiComponent }, { path: **, component: HomeComponent }]; NgModule({ declarations: [ AppComponent, HomeComponent, ApiComponent, AboutComponent ], imports: [ BrowserModule, RouterModule.forRoot( appRoutes, { enableTracing: false } ) ], providers: [], bootstrap: [AppComponent]})export class AppModule { }然后在app.component.html中加入1router-outlet/router-outlet再次运行站点可以看到我们已经可以通过菜单来切换component了在Angular页面中调用API显示结果Angular调用API的方法我就不详细介绍了Angular的官方文档有很详细的内容可以参考。在这个演练中我们需要注意的是首先将上篇文章中对于Weather API的认证功能关闭以便测试API的调用是否成功。关闭认证功能其实很简单只需要将Ocelot API网关中有关Ocelot的配置的相关节点注释掉就行了{ ReRoutes: [ { DownstreamPathTemplate: /weatherforecast, DownstreamScheme: http, DownstreamHostAndPorts: [ { Host: localhost, Port: 5000 } ], UpstreamPathTemplate: /api/weather, UpstreamHttpMethod: [ Get ], //AuthenticationOptions: { // AuthenticationProviderKey: AuthKey, // AllowedScopes: [] //} } ]}接下来修改Angular单页面应用在app.module.ts中加入HttpClientModuleimports: [ BrowserModule, HttpClientModule, RouterModule.forRoot( appRoutes, { enableTracing: false } ) ],然后实现一个调用Weather API的Service服务import { Injectable } from angular/core;import { HttpClient } from angular/common/http;import { WeatherData } from ../models/weather-data;import { Observable } from rxjs; Injectable({ providedIn: root})export class WeatherService { constructor(private httpClient: HttpClient) { } getWeather(): ObservableWeatherData[] { return this.httpClient.getWeatherData[](http://localhost:9000/api/weather); }}在这个Service实现中没有加入异常处理部分因为作为一个研究性质的项目没有必要进行异常处理到浏览器的调试窗口查看错误信息就行。上面的代码引用了一个类型就是WeatherData它其实非常简单对应着Weather API所返回的数据模型export class WeatherData { constructor(public temperatureF: number, public temperatureC: number, private summary: string, private date: string) { }}现在修改api.component.ts通过调用这个WeatherService来获取Weather API的数据1import { Component, OnInit } from angular/core;import { WeatherService } from ../services/weather.service;import { WeatherData } from ../models/weather-data; Component({ selector: app-api, templateUrl: ./api.component.html, styleUrls: [./api.component.css]})export class ApiComponent implements OnInit { data: WeatherData[]; constructor(private api: WeatherService) { } ngOnInit() { this.api.getWeather() .subscribe(ret this.data ret); }}并显示在前端1234567891011121314151617181920div classcontainer *ngIfdata table classtable table-striped thead tr th scopecolSummary/th th scopecolTempF/th th scopecolTempC/th th scopecolDate/th /tr /thead tbody tr *ngForlet d of data td{{d.summary}}/td td{{d.temperatureF}}/td td{{d.temperatureC}}/td td{{d.date}}/td /tr /tbody /table/div完成之后启动Weather API和Ocelot API网关然后运行Angular单页面应用我们已经可以在API这个页面显示调用结果了开启身份认证在Ocelot API网关的配置中打开被注释掉的部分重新启用身份认证功能再次刷新Angular页面发现页面已经打不开了在开发者工具的Console中输出了错误信息401 (Unauthorized)表示身份认证部分已经起作用了。下面我们来解决这个问题。既然是需要身份认证才能访问Weather API那么我们就在Angular页面上实现登录功能。首先在Angular单页面应用中安装oidc-clientoidc-client是一款为Javascript应用程序提供OpenID Connect和OAuth2协议支持的框架在Angular中使用也非常的方便。用npm install来安装这个库1npm install oidc-client然后实现一个用于身份认证的Serviceimport { Injectable } from angular/core;import { BehaviorSubject } from rxjs;import { UserManager, UserManagerSettings, User } from oidc-client; Injectable({ providedIn: root})export class AuthService { private authStatusSource new BehaviorSubjectboolean(false); private userNameStatusSource new BehaviorSubjectstring(); private userManager new UserManager(this.getUserManagerSettings()); private user: User | null; authStatus$ this.authStatusSource.asObservable(); userNameStatus$ this.userNameStatusSource.asObservable(); constructor() { this.userManager.getUser().then(user { this.user user; this.authStatusSource.next(this.isAuthenticated()); this.userNameStatusSource.next(this.user.profile.name); }); } async login() { await this.userManager.signinRedirect(); } async logout() { await this.userManager.signoutRedirect(); } async completeAuthentication() { this.user await this.userManager.signinRedirectCallback(); this.authStatusSource.next(this.isAuthenticated()); this.userNameStatusSource.next(this.user.profile.name); } isAuthenticated(): boolean { return this.user ! null !this.user.expired; } get authorizationHeaderValue(): string { return ${this.user.token_type} ${this.user.access_token}; } private getUserManagerSettings(): UserManagerSettings { return { authority: http://localhost:7889, client_id: angular, redirect_uri: http://localhost:4200/auth-callback, post_logout_redirect_uri: http://localhost:4200/, response_type: id_token token, scope: openid profile email api.weather.full_access, filterProtocolClaims: true, loadUserInfo: true, automaticSilentRenew: true, silent_redirect_uri: http://localhost:4200/silent-refresh.html }; }}AuthService为Angular应用程序提供了用户身份认证的基本功能比如登录、注销以及判断是否经过身份认证isAuthenticated等。需要注意的是getUserManagerSettings方法它为oidc-client提供了基本的参数配置其中的authority为Identity Service的URLredirect_uri为认证完成后Identity Service需要返回到哪个页面上post_logout_redirect_uri表示用户注销以后需要返回到哪个页面上client_id和scope为Identity Service中为Angular应用所配置的Client的ClientId和Scope参考Identity Service中的Config.cs文件。接下来修改app.component.html将原来的“登录”按钮改为form classform-inline my-2 my-md-0 ul classnavbar-nav mr-auto a *ngIf!isAuthenticated classnav-link hrefjavascript:void(0) (click)onLogin()登录/a li *ngIfisAuthenticated classnav-item dropdown a classnav-link dropdown-toggle href# idnavbarDropdown rolebutton data-toggledropdown aria-haspopuptrue aria-expandedfalse {{userName}} /a div classdropdown-menu aria-labelledbynavbarDropdown a classdropdown-item hrefjavascript:void(0) (click)onLogOut()注销/a /div /li /ul/form然后修改app.component.ts完成登录和注销部分的代码import { Component, OnInit, OnDestroy } from angular/core;import { AuthService } from ./services/auth.service;import { Subscription } from rxjs; Component({ selector: app-root, templateUrl: ./app.component.html, styleUrls: [./app.component.css]})export class AppComponent implements OnInit, OnDestroy { title identity-demo-spa; isAuthenticated: boolean; authStatusSubscription: Subscription; userNameSubscription: Subscription; userName: string; constructor(private authService: AuthService) { } ngOnDestroy(): void { this.authStatusSubscription.unsubscribe(); this.userNameSubscription.unsubscribe(); } ngOnInit(): void { this.authStatusSubscription this.authService.authStatus$.subscribe(status this.isAuthenticated status); this.userNameSubscription this.authService.userNameStatus$.subscribe(status this.userName status); } async onLogin() { await this.authService.login(); } async onLogOut() { await this.authService.logout(); }}我们还需要增加一个新的componentAuthCallbackComponent用来接收登录成功之后的回调它会通知AuthService以更新登录状态和用户信息import { Component, OnInit } from angular/core;import { AuthService } from ../services/auth.service;import { Router, ActivatedRoute } from angular/router; Component({ selector: app-auth-callback, templateUrl: ./auth-callback.component.html, styleUrls: [./auth-callback.component.css]})export class AuthCallbackComponent implements OnInit { constructor(private authService: AuthService, private router: Router, private route: ActivatedRoute) { } async ngOnInit() { await this.authService.completeAuthentication(); this.router.navigate([/home]); } }最后将AuthCallbackComponent添加到Route中const appRoutes: Routes [ { path: about, component: AboutComponent }, { path: home, component: HomeComponent }, { path: api, component: ApiComponent }, { path: auth-callback, component: AuthCallbackComponent }, { path: **, component: HomeComponent }];重新运行Angular应用你会看到以下效果现在我们就可以在Angular的页面中完成用户登录和注销了。如你所见登录界面来自Identity Service本身也是由IdentityServer4提供的界面开发者可以自己修改Identity Service来定制界面登录成功后原本的“登录”按钮变成了显示用户名称的下拉菜单选择菜单就可以点击“注销”按钮退出登录此时访问API页面仍然无法正确调用Weather API因为我们还没有将Access Token传入API调用登录状态下的API调用接下来我们将Access Token传入使得Angular应用可以使用登录用户获取的Access Token正确调用Weather API。修改AuthService如下export class WeatherService { constructor(private httpClient: HttpClient, private authService: AuthService) { } getWeather(): ObservableWeatherData[] { const authHeaderValue this.authService.authorizationHeaderValue; const httpOptions { headers: new HttpHeaders({ Content-Type: application/json, Authorization: authHeaderValue }) }; return this.httpClient.getWeatherData[](http://localhost:9000/api/weather, httpOptions); }}再次运行Angular应用可以看到已经可以在登录的状态下成功调用Weather API。你也可以试试在退出登录的状态下是否还能正确调用API。小结本文详细介绍了Angular单页面应用作为Ocelot API网关的客户端通过Identity Service进行身份认证和API调用的整个过程。当然很多细节部分没有做到那么完美本身也是为了能够演示开发过程中遇到的问题。从下一讲开始我会开始介绍基于Ocelot API网关的授权问题。源代码访问以下Github地址以获取源代码https://github.com/daxnet/identity-demo