delon-auth-authentication-authorization delon-auth-authentication-authorization

使用 @delon/auth 库实现 Angular 应用的认证和授权管理,包括JWT令牌管理、角色和权限控制。

前端开发 0 次安装 0 次浏览 更新于 2/28/2026

name: delon-auth-authentication-authorization description: “使用 @delon/auth 实现认证和授权。当添加登录/登出流程、JWT令牌管理、基于角色的访问控制(RBAC)、路由守卫、HTTP拦截器和会话管理时,使用此技能。与Firebase Auth和自定义权限系统集成。确保安全的令牌存储、自动令牌刷新和跨组件和服务的一致授权检查。” license: “MIT”

@delon/auth 认证与授权技能

此技能有助于使用 @delon/auth 库实现认证和授权。

核心原则

令牌管理

  • JWT存储:使用 DA_SERVICE_TOKEN 安全存储令牌
  • 自动刷新:在令牌过期前自动更新
  • 拦截器:在HTTP请求中自动注入令牌
  • 登出:清除令牌和会话清理

授权

  • RBAC:基于角色的访问控制
  • 权限:细粒度的权限检查
  • 守卫:带有角色/权限检查的路由保护
  • ACL集成:与 @delon/acl 一起工作,用于UI级控制

配置

应用配置

// src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideDelonAuth, DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { JWTTokenModel } from '@delon/auth';

export const appConfig: ApplicationConfig = {
  providers: [
    provideDelonAuth({
      token_send_key: 'Authorization',
      token_send_template: 'Bearer ${token}',
      token_send_place: 'header',
      login_url: '/passport/login',
      ignores: [/\/login/, /assets\//],
      allow_anonymous_key: 'allow_anonymous',
      executeOtherInterceptors: true,
      refreshTime: 3000, // 刷新令牌检查间隔(ms)
      refreshOffset: 6000 // 令牌过期前刷新(ms)
    })
  ]
};

令牌模型

// src/app/core/models/auth-token.model.ts
import { JWTTokenModel } from '@delon/auth';

export interface AuthTokenModel extends JWTTokenModel {
  token: string;
  uid: string;
  email: string;
  displayName: string;
  role: string;
  permissions: string[];
  exp: number;
}

认证服务

// src/app/core/services/auth.service.ts
import { Injectable, inject } from '@angular/core';
import { Router } from '@angular/router';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { Auth, signInWithEmailAndPassword, signOut, User } from '@angular/fire/auth';
import { AuthTokenModel } from '@core/models/auth-token.model';
import { signal, computed } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private auth = inject(Auth);
  private tokenService = inject<ITokenService>(DA_SERVICE_TOKEN);
  private router = inject(Router);
  
  // 状态
  private currentUser = signal<User | null>(null);
  private authToken = signal<AuthTokenModel | null>(null);
  
  // 计算属性
  isAuthenticated = computed(() => this.tokenService.get()?.token != null);
  userRole = computed(() => this.authToken()?.role);
  userPermissions = computed(() => this.authToken()?.permissions || []);
  
  constructor() {
    // 监听 Firebase 认证状态
    this.auth.onAuthStateChanged(async (user) => {
      this.currentUser.set(user);
      if (user) {
        await this.refreshToken();
      } else {
        this.clearSession();
      }
    });
  }
  
  /**
   * 使用电子邮件和密码登录
   */
  async login(email: string, password: string): Promise<void> {
    try {
      const credential = await signInWithEmailAndPassword(this.auth, email, password);
      const token = await credential.user.getIdToken();
      const tokenData = await this.parseToken(token);
      
      // 在 @delon/auth 中存储令牌
      this.tokenService.set(tokenData);
      this.authToken.set(tokenData);
      
      // 导航到仪表板
      await this.router.navigateByUrl('/dashboard');
    } catch (error) {
      console.error('登录失败:', error);
      throw error;
    }
  }
  
  /**
   * 登出
   */
  async logout(): Promise<void> {
    try {
      await signOut(this.auth);
      this.clearSession();
      await this.router.navigateByUrl('/passport/login');
    } catch (error) {
      console.error('登出失败:', error);
      throw error;
    }
  }
  
  /**
   * 刷新令牌
   */
  async refreshToken(): Promise<void> {
    const user = this.currentUser();
    if (!user) return;
    
    try {
      const token = await user.getIdToken(true); // 强制刷新
      const tokenData = await this.parseToken(token);
      
      this.tokenService.set(tokenData);
      this.authToken.set(tokenData);
    } catch (error) {
      console.error('令牌刷新失败:', error);
      await this.logout();
    }
  }
  
  /**
   * 检查用户是否具有特定角色
   */
  hasRole(role: string): boolean {
    return this.userRole() === role;
  }
  
  /**
   * 检查用户是否具有特定权限
   */
  hasPermission(permission: string): boolean {
    return this.userPermissions().includes(permission);
  }
  
  /**
   * 检查用户是否具有指定的任何权限
   */
  hasAnyPermission(permissions: string[]): boolean {
    const userPerms = this.userPermissions();
    return permissions.some(p => userPerms.includes(p));
  }
  
  /**
   * 检查用户是否具有所有指定的权限
   */
  hasAllPermissions(permissions: string[]): boolean {
    const userPerms = this.userPermissions();
    return permissions.every(p => userPerms.includes(p));
  }
  
  /**
   * 解析JWT令牌
   */
  private async parseToken(token: string): Promise<AuthTokenModel> {
    // 解码JWT负载
    const payload = JSON.parse(atob(token.split('.')[1]));
    
    // 从Firestore加载用户权限(自定义声明)
    const permissions = await this.loadUserPermissions(payload.uid);
    
    return {
      token,
      uid: payload.uid,
      email: payload.email,
      displayName: payload.name || payload.email,
      role: payload.role || 'member',
      permissions,
      exp: payload.exp
    };
  }
  
  /**
   * 从Firestore加载用户权限
   */
  private async loadUserPermissions(uid: string): Promise<string[]> {
    // 实现:根据你的模式从Firestore查询用户的权限
    // 这是一个占位符 - 根据需要实现
    return ['read:tasks', 'write:tasks'];
  }
  
  /**
   * 清除会话
   */
  private clearSession(): void {
    this.tokenService.clear();
    this.authToken.set(null);
    this.currentUser.set(null);
  }
}

