项目

Angular UI 中的授权

Angular 应用模板已预先配置了 OAuth。因此,当你使用 CLI(或 Suite)启动项目时,授权功能已经就绪。ABP Angular UI 包使用 angular-oauth2-oidc 库来管理 Angular 客户端的 OAuth。 你可以在 environment.ts 文件中找到 OAuth 配置

授权码流程

import { Config } from '@abp/ng.core';

const baseUrl = 'http://localhost:4200';

export const environment = {
  // 为简洁起见,其他选项已省略

  oAuthConfig: {
    issuer: 'https://localhost:44305',
    redirectUri: baseUrl,
    clientId: 'MyProjectName_App',
    responseType: 'code',
    scope: 'offline_access MyProjectName',
  },

  // 为简洁起见,其他选项已省略
} as Config.Environment;

此配置实现了带有 PKCE 的 OAuth 授权码流程。 根据此流程,用户将被重定向到使用 MVC 构建的外部登录页面。因此,如果你需要自定义登录页面,请参考此社区文章

资源所有者密码流程

如果你在项目中使用了 Angular UI 账户模块,可以通过修改 environment.ts 文件中的 OAuth 配置来切换到资源所有者密码流程,如下所示:

import { Config } from '@abp/ng.core';

export const environment = {
  // 为简洁起见,其他选项已省略

  oAuthConfig: {
    issuer: 'https://localhost:44305',
    clientId: 'MyProjectName_App',
    dummyClientSecret: '1q2w3e*',
    scope: 'offline_access MyProjectName',
  },

  // 为简洁起见,其他选项已省略
} as Config.Environment;

根据此流程,用户将被重定向到账户模块中的登录页面。

错误过滤

AuthFlowStrategy 类中,有一个名为 listenToOauthErrors 的方法,用于监听 OAuthErrorEvent 错误。该方法会清除 localStorage 中的 OAuth 密钥。但在某些情况下,我们可能希望跳过此过程。为此,我们可以使用 AuthErrorFilterServiceAuthErrorFilterService 是一个抽象服务,需要通过自定义实现来替换

默认情况下,此服务在 @abp/ng.oauth 包中被替换

使用方法

1. 创建认证过滤器提供者

//auth-filter.provider.ts
import { inject, provideAppInitializer } from '@angular/core';
import { AuthErrorFilter, AuthErrorEvent, AuthErrorFilterService } from '@abp/ng.core';
import { eCustomersAuthFilterNames } from '../enums';

export const CUSTOMERS_AUTH_FILTER_PROVIDER = [
  provideAppInitializer(() => {
      configureAuthFilter()
  }),
];

type Reason = object & { error: { grant_type: string | undefined } };

function configureAuthFilter() {
  const errorFilterService = inject(
    AuthErrorFilterService<AuthErrorFilter<AuthErrorEvent>, AuthErrorEvent>,
  );
  const filter: AuthErrorFilter = {
    id: eCustomersAuthFilterNames.LinkedUser,
    executable: true,
    execute: (event: AuthErrorEvent) => {
      const { reason } = event;
      const {
        error: { grant_type },
      } = <Reason>(reason || {});

      return !!grant_type && grant_type === eCustomersAuthFilterNames.LinkedUser;
    },
  };

  return () => errorFilterService.add(filter);
}
  • AuthErrorFilter: 是过滤器对象的模型,它具有 3 个属性
    • id: 列表中过滤器对象的唯一键
    • executable: 过滤器对象的状态。如果为 false,则不会执行,但仍保留在列表中
    • execute: 存储跳过逻辑的函数

2. 添加到客户配置提供者

// customer-config.provider.ts
import { EnvironmentProviders, makeEnvironmentProviders } from "@angular/core";
import { CUSTOMERS_AUTH_FILTER_PROVIDER } from "./auth-filter.provider";

export function provideCustomerConfig(): EnvironmentProviders {
  return makeEnvironmentProviders([
    CUSTOMERS_AUTH_FILTER_PROVIDER
  ])
}

现在,如果发生任何 OAuthErrorEvent,它将跳过清除 LinkedUser grant_type 的 OAuth 存储密钥

使用自定义实现替换

使用 AbstractAuthErrorFilter<T,E> 类作为处理过程的标识。

示例

my-auth-error-filter.service.ts

import { Injectable, signal } from '@angular/core';
import { MyAuthErrorEvent } from 'angular-my-auth-oidc';
import { AbstractAuthErrorFilter, AuthErrorFilter } from '@abp/ng.core';

@Injectable({ providedIn: 'root' })
export class OAuthErrorFilterService extends AbstractAuthErrorFilter<
  AuthErrorFilter<MyAuthErrorEvent>,
  MyAuthErrorEvent
> {
  protected readonly _filters = signal<Array<AuthErrorFilter<MyAuthErrorEvent>>>([]);
  readonly filters = this._filters.asReadonly();

  get(id: string): AuthErrorFilter<MyAuthErrorEvent> {
    return this._filters().find(({ id: _id }) => _id === id);
  }

  add(filter: AuthErrorFilter<MyAuthErrorEvent>): void {
    this._filters.update(items => [...items, filter]);
  }

  patch(item: Partial<AuthErrorFilter<MyAuthErrorEvent>>): void {
    const _item = this.filters().find(({ id }) => id === item.id);
    if (!_item) {
      return;
    }

    Object.assign(_item, item);
  }

  remove(id: string): void {
    const item = this.filters().find(({ id: _id }) => _id === id);
    if (!item) {
      return;
    }

    this._filters.update(items => items.filter(({ id: _id }) => _id !== id));
  }

  run(event: MyAuthErrorEvent): boolean {
    return this.filters()
      .filter(({ executable }) => !!executable)
      .map(({ execute }) => execute(event))
      .some(item => item);
  }
}

另请参阅

在本文档中