.NET 7与Angular 16鉴权联动:JWT处理及路由守卫实现咨询
.NET 7 JWT API与Angular 16联动指南
我已经完成了基于.NET 7的API鉴权开发,包含Program.cs的中间件配置和AuthService实现,但不清楚如何与Angular 16项目联动。需要了解JWT的前后端传输方式、前端处理方法,以及如何用路由守卫实现角色(管理员/客户)的组件授权。以下是我的API代码:
Program.cs 中间件配置
builder.Services.AddTransient<IAuthService, AuthService>(); builder.Services.AddDbContext<DatabContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); builder.Services.AddIdentity<User, IdentityRole>(options => {}) .AddEntityFrameworkStores<DatabContext>(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) // Adding Jwt Bearer .AddJwtBearer(options => { options.SaveToken = true; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, ValidateAudience = true, ValidAudience = builder.Configuration["JWT:ValidAudience"], ValidIssuer = builder.Configuration["JWT:ValidIssuer"], ClockSkew = TimeSpan.Zero, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:Secret"])) }; });
AuthService 代码
public class AuthService : IAuthService { private readonly UserManager<User> userManager; private readonly RoleManager<IdentityRole> roleManager; private readonly IConfiguration _configuration; private readonly DatabContext databContext; public AuthService(UserManager<User> userManager, RoleManager<IdentityRole> roleManager, IConfiguration configuration, DatabContext databContext) { this.userManager = userManager; this.roleManager = roleManager; _configuration = configuration; this.databContext = databContext; } public async Task<(int, string)> Registration(RegistrationModel model) { var userExists = await userManager.FindByNameAsync(model.username); if (userExists != null) return (0, "User already exists"); var user = new User() { Email = model.mail, SecurityStamp = Guid.NewGuid().ToString(), UserName = model.username, Token="Null" }; var createUserResult = await userManager.CreateAsync(user, model.password); if (!createUserResult.Succeeded) return (0, "User creation failed! Please check user details and try again."); if (!await roleManager.RoleExistsAsync(UserRoles.Comptable)) await roleManager.CreateAsync(new IdentityRole(UserRoles.Comptable)); if (await roleManager.RoleExistsAsync(UserRoles.Comptable)) await userManager.AddToRoleAsync(user, UserRoles.Comptable); databContext.SaveChangesAsync(); return (1, "User created successfully!"); } public async Task<(int, string)> Login(LoginModel model) { var user = await userManager.FindByNameAsync(model.Username); if (user == null) return (0, "Invalid username"); if (!await userManager.CheckPasswordAsync(user, model.Password)) return (0, "Invalid password"); var userRoles = await userManager.GetRolesAsync(user); var authClaims = new List<Claim> { new Claim(ClaimTypes.Name, user.UserName), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), }; foreach (var userRole in userRoles) { authClaims.Add(new Claim(ClaimTypes.Role, userRole)); } string token = GenerateToken(authClaims); return (1, token); } private string GenerateToken(IEnumerable<Claim> claims) { var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"])); var tokenDescriptor = new SecurityTokenDescriptor { Issuer = _configuration["JWT:ValidIssuer"], Audience = _configuration["JWT:ValidAudience"], Expires = DateTime.UtcNow.AddHours(3), SigningCredentials = new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256), Subject = new ClaimsIdentity(claims) }; var tokenHandler = new JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } }
一、JWT前后端传输方式
- 登录阶段:前端发起POST请求到API的Login接口,API验证账号密码通过后,返回JWT Token(对应AuthService中Login方法返回的第二个字符串参数)。
- 请求携带Token:前端后续所有需要鉴权的API请求,必须在请求头中添加
Authorization: Bearer {Token},API通过JwtBearer中间件自动验证Token的合法性。 - Token存储:前端将Token存储在
localStorage(持久化,页面刷新不丢失)或sessionStorage(会话级,关闭页面自动清除)中,也可结合Angular的全局服务统一管理。
二、Angular前端处理实现
1. 封装Auth服务(auth.service.ts)
用于处理登录、注册、Token管理和角色解析:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; interface LoginResponse { code: number; message: string; } interface UserInfo { username: string; roles: string[]; } @Injectable({ providedIn: 'root' }) export class AuthService { private currentUserSubject: BehaviorSubject<UserInfo | null>; public currentUser: Observable<UserInfo | null>; constructor(private http: HttpClient) { const storedUser = localStorage.getItem('currentUser'); this.currentUserSubject = new BehaviorSubject<UserInfo | null>(storedUser ? JSON.parse(storedUser) : null); this.currentUser = this.currentUserSubject.asObservable(); } public get currentUserValue(): UserInfo | null { return this.currentUserSubject.value; } login(username: string, password: string): Observable<boolean> { return this.http.post<LoginResponse>('https://你的API地址/login', { Username: username, Password: password }) .pipe(map(response => { if (response.code === 1) { const token = response.message; // 解析Token中的用户名和角色信息 const decodedToken = this.decodeToken(token); const userInfo: UserInfo = { username: decodedToken['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name'], roles: Array.isArray(decodedToken['http://schemas.microsoft.com/ws/2008/06/identity/claims/role']) ? decodedToken['http://schemas.microsoft.com/ws/2008/06/identity/claims/role'] : [decodedToken['http://schemas.microsoft.com/ws/2008/06/identity/claims/role']] }; // 存储Token和用户信息 localStorage.setItem('token', token); localStorage.setItem('currentUser', JSON.stringify(userInfo)); this.currentUserSubject.next(userInfo); return true; } return false; })); } logout(): void { localStorage.removeItem('token'); localStorage.removeItem('currentUser'); this.currentUserSubject.next(null); } private decodeToken(token: string): any { const payload = token.split('.')[1]; return JSON.parse(atob(payload)); } }
2. 配置HTTP拦截器(auth.interceptor.ts)
自动为所有请求添加Authorization头,无需手动在每个请求中配置:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = localStorage.getItem('token'); if (token) { request = request.clone({ setHeaders: { Authorization: `Bearer ${token}` } }); } return next.handle(request); } }
在app.module.ts中注册拦截器:
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './auth.interceptor'; @NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } ] }) export class AppModule { }
3. 实现路由守卫
(1)登录守卫(auth.guard.ts):验证用户是否已登录
import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { const currentUser = this.authService.currentUserValue; if (currentUser) { return true; } // 未登录则跳转到登录页,记录当前路由以便登录后返回 this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); return false; } }
(2)角色守卫(role.guard.ts):验证用户角色是否有权限访问目标页面
import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable({ providedIn: 'root' }) export class RoleGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { const currentUser = this.authService.currentUserValue; if (!currentUser) { this.router.navigate(['/login']); return false; } // 获取路由配置中要求的角色列表 const requiredRoles = route.data['requiredRoles'] as string[]; if (!requiredRoles) { return true; } // 判断用户角色是否包含要求的角色 const hasPermission = currentUser.roles.some(role => requiredRoles.includes(role)); if (hasPermission) { return true; } // 无权限则跳转到无权限提示页 this.router.navigate(['/unauthorized']); return false; } }
4. 配置路由(app-routing.module.ts)
在路由中通过守卫控制页面访问权限:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './login/login.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { AdminPanelComponent } from './admin-panel/admin-panel.component'; import { CustomerComponent } from './customer/customer.component'; import { AuthGuard } from './auth.guard'; import { RoleGuard } from './role.guard'; const routes: Routes = [ { path: 'login', component: LoginComponent }, // 登录后才能访问的首页 { path: '', component: DashboardComponent, canActivate: [AuthGuard] }, // 管理员页面:要求Comptable角色(对应API中注册的默认角色) { path: 'admin', component: AdminPanelComponent, canActivate: [AuthGuard, RoleGuard], data: { requiredRoles: ['Comptable'] } }, // 客户页面:要求Customer角色(需在API中补充该角色的创建和分配逻辑) { path: 'customer', component: CustomerComponent, canActivate: [AuthGuard, RoleGuard], data: { requiredRoles: ['Customer'] } }, { path: 'unauthorized', component: () => import('./unauthorized/unauthorized.component').then(m => m.UnauthorizedComponent) }, { path: '**', redirectTo: '' } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
三、注意事项
- 目前API中注册默认分配
Comptable角色,若需要Customer角色,需在AuthService的Registration方法中添加对应的角色判断和分配逻辑。 - 生产环境建议使用
HttpOnlyCookie存储Token(需API配置Cookie发送),比localStorage更安全,可避免XSS攻击。 - 需处理Token过期场景:在HTTP拦截器中捕获401错误,自动清除Token并跳转到登录页。
内容的提问来源于stack exchange,提问作者Oussama hdidou
相关产品推荐
相关产品推荐

