[英]Angular JWT refresh token
我正在嘗試使用基於外部 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 響應時,我正在嘗試刷新令牌,現在發生以下情況:
"You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable."
之后,當我刷新頁面時,團隊變量已正確加載並且可以使用。 我的問題:如何在發出請求之前刷新令牌,以便始終可以使用有效的訪問令牌發出請求? 似乎錯誤出在TokenInterceptor
但我似乎無法弄清楚如何解決這個問題
一切看起來都很好,除了在攔截器內訂閱而不是嘗試 map pipe 中的響應。
更新
正如@ionut-t 在評論中指出的那樣,必須有兩個變化:
switchMap
操作符替換subscription
catchError
運算符中返回 observableintercept(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')))
)
)
...
您無法刷新所有 401 響應的令牌,因為如果用戶嘗試使用無效憑據登錄您的系統,也會有 401 響應。
通常,來自 API 的 HTTP 響應 header 有一些東西表明該客戶端曾經通過身份驗證,但現在有一個過期的令牌。 通常,響應 header 具有稱為 token-expired 或 www-authenticate 的屬性; 您必須在開始刷新令牌過程之前檢查這一點。
代碼示例:
身份驗證攔截器
import { Injectable } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpErrorResponse
} from '@angular/common/http';
import { AuthService } from '../services/auth.service';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { environment } from 'src/environments/environment';
import { filter, switchMap, take, catchError } from 'rxjs/operators';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private tryingRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(public authService: AuthService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.authService.getToken();
request = this.addAuthorization(request, token);
return next.handle(request).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
const tokenExpired = error.headers.get('token-expired');
if (tokenExpired) {
return this.handle401Error(request, next);
}
this.authService.logout();
return throwError(error);
} else {
return throwError(error);
}
}));
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.tryingRefreshing) {
this.tryingRefreshing = true;
this.refreshTokenSubject.next(null);
return this.authService.refreshToken().pipe(
switchMap((token: any) => {
this.tryingRefreshing = false;
this.refreshTokenSubject.next(token);
return next.handle(this.addAuthorization(request, token));
}));
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(jwt => {
return next.handle(this.addAuthorization(request, jwt));
}));
}
}
addAuthorization(httpRequest: HttpRequest<any>, token: string) {
return httpRequest = httpRequest.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
}
刷新令牌
這只是展示 share() 方法的示例方法。
refreshToken(): Observable<string> {
return this.http.post<any>(`${this.baseUrl}/auth/token/refresh-token`, {}, { withCredentials: true })
.pipe(
share(),
map((authResponse) => {
this.currentAuthSubject.next(authResponse);
this.addToLocalStorage(authResponse);
return authResponse.token;
}));
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.