用户授权对象与Angular组件可见性

用户授权对象(UserAuth Object)的作用是什么?

当用户登录后,应用程序会获取一个包含用户ID、账户ID、电子邮件和角色等信息的用户授权对象,并将其保留在内存中。

interface UserAuth {
  id: string;          // 用户唯一标识
  accountId: string;   // 关联账户ID
  email: string;      // 用户邮箱
  roles: string[];     // 角色数组(如['owner', 'admin'])
  isAuthenticated: boolean; // 认证状态标志
}

在会话认证中,用户授权对象不会随Cookie自动发送给客户端,需要通过携带新获得的Cookie再次请求特定接口获取。在JWT认证中,客户端通常会将JWT存储在本地存储(localStorage),存储时直接顺便解码即可获得用户授权对象。

HTTP/1.1 200 OK
Set-Cookie: sessionId=abc123; Path=/; HttpOnly; Secure
// Angular服务示例
@Injectable()
export class AuthService {
	private currentUser: UserAuth | null = null;
	
	fetchUser(): Observable<UserAuth> {
	return this.http.get<UserAuth>('/api/user').pipe(
	  tap(user => this.currentUser = user) // 存储到内存
	);
	}

	// 会话认证(仅存ID)
	loginWithSession() {
	  this.http.post('/login', credentials).subscribe(() => {
		this.fetchUser(); // 需额外请求用户数据
	  });
	}

	// JWT认证(存完整Token)
	loginWithJWT() {
	  this.http.post('/login', credentials).subscribe((res: {token: string}) => {
	    localStorage.setItem('jwt', res.token); // 存储完整Token
	    this.currentUser = this.decodeJWT(res.token); // 解码到内存
	  });
	}
}

客户端应用程序会存储此对象,并利用它来判断用户的登录状态。

// 标准的登录状态检查
isLoggedIn(): boolean {
  // 内存中存在有效用户对象
  if (this.currentUser?.isAuthenticated) return true;
  
  // JWT场景:尝试从本地存储恢复
  const token = localStorage.getItem('jwt');
  if (token && !isTokenExpired(token)) {
    this.currentUser = this.decodeJWT(token);
    return true;
  }
  
  return false;
}

// 辅助方法:检查JWT过期
private isTokenExpired(token: string): boolean {
  const expiry = JSON.parse(atob(token.split('.')[1])).exp;
  return Math.floor(Date.now() / 1000) >= expiry;
}

当刷新页面时,使用会话验证的应用程序会自动向后端请求用户授权对象,如果请求成功并返回用户信息,则表示用户已登录。JWT验证方式下直接读取localStorage即可,但是可以根据体验要求增加JWT时效性的验证逻辑。登录态的过期检查不只是在刷新页面时进行,页面生命周期中的每一次用户相关的请求其实后端都进行了登录态检查(根据会话对象或过期时间)。后端返回401时,前端应用程序登出用户即可。

// JWT验证方式下Angular的初始化逻辑
ngOnInit() {
  const token = localStorage.getItem('jwt');
  if (token) {
    this.currentUser = this.decodeJWT(token);
    // 可选:向服务端验证Token有效性
    this.http.get('/validate-token').subscribe({
      error: () => this.clearAuth()
    });
  }
}

如何根据用户角色实现组件的条件可见性?

1.声明式的可见性控制

可以通过自定义指令操纵组件条件可见性。

例如,一个名为ForRoles的类似于Angular的内置ngIf的指令,它接收角色列表作为输入,检查用户的角色是否在指令的角色列表中。

@Directive({ selector: '[appForRoles]' })
export class ForRolesDirective {
  @Input() set appForRoles(roles: string | string[]) {
    const allowedRoles = Array.isArray(roles) ? roles : [roles];
    const userRoles = this.auth.getCurrentUser()?.roles || [];
    
    if (allowedRoles.some(r => userRoles.includes(r))) {
      this.vcRef.createEmbeddedView(this.templateRef);
    } else {
      this.vcRef.clear();
    }
  }

  constructor(
    private templateRef: TemplateRef<any>,
    private vcRef: ViewContainerRef,
    private auth: AuthService
  ) {}
}
<button *appForRoles="'owner'">删除账户</button>
<div *appForRoles="['admin', 'auditor']">审计面板</div>

2.非声明式的可见性控制

对于非声明式的可见性控制,可以在父组件的 ngOnInit 生命周期钩子中判断。

  • 根据角色,命令式地运行逻辑。
  • 这种方法还可以用于优化数据加载,例如,只有当用户有权限创建或编辑支出时,才加载支出类别列表。
// 费用列表组件
export class ExpenseListComponent implements OnInit {
  displayedColumns: string[] = ['date', 'amount'];

  ngOnInit() {
    const user = this.auth.getCurrentUser();
    if (user?.roles.includes('owner')) {
      this.displayedColumns.push('actions');
    }
  }
}