Angular 8 配置 oidc-client

配置相對較為繁瑣绿鸣,最后會放上 Github 源碼地址

新建一個 ng 項(xiàng)目

ng new angular-oidc

進(jìn)入目錄 cd angular-oidc

安裝 oidc-client

npm i oidc-client --save

配置 oidc-client 參數(shù)

打開 environment.ts 將下面的代碼覆蓋原來的內(nèi)容

import { WebStorageStateStore } from "oidc-client";

export const environment = {
  production: false,
  authConfig: {
    authority: "http://localhost:57001",
    client_id: "query",
    redirect_uri: "http://localhost:4200/login-callback",
    response_type: "id_token token",
    scope: "openid profile",
    post_logout_redirect_uri: "http://localhost:4200",
    accessTokenExpiringNotificationTime: 4,
    filterProtocolClaims: true,
    silentRequestTimeout: 10000,
    loadUserInfo: true,
    userStore: new WebStorageStateStore({ store: window.localStorage }),
  },
};

需要修改的幾個參數(shù):

  • authority: 認(rèn)證服務(wù)器,需要修改為自己的認(rèn)證服務(wù)器
  • client_id: 客戶端 id ,按照約定修改即可
  • redirect_uri: 認(rèn)證服務(wù)器回調(diào)的客戶端頁面
  • post_logout_redirect_uri: 登出回調(diào)鏈接

模塊劃分

這里我們把模塊劃分為2塊: 1) 游客模塊 2) 用戶模塊

默認(rèn)的殼組件所在的 module 作為游客模塊, 另外還需要構(gòu)建一個用戶模塊

游客模塊

為了方便理解, 游客模塊創(chuàng)建一個歡迎頁, 點(diǎn)擊繼續(xù)按鈕訪問用戶模塊.

1. 創(chuàng)建一個歡迎頁

沒什么特別的作用, 就是為了方便理解單獨(dú)設(shè)立的一個交互頁面.

ng g c public/index

修改 index.component.html

<h3>WELLCOME TO ANGULAR OIDC</h3>
<input type="button" value="visit" (click)="visitAuth()">

修改 index.component.ts

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";

@Component({
  selector: "app-index",
  templateUrl: "./index.component.html",
  styleUrls: ["./index.component.less"],
})
export class IndexComponent implements OnInit {
  constructor(private _router: Router) {}

  ngOnInit() {}

  public visitAuth(): void {
    this._router.navigate(["auth"]);
  }
}

2. 創(chuàng)建一個回調(diào)頁

回調(diào)頁是用戶 oidc 認(rèn)證結(jié)束后的回調(diào), 起到一個過度的作用(目前先空著)

ng g c public/login-callback

3. 配置路由

打開 app-routing.module.ts, 對照修改

import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { IndexComponent } from "./public/index/index.component";
import { LoginCallbackComponent } from "./public/login-callback/login-callback.component";

const routes: Routes = [
  {
    path: "",
    pathMatch: "full",
    component: IndexComponent,
  },
  {
    path: "login-callback",
    component: LoginCallbackComponent,
  },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

啟動程序 ng s -o, 這時候已經(jīng)能看到一點(diǎn)點(diǎn)信息了, 不過還沒有 home 路由, 下面來配置一下

用戶模塊

1. 添加一個 auth 模塊

ng g m auth/auth --flat

--flat:在一個單獨(dú)的文件夾創(chuàng)建

2. 將 auth 模塊添加到殼組件

打開 app-module.ts, 主要修改一下內(nèi)容

import { AuthModule } from "./auth/auth.module";
...

imports: [..., AuthModule],

3. 添加 auth "殼組件"

ng g c auth/auth

4. 添加 auth 模塊的路由

ng g m auth/auth-routing --flat

修改 auth-routing.module.ts 內(nèi)容如下:

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthComponent } from "./auth/auth.component";

const routes: Routes = [
  {
    path: "home",
    component: AuthComponent,
  },
];

@NgModule({
  exports: [RouterModule],
})
export class AuthRoutingModule {}

5. 修改 app-routing.module.ts 添加 home 路由

const routes: Routes = [
  {
    path: "",
    pathMatch: "full",
    component: IndexComponent,
  },
  {
    path: "login-callback",
    component: LoginCallbackComponent,
  },
  {
    path: "home",
    component: AuthComponent,
  },
];

