Angular 8 配置 oidc-client

配置相对较为繁琐,最后会放上 Github 源码地址

新建一个 ng 项目

ng new angular-oidc

进入目录 cd angular-oidc

安装 oidc-client

npm i oidc-client --save

配置 oidc-client 参数

打开 environment.ts 将下面的代码覆盖原来的内容

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 }),
  },
};

需要修改的几个参数:

  • authority: 认证服务器,需要修改为自己的认证服务器
  • client_id: 客户端 id ,按照约定修改即可
  • redirect_uri: 认证服务器回调的客户端页面
  • post_logout_redirect_uri: 登出回调链接

模块划分

这里我们把模块划分为2块: 1) 游客模块 2) 用户模块

默认的壳组件所在的 module 作为游客模块, 另外还需要构建一个用户模块

游客模块

为了方便理解, 游客模块创建一个欢迎页, 点击继续按钮访问用户模块.

1. 创建一个欢迎页

没什么特别的作用, 就是为了方便理解单独设立的一个交互页面.

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. 创建一个回调页

回调页是用户 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) 登场了。

配置守卫(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 子组件、子路由

修改 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

保持默认内容即可。

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 居然跳过了认证,直接进来了!

造成这个问题的原因是但是我们的守卫添加的方式是 canActivatecanActivate只会保护本路由,而不会保护其子路由。因此,我们还需要保护子路由!

保护子路由

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',同样成功跳转。

Github

angular-oidc

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容

  • 一、SPA的概念 首先明确一个概念,SPA,全称为Single Page Application单页应用,一个单页...
    耦耦阅读 5,931评论 0 3
  • Angular Route导航 路由基础知识 路由相关对象介绍 新建路由项目 使用angular-cli新建项目。...
    JustTheSame阅读 836评论 0 0
  • 摘要:在本教程中,Ahmed Bouchefra 介绍了angular路由器(router),以及如何使用它创建客...
    哈维尔23456阅读 3,242评论 0 3
  • 2018-3-15 星期四 一、安装 注意:请现在终端/控制台窗口中运行 node -v 和 npm -v,来验证...
    ftmo阅读 1,121评论 1 2
  • Angular介绍 Angular安装、创建项目、目录结构、组件、服务 创建组件、绑定数据、绑定属性、数据循环、条...
    地瓜粉丝阅读 504评论 0 2