使用angular4和asp.net core 2 web api做個練習專案(三)
下面將開發登陸和授權的部分, 這裡要用到identity server 4.
在VS解決方案中設定多個專案同時啟動:
AspNetIdentityAuthorizationServer就是authorization server. 它的地址是 http://localhost:5000
CoreApi.Web作為api, 都已經配置好了.它的地址是 http://localhost:5001
Login 登陸
由於我們使用的是Identity Server 4的登入頁面, 所以angular專案裡面無需登入頁面, 把login相關的檔案刪除...........
登陸需要使用到oidc-client.js所以通過npm安裝:
npm install --save oidc-client
Auth Service
需要登陸服務 auth.service:
ng g s services/auth
開啟auth.services.ts:
import { Injectable, OnInit, EventEmitter } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { User, UserManager, Log } from 'oidc-client'; import 'rxjs/add/observable/fromPromise'; const config: any = { authority: 'http://localhost:5000', client_id: 'corejs', redirect_uri: 'http://localhost:4200/login-callback', response_type: 'id_token token', scope: 'openid profile coreapi', post_logout_redirect_uri: 'http://localhost:4200/index.html', }; Log.logger = console; Log.level = Log.DEBUG; @Injectable() export class AuthService implements OnInit { private manager: UserManager = new UserManager(config); public loginStatusChanged: EventEmitter<User>; constructor() { this.loginStatusChanged = new EventEmitter(); } ngOnInit() { } login() { this.manager.signinRedirect(); } loginCallBack() { return Observable.create(observer => { Observable.fromPromise(this.manager.signinRedirectCallback()) .subscribe(() => { this.tryGetUser().subscribe((user: User) => { this.loginStatusChanged.emit(user); observer.next(user); observer.complete(); }, e => { observer.error(e); }); }); }); } checkUser() { this.tryGetUser().subscribe((user: User) => { this.loginStatusChanged.emit(user); }, e => { this.loginStatusChanged.emit(null); }); } private tryGetUser() { return Observable.fromPromise(this.manager.getUser()); } logout() { this.manager.signoutRedirect(); } }
config是針對identity server 4伺服器的配置, authorization server的地址是 http://localhost:5000, 登陸成功後跳轉後來的地址是: http://localhost:4200/login-callback
其中的UserManager就是oidc-client裡面的東西, 它負責處理登入登出和獲取當前登入使用者等操作.
這裡login()方法被呼叫後會直接跳轉到 authorization server的登入頁面.
登入成功後會跳轉到一個callback頁面, 裡面需要呼叫一個callback方法, 這就是loginCallback()方法.
loginStatusChanged是一個EventEmitter, 任何訂閱了這個事件的component, 都會在登入使用者變化時(登入/退出)觸發component裡面自定義的事件.
logout()是退出, 呼叫方法後也會跳轉到authorization server的頁面.
最後別忘了在app.module裡面註冊:
providers: [
ClientService,
AuthService
],
登陸成功後跳轉回掉頁面
建立一個跳轉回掉的component和路由:
ng g c components/loginCallback
修改app.module的路由:
const appRoutes: Routes = [
{ path: '', component: DashboardComponent },
{ path: 'login-callback', component: LoginCallbackComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'add-client', component: AddClientComponent },
{ path: 'client/:id', component: ClientDetailsComponent },
{ path: 'edit-client/:id', component: EditClientComponent }
];
開啟login-callback.component.ts:
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
import { User } from 'oidc-client';
@Component({
selector: 'app-login-callback',
templateUrl: './login-callback.component.html',
styleUrls: ['./login-callback.component.css']
})
export class LoginCallbackComponent implements OnInit {
constructor(
private authService: AuthService,
private router: Router
) { }
ngOnInit() {
this.authService.loginCallBack().subscribe(
(user: User) => {
console.log('login callback user:', user);
if (user) {
this.router.navigate(['/']);
}
}
);
}
}
這裡主要是呼叫oidc的回掉函式. 然後跳轉到主頁.
html:
<p>
登入成功!
</p>
這個html, 基本是看不見的.
修改Navbar
navbar.component.html:
<nav class="navbar navbar-expand-md navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="#">Client Panel</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
<ul class="navbar-nav mr-auto">
<li *ngIf="isLoggedIn" class="nav-item">
<a class="nav-link" href="#" routerLink="/">Dashboard </a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li *ngIf="!isLoggedIn" class="nav-item">
<a class="nav-link" href="#" routerLink="/register">Register </a>
</li>
<li *ngIf="!isLoggedIn" class="nav-item">
<a class="nav-link" href="#" (click)="login()">Login </a>
</li>
<li *ngIf="isLoggedIn" class="nav-item">
<a class="nav-link" href="#" (click)="logout()">Logout </a>
</li>
</ul>
</div>
</div>
</nav>
<br>
主要是檢查是否有使用者登陸了, 有的話不顯示註冊和登陸連結, 並且顯示退出連結按鈕. 沒有的話, 則顯示註冊和登入.
navbar.component.ts:
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../../services/auth.service';
import 'rxjs/add/operator/map';
import { User } from 'oidc-client';
import { FlashMessagesService } from 'angular2-flash-messages';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
public isLoggedIn: boolean;
public loggedInUser: User;
constructor(
private authService: AuthService,
private router: Router,
private flashMessagesService: FlashMessagesService
) { }
ngOnInit() {
this.authService.loginStatusChanged.subscribe((user: User) => {
this.loggedInUser = user;
this.isLoggedIn = !!user;
if (user) {
this.flashMessagesService.show('登陸成功', { cssClass: 'alert alert-success', timeout: 4000 });
}
});
this.authService.checkUser();
}
login() {
this.authService.login();
}
logout() {
this.authService.logout();
}
}
在ngOnInit裡面訂閱authservice的那個登入狀態變化的事件. 以便切換導航欄的按鈕顯示情況.
angular的部分先到這, 然後要
修改一個identity server的配置:
在VS2017開啟AspNetIdentityAuthorizationServer這個專案的Config.cs檔案, 看GetClients()那部分, 裡面有一個Client是js client, 我們就用這個....
// JavaScript Client
new Client
{
ClientId = CoreApiSettings.Client.ClientId,
ClientName = CoreApiSettings.Client.ClientName,
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RedirectUris = { CoreApiSettings.Client.RedirectUris },
PostLogoutRedirectUris = { CoreApiSettings.Client.PostLogoutRedirectUris },
AllowedCorsOrigins = { CoreApiSettings.Client.AllowedCorsOrigins },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
CoreApiSettings.CoreApiResource.Name
}
}
開啟CoreApiSettings, 它在SharedSettings這個專案裡面:
namespace SharedSettings.Settings
{
public class CoreApiSettings
{
#region CoreApi
public static string AuthorizationServerBase = "http://localhost:5000";
public static string CorsPolicyName = "default";
public static string CorsOrigin = "http://localhost:4200";
public static (string Name, string DisplayName) CoreApiResource = ("coreapi", "Core APIs");
public static (string ClientId, string ClientName, string RedirectUris, string PostLogoutRedirectUris, string AllowedCorsOrigins) Client =
("corejs", "Core Javascript Client", "http://localhost:4200/login-callback", "http://localhost:4200/index.html", "http://localhost:4200");
#endregion
}
}
把相應的地址改成和angular auth.service裡面config一樣的地址才能工作.
這裡面使用了C# 7的命名Tuple, 非常好用.
差不多可以了, 執行VS. 同時執行angular專案:
1. 首次瀏覽:
2. 點選登陸:
點選登陸就跳轉到authorization server的登入頁面了, 你在這裡需要註冊一個使用者.....
然後輸入使用者名稱密碼登陸.
3.同意授權
點選yes 同意授權.
4.跳轉回angular頁面:
首先跳轉回的是angular的login-callback路由, 然後瞬間回到了主頁:
5. 重新整理, 還是可以取得到登入的使用者.
但是如果再開啟一個瀏覽器例項就無法取得到登陸使用者了, oidc應該是把登陸資訊存到了session storage裡面.
開啟瀏覽器F12--Application:
可以看到在session storage裡面確實有東西, 而 localstorage裡面卻沒有.
今天比較忙, 先寫到這... 估計還得寫一篇....