在前端单页应用(SPA)开发中,路由权限控制是保障应用安全的核心环节 —— 比如未登录用户不能访问核心业务页面、普通用户不能访问管理员页面。Angular 提供了强大的路由守卫(Route Guard)机制,其中CanActivate守卫是实现路由访问控制最常用的方式,本文将从实战角度讲解如何通过CanActivate实现登录拦截和精细化的权限控制。
一、核心概念:什么是 CanActivate?
CanActivate是 Angular 路由守卫接口,它会在路由激活前触发,返回一个布尔值(或 Observable/Promise 包装的布尔值),决定当前路由是否能被正常访问:
- 返回
true:允许进入该路由; - 返回
false:拒绝进入,路由跳转终止; - 支持异步判断(比如请求后端接口校验权限),适配复杂业务场景。
简单来说,CanActivate就像路由的 “门卫”,只有通过它的校验,才能进入对应的页面。
二、实战实现:登录拦截(基础版)
1. 先准备用户状态服务
首先创建一个用户服务(auth.service.ts),用于管理用户登录状态、token 存储等核心逻辑:
// src/app/services/auth.service.ts import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' // 全局单例服务 }) export class AuthService { // 模拟token存储(实际项目可存localStorage/sessionStorage) private readonly TOKEN_KEY = 'user_token'; // 判断用户是否已登录 isLoggedIn(): boolean { const token = localStorage.getItem(this.TOKEN_KEY); // 简单校验:token存在且非空即视为已登录 return !!token; } // 模拟登录:存储token login(token: string): void { localStorage.setItem(this.TOKEN_KEY, token); } // 模拟登出:清除token logout(): void { localStorage.removeItem(this.TOKEN_KEY); } }2. 实现 CanActivate 守卫
创建登录守卫(auth.guard.ts),实现CanActivate接口:
// src/app/guards/auth.guard.ts import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { AuthService } from '../services/auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { // 注入用户服务和路由服务 constructor(private authService: AuthService, private router: Router) {} // 实现CanActivate核心方法 canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): boolean { // 1. 判断用户是否已登录 if (this.authService.isLoggedIn()) { return true; // 已登录,允许访问目标路由 } // 2. 未登录:跳转到登录页,并携带“来源路由”参数(方便登录后返回) this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); return false; // 拒绝访问目标路由 } }3. 配置路由,绑定守卫
在路由模块(app-routing.module.ts)中,为需要保护的路由绑定AuthGuard:
// src/app/app-routing.module.ts import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './components/login/login.component'; import { DashboardComponent } from './components/dashboard/dashboard.component'; import { ProfileComponent } from './components/profile/profile.component'; import { AuthGuard } from './guards/auth.guard'; // 定义路由规则 const routes: Routes = [ { path: 'login', component: LoginComponent }, // 登录页(无需守卫) // 核心业务页:绑定AuthGuard,未登录无法访问 { path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] }, { path: 'profile', component: ProfileComponent, canActivate: [AuthGuard] }, { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, // 默认路由 { path: '**', redirectTo: '/dashboard' } // 404兜底 ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }4. 登录组件适配(可选)
在登录组件中,完成登录后根据returnUrl跳回来源页面:
// src/app/components/login/login.component.ts import { Component } from '@angular/core'; import { Router, ActivatedRoute } from '@angular/router'; import { AuthService } from '../../services/auth.service'; @Component({ selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { username: string = ''; password: string = ''; returnUrl: string = ''; constructor( private authService: AuthService, private router: Router, private route: ActivatedRoute ) { // 获取登录页URL中的returnUrl参数 this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/dashboard'; } // 模拟登录逻辑 onLogin(): void { // 实际项目需请求后端接口校验账号密码,这里简化为直接生成token if (this.username && this.password) { this.authService.login('mock_token_123456'); // 登录成功后跳回来源页面 this.router.navigate([this.returnUrl]); } } }三、进阶:精细化权限控制(按角色 / 权限拦截)
实际项目中,仅判断登录状态不够 —— 不同角色(如管理员、普通用户)需访问不同路由。我们可以扩展CanActivate实现基于角色的权限控制。
1. 扩展用户服务,增加角色判断
// src/app/services/auth.service.ts 新增代码 // 模拟用户角色(实际从后端返回的用户信息中获取) private readonly ROLE_KEY = 'user_role'; // 设置用户角色 setUserRole(role: 'admin' | 'user'): void { localStorage.setItem(this.ROLE_KEY, role); } // 获取用户角色 getUserRole(): 'admin' | 'user' | null { return localStorage.getItem(this.ROLE_KEY) as 'admin' | 'user' | null; }2. 实现角色权限守卫
创建RoleGuard,在CanActivate中增加角色校验:
// src/app/guards/role.guard.ts import { Injectable } from '@angular/core'; import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; import { AuthService } from '../services/auth.service'; @Injectable({ providedIn: 'root' }) export class RoleGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): boolean { // 1. 先判断是否登录(基础校验) if (!this.authService.isLoggedIn()) { this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); return false; } // 2. 获取路由配置中要求的角色(通过data属性传递) const requiredRoles = route.data['roles'] as Array<'admin' | 'user'>; if (!requiredRoles || requiredRoles.length === 0) { return true; // 未配置角色要求,直接放行 } // 3. 获取当前用户角色,判断是否匹配 const userRole = this.authService.getUserRole(); if (userRole && requiredRoles.includes(userRole)) { return true; // 角色匹配,放行 } // 4. 角色不匹配:跳转到无权限页面 this.router.navigate(['/unauthorized']); return false; } }3. 配置带角色要求的路由
// src/app/app-routing.module.ts 路由规则修改 const routes: Routes = [ { path: 'login', component: LoginComponent }, { path: 'unauthorized', component: UnauthorizedComponent }, // 无权限页面 // 普通用户和管理员都能访问 { path: 'dashboard', component: DashboardComponent, canActivate: [RoleGuard], data: { roles: ['admin', 'user'] } }, // 仅管理员能访问 { path: 'admin-panel', component: AdminPanelComponent, canActivate: [RoleGuard], data: { roles: ['admin'] } }, { path: '', redirectTo: '/dashboard', pathMatch: 'full' }, { path: '**', redirectTo: '/dashboard' } ];四、注意事项
- 异步场景处理:如果权限校验需要请求后端接口(比如校验 token 有效性),
canActivate可以返回Observable<boolean>或Promise<boolean>:canActivate(): Observable<boolean> { return this.authService.checkTokenValidity().pipe( map(valid => { if (!valid) { this.router.navigate(['/login']); return false; } return true; }) ); } - 守卫执行顺序:如果给一个路由绑定多个守卫(
canActivate: [AuthGuard, RoleGuard]),Angular 会按数组顺序执行,只要有一个守卫返回false,路由就会被拦截。 - 避免前端权限 “裸奔”:前端路由守卫仅为用户体验优化,核心权限必须由后端校验(比如接口请求时校验 token 和角色),防止用户通过修改前端代码绕过拦截。
总结
CanActivate是 Angular 控制路由访问的核心守卫,通过返回布尔值(或异步布尔值)决定是否允许路由激活;- 基础场景下,可通过
CanActivate实现登录拦截,未登录用户自动跳转到登录页; - 进阶场景中,结合路由
data属性可实现基于角色 / 权限的精细化路由控制,同时需注意异步校验和后端权限兜底。
通过CanActivate守卫,我们可以优雅地实现 Angular 应用的路由权限控制,既保障了应用安全,又能给用户提供清晰的访问指引,是 Angular 前端安全开发的必备技能。