[英]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.