ctrl + c -> y 停止之前啟動項(xiàng)目的終端, ng s 重新啟動項(xiàng)目

此時的項(xiàng)目已經(jīng)可以從游客路由跳轉(zhuǎn)至用戶路由派任,但我們是不允許游客默認(rèn)訪問用戶路由的, 這時候就應(yīng)該 守衛(wèi)(Guard) 登場了赶诊。

配置守衛(wèi)(Guard)

1. 添加 auth.service (認(rèn)證相關(guān)的函數(shù))

ng g s auth/auth --flat

替換 auth.service.ts 內(nèi)容:

import { Injectable, EventEmitter } from '@angular/core';
import { environment } from 'src/environments/environment';
import { UserManager, User } from 'oidc-client';
import { Observable, from } from 'rxjs';


@Injectable({
  providedIn: 'root'
})
export class AuthService {

  // 大多數(shù) oidc-client 操作都在其中
  private manager: UserManager = new UserManager(environment.authConfig);
  // private manager: UserManager = undefined;

  // 登錄狀態(tài)改變事件
  public loginStatusChanged: EventEmitter<User> = new EventEmitter();
  // localStorage 中存放用戶信息的 Key

  private userKey = `oidc.user:${environment.authConfig.authority}:${environment.authConfig.client_id}`;
  // private userKey = `oidc.user:${this._conf.env.authConfig.authority}:${this._conf.env.authConfig.client_id}`;

  constructor() {
    // 如果訪問用的 token 過期,調(diào)用 login()
    this.manager.events.addAccessTokenExpired(() => {
      this.login();
    });
  }

  login() {
    this.manager.signinRedirect();
  }

  logout() {
    this.manager.signoutRedirect();
  }

  loginCallBack() {
    return Observable.create(observer => {
      from(this.manager.signinRedirectCallback())
        .subscribe((user: User) => {
          this.loginStatusChanged.emit(user);
          observer.next(user);
          observer.complete();
        });
    });
  }

  tryGetUser() {

    return from(this.manager.getUser());
  }

  get type(): string {
    return 'Bearer';
  }

  get user(): User | null {
    const temp = localStorage.getItem(this.userKey);
    if (temp) {
      const user: User = JSON.parse(temp);
      return user;
    }
    return null;
  }

  get token(): string | null {
    const temp = localStorage.getItem(this.userKey);
    if (temp) {
      const user: User = JSON.parse(temp);
      return user.access_token;
    }
    return null;
  }

  get authorizationHeader(): string | null {
    if (this.token) {
      return `${this.type} ${this.token}`;
    }
    return null;
  }
}

2. 添加 auth.guard

ng g g auth/auth --flat

選擇 CanActivate

替換 auth.guard.ts 內(nèi)容:

import { Injectable } from "@angular/core";
import {
  CanActivate,
  CanActivateChild,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
} from "@angular/router";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { AuthService } from "./auth.service";
import { User } from "oidc-client";

@Injectable({
  providedIn: "root",
})
export class AuthGuard implements CanActivate {
  constructor(private _auth: AuthService) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return this.mapper(this._auth.tryGetUser());
  }

  private mapper = map((user: User) => {
    if (user) return true;
    this._auth.login();
    return false;
  });
}

3. 修改 app-routing.module.ts

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthComponent } from "./auth/auth.component";
import { C1Component } from "./test/c1/c1.component";
import { C2Component } from "./test/c2/c2.component";