认证守卫

// src/app/core/guards/auth.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from '@core/services/auth.service';

/**
 * 保护需要认证的路由的守卫
 */
export const authGuard: CanActivateFn = () => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (authService.isAuthenticated()) {
    return true;
  }
  
  return router.createUrlTree(['/passport/login']);
};

角色守卫

// src/app/core/guards/role.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from '@core/services/auth.service';

/**
 * 按角色保护路由的守卫
 */
export const roleGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  const requiredRole = route.data['role'] as string;
  
  if (!requiredRole) {
    console.warn('角色守卫使用时未指定所需角色');
    return true;
  }
  
  if (authService.hasRole(requiredRole)) {
    return true;
  }
  
  // 重定向到未授权页面
  return router.createUrlTree(['/exception/403']);
};

/**
 * 在路由中的使用:
 * {
 *   path: 'admin',
 *   component: AdminComponent,
 *   canActivate: [authGuard, roleGuard],
 *   data: { role: 'admin' }
 * }
 */

权限守卫

// src/app/core/guards/permission.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from '@core/services/auth.service';

/**
 * 按权限保护路由的守卫
 */
export const permissionGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  const requiredPermissions = route.data['permissions'] as string[];
  const requireAll = route.data['requireAll'] as boolean ?? true;
  
  if (!requiredPermissions || requiredPermissions.length === 0) {
    console.warn('权限守卫使用时未指定所需权限');
    return true;
  }
  
  const hasAccess = requireAll
    ? authService.hasAllPermissions(requiredPermissions)
    : authService.hasAnyPermission(requiredPermissions);
  
  if (hasAccess) {
    return true;
  }
  
  return router.createUrlTree(['/exception/403']);
};

/**
 * 在路由中的使用:
 * {
 *   path: 'tasks',
 *   component: TaskListComponent,
 *   canActivate: [authGuard, permissionGuard],
 *   data: { 
 *     permissions: ['read:tasks'], 
 *     requireAll: true 
 *   }
 * }
 */

登录组件

// src/app/routes/passport/login/login.component.ts
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { AuthService } from '@core/services/auth.service';
import { SHARED_IMPORTS } from '@shared';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [SHARED_IMPORTS],
  template: `
    <div class="login-container">
      <nz-card>
        <h2>登录</h2>
        <form nz-form [formGroup]="loginForm" (ngSubmit)="handleLogin()">
          <nz-form-item>
            <nz-form-control nzErrorTip="请输入您的电子邮件!">
              <nz-input-group nzPrefixIcon="mail">
                <input 
                  nz-input 
                  formControlName="email" 
                  placeholder="电子邮件"
                  type="email"
                />
              </nz-input-group>
            </nz-form-control>
          </nz-form-item>
          
          <nz-form-item>
            <nz-form-control nzErrorTip="请输入您的密码!">
              <nz-input-group nzPrefixIcon="lock">
                <input 
                  nz-input 
                  formControlName="password" 
                  placeholder="密码"
                  type="password"
                />
              </nz-input-group>
            </nz-form-control>
          </nz-form-item>
          
          @if (error()) {
            <nz-alert 
              nzType="error" 
              [nzMessage]="error()!" 
              nzShowIcon 
            />
          }
          
          <button 
            nz-button 
            nzType="primary" 
            nzBlock 
            [nzLoading]="loading()"
            [disabled]="loginForm.invalid"
          >
            登录
          </button>
        </form>
      </nz-card>
    </div>
  `
})
export class LoginComponent {
  private fb = inject(FormBuilder);
  private authService = inject(AuthService);
  
