ng new angular-oidc
css
進入目錄 cd angular-oidc
html
npm i oidc-client --save
打開 environment.ts
將下面的代碼覆蓋原來的內容git
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 }), }, };
須要修改的幾個參數:github
authority
: 認證服務器,須要修改成本身的認證服務器client_id
: 客戶端 id ,按照約定修改便可redirect_uri
: 認證服務器回調的客戶端頁面post_logout_redirect_uri
: 登出回調連接這裏咱們把模塊劃分爲2塊: 1) 遊客模塊
2) 用戶模塊
npm
默認的殼組件所在的 module 做爲遊客模塊, 另外還須要構建一個用戶模塊sass
爲了方便理解, 遊客模塊建立一個歡迎頁, 點擊繼續按鈕訪問用戶模塊.服務器
1. 建立一個歡迎頁app
沒什麼特別的做用, 就是爲了方便理解單獨設立的一個交互頁面.less
ng g c public/index
修改 index.component.html
ide
<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. 建立一個回調頁
回調頁是用戶 oidc 認證結束後的回調, 起到一個過分的做用(目前先空着)
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
, 這時候已經能看到一點點信息了, 不過尚未 home
路由, 下面來配置一下
1. 添加一個 auth 模塊
ng g m auth/auth --flat
--flat
:不在一個單獨的文件夾建立
2. 將 auth 模塊添加到殼組件
打開 app-module.ts
, 主要修改一下內容
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
內容以下:
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
中止以前啓動項目的終端, ng s
從新啓動項目
此時的項目已經能夠從遊客路由跳轉至用戶路由,但咱們是不容許遊客默認訪問用戶路由的, 這時候就應該 守衛(Guard)
登場了。
1. 添加 auth.service (認證相關的函數)
ng g s auth/auth --flat
替換 auth.service.ts
內容:
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 { // 大多數 oidc-client 操做都在其中 private manager: UserManager = new UserManager(environment.authConfig); // private manager: UserManager = undefined; // 登陸狀態改變事件 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 過時,調用 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
內容:
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
頁
回到成功後,導航到 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;">登陸成功,跳轉中...</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; }
再此重啓一下程序(每每一些奇奇怪怪的問題從新啓動後會被解決)。
這時候就已經實現了一個認證的過程,不過 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. 添加 c一、c2 子組件
ng g c auth/test/c1 ng g c auth/test/c2
保持默認內容便可。
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 {}
重啓項目,這時候獲得一個錯誤信息:
Error: Template parse errors: 'router-outlet' is not a known element:
這表示 auth 模塊沒有引入 RouterModule,實際上是咱們的 auth.module.ts 沒有引入 auth-routing.module.ts 致使的(routing 中有引入 RouterModule)
修改 auth.module.ts
:
... import { AuthRoutingModule } from './auth-routing.module'; @NgModule({ ... imports: [..., AuthRoutingModule], })
重啓項目,能夠看到如今基本功能都已經實現了,不過還差一個退出功能。
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
自動跳轉登陸,沒問題。
訪問 /home/c1
竟然跳過了認證,直接進來了!
形成這個問題的緣由是可是咱們的守衛添加的方式是 canActivate
,canActivate
只會保護本路由,而不會保護其子路由。所以,咱們還須要保護子路由!
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 }, ], }, ];
重啓項目,再此訪問 '/home/c1',成功跳轉,訪問 '/home',一樣成功跳轉。