const routes: Routes = [
  {
    path: "home",
    component: AuthComponent,
    children: [
      { path: "c1", component: C1Component },
      { path: "c2", component: C2Component },
    ],
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

4. 修改 login-callback.component.ts

回到成功后逸贾,導(dǎo)航到 home 頁陨仅,你也可以寫更多的其他邏輯。

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { User } from "oidc-client";
import { AuthService } from "src/app/auth/auth.service";

@Component({
  selector: "app-login-callback",
  templateUrl: "./login-callback.component.html",
  styleUrls: ["./login-callback.component.less"],
})
export class LoginCallbackComponent implements OnInit {
  constructor(private _router: Router, private _auth: AuthService) {}

  ngOnInit() {
    this._auth.loginCallBack().subscribe((user: User) => {
      this._router.navigate(["home"]);
    });
  }
}

順便美化一下下樣式

login-callback.component.html:

<div class="callback-bar">
  <span style="margin-left: 10px;">登錄成功铝侵,跳轉(zhuǎn)中...</span>
</div>

login-callback.component.less(我這里使用的是 less灼伤,你的可能是 css/scss/sass):

.callback-bar {
    margin: 0px 0px 0px 0px;
    padding: 8px 0px 0px 0px;
    font-size: 24px;
    font-weight: 600px;
    color: white;
    background-color: #3881bf;
    box-shadow: 0px 3px 5px #666;
    height: 50px;
}

再此重啟一下程序(往往一些奇奇怪怪的問題重新啟動后會被解決)。

這時候就已經(jīng)實(shí)現(xiàn)了一個認(rèn)證的過程咪鲜,不過 auth 模塊(用戶模塊)只有一個組件狐赡,總感覺不夠直觀,因此疟丙,我們需要在 auth 模塊添加更多的組件颖侄,形成子路由鸟雏,在觀察功能。

添加 auth 子組件览祖、子路由

修改 auth.component 組件

1. auth.component.html

<div>
  <input type="button" value="c1" (click)="goC1()">
  <input type="button" value="c2" (click)="goC2()">
</div>

<div>
  <router-outlet></router-outlet>
</div>

2. auth.component.ts

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";

@Component({
  selector: "app-auth",
  templateUrl: "./auth.component.html",
  styleUrls: ["./auth.component.less"],
})
export class AuthComponent implements OnInit {
  constructor(private _router: Router) {}

  ngOnInit() {}

  public goC1(): void {
    this._router.navigate(["home/c1"]);
  }

  public goC2(): void {
    this._router.navigate(["home/c2"]);
  }
}

新建子路由

2. 添加 c1崔慧、c2 子組件

ng g c auth/test/c1
ng g c auth/test/c2

保持默認(rèn)內(nèi)容即可。

3. 修改 auth-routing.module.ts

import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { AuthComponent } from "./auth/auth.component";
import { C1Component } from "./test/c1/c1.component";
import { C2Component } from "./test/c2/c2.component";

const routes: Routes = [
  {
    path: "home",
    component: AuthComponent,
    children: [
      { path: "c1", component: C1Component },
      { path: "c2", component: C2Component },
    ],
  },
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class AuthRoutingModule {}

重啟項(xiàng)目穴墅,這時候得到一個錯誤信息:

Error: Template parse errors:
'router-outlet' is not a known element:

這表示 auth 模塊沒有引入 RouterModule,其實(shí)是我們的 auth.module.ts 沒有引入 auth-routing.module.ts 導(dǎo)致的(routing 中有引入 RouterModule)

修改 auth.module.ts:

...
import { AuthRoutingModule } from './auth-routing.module';

@NgModule({
  ...
  imports: [..., AuthRoutingModule],
})

重啟項(xiàng)目温自,可以看到現(xiàn)在基本功能都已經(jīng)實(shí)現(xiàn)了玄货,不過還差一個退出功能。

退出登錄

1. 修改 auth.component.html

<div>
  <input type="button" value="c1" (click)="goC1()">
  <input type="button" value="c2" (click)="goC2()">
  <input type="button" value="exit" (click)="exit()">
</div>

<div>
  <router-outlet></router-outlet>
</div>

2. 修改 auth.component.ts

import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { AuthService } from "../auth.service";

@Component({
  selector: "app-auth",
  templateUrl: "./auth.component.html",
  styleUrls: ["./auth.component.less"],
})
export class AuthComponent implements OnInit {
  constructor(private _router: Router, private _auth: AuthService) {}

  ngOnInit() {}

  public goC1(): void {
    this._router.navigate(["home/c1"]);
  }

  public goC2(): void {
    this._router.navigate(["home/c2"]);
  }

  public exit(): void {
    this._auth.logout();
  }
}

重啟測試悼泌,退出成功松捉!

訪問 /home 自動跳轉(zhuǎn)登錄,沒問題馆里。

訪問 /home/c1 居然跳過了認(rèn)證隘世,直接進(jìn)來了!

造成這個問題的原因是但是我們的守衛(wèi)添加的方式是 canActivate鸠踪,canActivate只會保護(hù)本路由丙者,而不會保護(hù)其子路由。因此营密,我們還需要保護(hù)子路由械媒!

保護(hù)子路由

1. 修改 auth.guard.ts

import { Injectable } from "@angular/core";
import {
  CanActivate,
  CanActivateChild,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  UrlTree,
} from "@angular/router";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
import { AuthService } from "./auth.service";
import { User } from "oidc-client";

@Injectable({
  providedIn: "root",
})
export class AuthGuard implements CanActivate, CanActivateChild {
  constructor(private _auth: AuthService) {}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> {
    return this.mapper(this._auth.tryGetUser());
  }

  canActivateChild(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    return this.mapper(this._auth.tryGetUser());
  }

  private mapper = map((user: User) => {
    if (user) return true;
    this._auth.login();
    return false;
  });
}

2. 修改 auth-routing.module.ts
主要修改代碼如下:

import { AuthGuard } from "./auth.guard"; // <- here

const routes: Routes = [
  {
    path: "home",
    component: AuthComponent,
    canActivateChild: [AuthGuard], // <- here
    children: [
      { path: "c1", component: C1Component },
      { path: "c2", component: C2Component },
    ],
  },
];

重啟項(xiàng)目,再此訪問 '/home/c1'评汰,成功跳轉(zhuǎn)纷捞,訪問 '/home',同樣成功跳轉(zhuǎn)被去。

Github

angular-oidc

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末主儡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惨缆,更是在濱河造成了極大的恐慌糜值,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踪央,死亡現(xiàn)場離奇詭異臀玄,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)畅蹂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進(jìn)店門健无,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人液斜,你說我怎么就攤上這事累贤〉拢” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵臼膏,是天一觀的道長硼被。 經(jīng)常有香客問我,道長渗磅,這世上最難降的妖魔是什么嚷硫? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮始鱼,結(jié)果婚禮上仔掸,老公的妹妹穿的比我還像新娘。我一直安慰自己医清,他們只是感情好起暮,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著会烙,像睡著了一般负懦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上柏腻,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天纸厉,我揣著相機(jī)與錄音,去河邊找鬼葫盼。 笑死残腌,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贫导。 我是一名探鬼主播抛猫,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孩灯!你這毒婦竟也來了闺金?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤峰档,失蹤者是張志新(化名)和其女友劉穎败匹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讥巡,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掀亩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了欢顷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片槽棍。...
    茶點(diǎn)故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炼七,到底是詐尸還是另有隱情缆巧,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布豌拙,位于F島的核電站陕悬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏按傅。R本人自食惡果不足惜捉超,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唯绍。 院中可真熱鬧狂秦,春花似錦、人聲如沸推捐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牛柒。三九已至,卻和暖如春痊乾,著一層夾襖步出監(jiān)牢的瞬間皮壁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工哪审, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛾魄,地道東北人。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓湿滓,卻偏偏與公主長得像滴须,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子叽奥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 一扔水、SPA的概念 首先明確一個概念,SPA朝氓,全稱為Single Page Application單頁應(yīng)用魔市,一個單頁...
    耦耦閱讀 5,960評論 0 3
  • Angular Route導(dǎo)航 路由基礎(chǔ)知識 路由相關(guān)對象介紹 新建路由項(xiàng)目 使用angular-cli新建項(xiàng)目。...
    JustTheSame閱讀 847評論 0 0
  • 摘要:在本教程中赵哲,Ahmed Bouchefra 介紹了angular路由器(router),以及如何使用它創(chuàng)建客...
    哈維爾23456閱讀 3,288評論 0 3
  • 2018-3-15 星期四 一待德、安裝 注意:請現(xiàn)在終端/控制臺窗口中運(yùn)行 node -v 和 npm -v,來驗(yàn)證...
    ftmo閱讀 1,128評論 1 2
  • Angular介紹 Angular安裝枫夺、創(chuàng)建項(xiàng)目将宪、目錄結(jié)構(gòu)、組件、服務(wù) 創(chuàng)建組件涧偷、綁定數(shù)據(jù)簸喂、綁定屬性、數(shù)據(jù)循環(huán)燎潮、條...
    地瓜粉絲閱讀 527評論 0 2