You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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前后端传输方式

  1. 登录阶段:前端发起POST请求到API的Login接口,API验证账号密码通过后,返回JWT Token(对应AuthService中Login方法返回的第二个字符串参数)。
  2. 请求携带Token:前端后续所有需要鉴权的API请求,必须在请求头中添加Authorization: Bearer {Token},API通过JwtBearer中间件自动验证Token的合法性。
  3. 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 { }

三、注意事项

  1. 目前API中注册默认分配Comptable角色,若需要Customer角色,需在AuthService的Registration方法中添加对应的角色判断和分配逻辑。
  2. 生产环境建议使用HttpOnly Cookie存储Token(需API配置Cookie发送),比localStorage更安全,可避免XSS攻击。
  3. 需处理Token过期场景:在HTTP拦截器中捕获401错误,自动清除Token并跳转到登录页。

内容的提问来源于stack exchange,提问作者Oussama hdidou

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:34:57