  loading = signal(false);
  error = signal<string | null>(null);
  
  loginForm: FormGroup = this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    password: ['', [Validators.required, Validators.minLength(6)]]
  });
  
  async handleLogin(): Promise<void> {
    if (this.loginForm.invalid) return;
    
    this.loading.set(true);
    this.error.set(null);
    
    try {
      const { email, password } = this.loginForm.value;
      await this.authService.login(email, password);
    } catch (err) {
      this.error.set(err instanceof Error ? err.message : '登录失败');
    } finally {
      this.loading.set(false);
    }
  }
}

HTTP拦截器(自动令牌注入)

@delon/auth 在配置时自动提供拦截器。无需手动实现!

// 所有HTTP请求中自动注入令牌
// 在app.config.ts中通过provideDelonAuth()配置

// 示例HTTP调用 - 自动添加令牌
this.http.get('/api/tasks').subscribe(tasks => {
  // 授权:Bearer {token} 头自动添加
});

组件级权限检查

import { Component, inject, computed } from '@angular/core';
import { AuthService } from '@core/services/auth.service';

@Component({
  selector: 'app-task-list',
  template: `
    @if (canCreate()) {
      <button nz-button nzType="primary" (click)="createTask()">
        创建任务
      </button>
    }
    
    @if (canEdit()) {
      <button nz-button (click)="editTask()"编辑</button>
    }
    
    @if (canDelete()) {
      <button nz-button nzDanger (click)="deleteTask()"删除</button>
    }
  `
})
export class TaskListComponent {
  private authService = inject(AuthService);
  
  canCreate = computed(() => this.authService.hasPermission('create:tasks'));
  canEdit = computed(() => this.authService.hasPermission('edit:tasks'));
  canDelete = computed(() => this.authService.hasPermission('delete:tasks'));
}

与@delon/acl集成

// src/app/app.config.ts
import { provideDelonACL } from '@delon/acl';

export const appConfig: ApplicationConfig = {
  providers: [
    provideDelonACL()
  ]
};
// 将权限同步到ACL
import { ACLService } from '@delon/acl';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private aclService = inject(ACLService);
  
  private syncPermissionsToACL(permissions: string[], role: string): void {
    this.aclService.setRole([role]);
    this.aclService.setAbility(permissions);
  }
}

路由配置

// src/app/routes/routes.ts
import { Routes } from '@angular/router';
import { authGuard } from '@core/guards/auth.guard';
import { roleGuard } from '@core/guards/role.guard';
import { permissionGuard } from '@core/guards/permission.guard';

export const routes: Routes = [
  {
    path: '',
    canActivate: [authGuard],
    children: [
      {
        path: 'dashboard',
        loadComponent: () => import('./dashboard/dashboard.component')
      },
      {
        path: 'admin',
        canActivate: [roleGuard],
        data: { role: 'admin' },
        loadComponent: () => import('./admin/admin.component')
      },
      {
        path: 'tasks',
        canActivate: [permissionGuard],
        data: { 
          permissions: ['read:tasks'],
          requireAll: true
        },
        loadComponent: () => import('./tasks/task-list.component')
      }
    ]
  },
  {
    path: 'passport',
    children: [
      {
        path: 'login',
        loadComponent: () => import('./passport/login/login.component')
      }
    ]
  }
];

检查清单

实现认证时:

  • [ ] 在 app.config.ts 中配置 @delon/auth
  • [ ] 创建具有令牌管理的 AuthService
  • [ ] 实现登录/登出方法
  • [ ] 设置令牌刷新机制
  • [ ] 为受保护的路由创建认证守卫
  • [ ] 根据需要创建角色/权限守卫
  • [ ] 与 Firebase Auth 集成
  • [ ] 将权限与 @delon/acl 同步
  • [ ] 处理令牌过期
  • [ ] 测试认证流程
  • [ ] 测试授权检查
  • [ ] 处理错误状态(无效凭据、网络错误)

参考