首页 > 解决方案 > Angular JWT 刷新令牌

问题描述

我正在尝试使用基于外部 API 和 Angular 的刷新令牌来实现 JWT。我写了以下代码

令牌拦截器

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, first } from 'rxjs/operators';
import {AuthenticationService} from '../services/authentication.service'

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  constructor(public authService : AuthenticationService ) {}

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
      console.log(`AddTokenInterceptor - ${request.url}`);      

      return next.handle(this.addToken(request, localStorage.getItem('access_token')))
      .pipe(catchError(error => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          this.refreshToken()
          .pipe(first())
          .subscribe(
              data => {
                return next.handle(this.addToken(request, localStorage.getItem('access_token')))
              },
          ) 
        } else {
          return throwError(error);
        }
      }));
  }

  private addToken(request: HttpRequest<any>, token: string) {
    return request.clone({
      setHeaders: {
        'Authorization': `Bearer ${token}`
      }
    });
  } 

  private refreshToken(){
    return this.authService.refreshToken()
  }
}

身份验证服务

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

import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class AuthenticationService {
  public currentUser: string

  constructor(
    private http: HttpClient, 
    ) { }

  login(username:string, password:string){
    return this.http.post<any>('http://localhost:8000/api/token/', {username: username, password: password})
      .pipe(
        map(data => {
          localStorage.setItem('access_token', data.access)
          localStorage.setItem('refresh_token', data.refresh)
        })
      )
  }

  logout(){
    localStorage.removeItem('access_token')
    localStorage.removeItem('refresh_token')
  }

  getJWToken(){
    return localStorage.getItem('access_token')
  }

  getRefreshToken(){
    return localStorage.getItem('refresh_token')
  }

  refreshToken(){
    let refreshToken : string = localStorage.getItem('refresh_token'); 

    return this.http.post<any>('http://localhost:8000/api/token/refresh/', {"refresh": refreshToken}).pipe(
      map(data => {
        localStorage.setItem('access_token', data.access)
      })
    )
  }
}

主页组件

import { Component, OnInit } from '@angular/core';
import { TeamService} from '../../services/team.service'
import { first } from 'rxjs/operators';


@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
  teams; 
  constructor(private teamService : TeamService) { }

  ngOnInit(): void {
    this.teamService.getTeams().pipe(first()).subscribe(
      data => {
        this.teams = data.results
      },
      error => {
        console.log(error.error)
      }
    )        

  }

  login() : void {
    console.log(this.teams)
  }
}

当返回 401 响应时,我正在尝试刷新令牌,现在发生以下情况:

之后,当我刷新页面时,团队变量已正确加载并且可以使用。我的问题:如何在发出请求之前刷新令牌,以便始终可以使用有效的访问令牌发出请求?似乎错误在于,TokenInterceptor但我似乎无法弄清楚如何解决这个问题

标签: angular

解决方案


一切看起来都很好,除了在拦截器中订阅而不是尝试在管道中映射响应。

更新

正如@ionut-t 在评论中指出的那样,必须有两个变化:

  • 替换subscriptionswitchMap运算符
  • catchError返回运算符中的可观察对象
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
  console.log(`AddTokenInterceptor - ${request.url}`);      

  return next.handle(this.addToken(request, localStorage.getItem('access_token')))
  .pipe(catchError(error => {
    if (error instanceof HttpErrorResponse && error.status === 401) {
      return this.refreshToken()
      .pipe(
        first(),
        switchMap(        // <-- map the response instead of subscribing here
          data => next.handle(this.addToken(request, localStorage.getItem('access_token')))
        )
      )
      ...

推荐阅读