配置相对较为繁琐,最后会放上 Github 源码地址新建一个 ng 项目ng new angular-oidc进入目录 cd angular-oidc安装 oidc-clientnpm 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.tsimport { 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-callback3. 配置路由打开 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/auth4. 添加 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.guardng 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.tsimport { 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.tsimport { 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/c1ng g c auth/test/c2保持默认内容即可。3. 修改 auth-routing.module.tsimport { 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.tsimport { 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.tsimport { 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”; // <- hereconst routes: Routes = [ { path: “home”, component: AuthComponent, canActivateChild: [AuthGuard], // <- here children: [ { path: “c1”, component: C1Component }, { path: “c2”, component: C2Component }, ], },];重启项目,再此访问 ‘/home/c1’,成功跳转,访问 ‘/home’,同样成功跳转。Githubangular-oidc