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

Angular 18独立项目JWT授权头无法添加至请求的问题

问题描述

我正在开发首个Spring Boot+Angular 18独立项目,包含三个接口:

  • /register - 公开访问
  • /login - 公开访问,返回token及过期时间
  • /users - 仅持有效JWT的授权用户可访问,返回用户列表

后端生成JWT用于授权,前端需将token存入localStorage,并在登录后为所有请求添加Authorization: Bearer TOKEN头。后端经Postman测试功能正常,但前端虽能接收并存储token,请求却未携带该授权头。

已尝试添加AuthInterceptor并配置至main.ts,但问题仍未解决,相关代码如下:

LoginComponent

import { Component } from '@angular/core';
import { CardModule } from 'primeng/card';
import { InputTextModule } from 'primeng/inputtext';
import { FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { FormBuilder } from '@angular/forms';
import { ButtonModule } from 'primeng/button';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';  
import { ImageModule } from 'primeng/image';
import { AuthService } from '../../services/auth.service';
import { Router } from '@angular/router';
import { UserLoginDto } from '../../interfaces/userLoginDto';
import { UserService } from '../../services/user.service';
import { LoginResponse } from '../../interfaces/loginResponse';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [
    CardModule,
    InputTextModule,
    ReactiveFormsModule,
    ButtonModule,
    RouterModule,
    CommonModule,
    ImageModule
  ],
  templateUrl: './login.component.html',
  styleUrl: './login.component.css',
})
export class LoginComponent {
  loginForm!: FormGroup;

  constructor(
    private fb: FormBuilder,
    private authService: AuthService,
    private router: Router,
    private userService: UserService
  ){}

  ngOnInit(): void {
    this.loginForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', Validators.required]
    });
  }

  get email() {
    return this.loginForm.controls['email'];
  }
  get password() { return this.loginForm.controls['password']; }

  onSubmit(){
    if(this.loginForm.valid){
      const userLoginDto: UserLoginDto = {
        email: this.loginForm.value.email,
        password: this.loginForm.value.password
      };
      this.userService.loginUser(userLoginDto).subscribe({
        next: (response: LoginResponse) => {  
          console.log('User logged in successfully', response);
          const token = response.token;  
          this.authService.setAuthToken(token);
          this.router.navigate(['/home']);
        },
        error: (err) => console.error('Error registering user', err)
      });
    }
  }
}

UserService

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from '../interfaces/user';
import { UserRegisterDto } from '../interfaces/userRegisterDto';
import { UserLoginDto } from '../interfaces/userLoginDto';
import { LoginResponse } from '../interfaces/loginResponse';

@Injectable({
  providedIn: 'root'
})
export class UserService {

  private apiServerUrl  = 'http://localhost:8080';

  constructor(private http: HttpClient) { }

  public getAllUsers(): Observable<User[]>{
    return this.http.get<User[]>(`${this.apiServerUrl}/users`);
  }

  public registerUser(user: UserRegisterDto): Observable<UserRegisterDto>{
    return this.http.post<UserRegisterDto>(`${this.apiServerUrl}/register`, user);
  }

  public loginUser(user: UserLoginDto): Observable<LoginResponse>{
    return this.http.post<LoginResponse>(`${this.apiServerUrl}/login`, user);
  }
}

AuthService

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private tokenKey: string = 'token';

  getAuthToken(): string | null{
    const token =  window.localStorage.getItem(this.tokenKey);
    console.log("Retrieved token:", token);
    return token;
  }

  setAuthToken(token: string | null): void{
    if(token !== null){
      window.localStorage.setItem(this.tokenKey, token);
    } else{
      window.localStorage.removeItem(this.tokenKey)
    }
  }
}

AuthInterceptor

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { Observable } from 'rxjs';
import { AuthService } from './services/auth.service';


@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(
    private authService: AuthService,
  ){}

  intercept(req: HttpRequest<any>, next: HttpHandler){
    const authToken = this.authService.getAuthToken();
    const authReq = req.clone({
      headers: req.headers.set('Authorization', `Bearer ${authToken}`)
    });

    return next.handle(authReq);
  }
}

main.ts

import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app/app.routes';
import { provideRouter } from '@angular/router';
import { AuthInterceptor } from './app/auth-interceptor';

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ]
})
  .catch((err) => console.error(err));

解决方案

针对Angular 18独立项目的拦截器配置问题,按以下步骤调整:

  1. 改用函数式拦截器(Angular 18适配方案)
    Angular 18独立项目推荐使用函数式拦截器替代传统类拦截器,修改拦截器代码:

    // auth.interceptor.ts
    import { HttpInterceptorFn } from '@angular/common/http';
    import { inject } from '@angular/core';
    import { AuthService } from './services/auth.service';
    
    export const authInterceptor: HttpInterceptorFn = (req, next) => {
      const authService = inject(AuthService);
      const authToken = authService.getAuthToken();
    
      // 仅给需要授权的请求添加头,排除公开接口
      if (authToken && !req.url.includes('/login') && !req.url.includes('/register')) {
        const authReq = req.clone({
          headers: req.headers.set('Authorization', `Bearer ${authToken}`)
        });
        return next(authReq);
      }
      return next(req);
    };
    
  2. 更新main.ts的HTTP配置
    移除HTTP_INTERCEPTORS提供者,改用withInterceptors注册函数式拦截器:

    // main.ts
    import { bootstrapApplication } from '@angular/platform-browser';
    import { AppComponent } from './app/app.component';
    import { provideHttpClient, withInterceptors } from '@angular/common/http';
    import { routes } from './app/app.routes';
    import { provideRouter } from '@angular/router';
    import { authInterceptor } from './app/auth.interceptor';
    
    bootstrapApplication(AppComponent, {
      providers: [
        provideRouter(routes),
        provideHttpClient(withInterceptors([authInterceptor]))
      ]
    })
      .catch((err) => console.error(err));
    
  3. 修复拦截器逻辑漏洞

    • 原代码未判断token是否存在,可能会生成Bearer null的无效请求头
    • 排除/login、/register等公开接口,避免不必要的授权头添加
  4. 验证拦截器生效状态
    在拦截器函数中添加日志,确认是否触发:

    export const authInterceptor: HttpInterceptorFn = (req, next) => {
      console.log('Interceptor triggered for URL:', req.url);
      // ... 其他代码
    };
    

    登录后调用/users接口,查看控制台日志及网络请求头是否包含Authorization。

  5. 确认token存储正确性
    登录后在控制台执行localStorage.getItem('token'),验证token是否正确存入。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:53:17