您好,我正试图通过刷新令牌并重试请求,找出如何实现新的角度拦截器和处理401未经授权的
错误。这是我一直遵循的指南:https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors
我成功缓存失败的请求,并可以刷新令牌,但我不知道如何重新发送以前失败的请求。我还想让它与我目前使用的解析器一起工作。
token.interceptor.ts
return next.handle( request ).do(( event: HttpEvent<any> ) => {
if ( event instanceof HttpResponse ) {
// do stuff with response if you want
}
}, ( err: any ) => {
if ( err instanceof HttpErrorResponse ) {
if ( err.status === 401 ) {
console.log( err );
this.auth.collectFailedRequest( request );
this.auth.refreshToken().subscribe( resp => {
if ( !resp ) {
console.log( "Invalid" );
} else {
this.auth.retryFailedRequests();
}
} );
}
}
} );
认证。服务ts
cachedRequests: Array<HttpRequest<any>> = [];
public collectFailedRequest ( request ): void {
this.cachedRequests.push( request );
}
public retryFailedRequests (): void {
// retry the requests. this method can
// be called after the token is refreshed
this.cachedRequests.forEach( request => {
request = request.clone( {
setHeaders: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${ this.getToken() }`
}
} );
//??What to do here
} );
}
上面的retryFailed请求()文件是我不明白的。如何重新发送请求,并使它们在重试后通过解析器的路由可用?
如果有帮助的话,这是所有相关的代码:https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9
我必须解决以下要求:
因此,我收集了不同的选项,以便在Angular中刷新令牌:
令牌刷新$
行为主体作为信号量catchError
参数重试请求失败的请求intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let retries = 0;
return this.authService.token$.pipe(
map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
concatMap(authReq => next.handle(authReq)),
// Catch the 401 and handle it by refreshing the token and restarting the chain
// (where a new subscription to this.auth.token will get the latest token).
catchError((err, restart) => {
// If the request is unauthorized, try refreshing the token before restarting.
if (err.status === 401 && retries === 0) {
retries++;
return concat(this.authService.refreshToken$, restart);
}
if (retries > 0) {
this.authService.logout();
}
return throwError(err);
})
);
}
retry当
RxJS运算符intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.authService.token$.pipe(
map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
concatMap(authReq => next.handle(authReq)),
retryWhen((errors: Observable<any>) => errors.pipe(
mergeMap((error, index) => {
// any other error than 401 with {error: 'invalid_grant'} should be ignored by this retryWhen
if (error.status !== 401) {
return throwError(error);
}
if (index === 0) {
// first time execute refresh token logic...
return this.authService.refreshToken$;
}
this.authService.logout();
return throwError(error);
}),
take(2)
// first request should refresh token and retry,
// if there's still an error the second time is the last time and should navigate to login
)),
);
}
所有这些选项都经过全面测试,可以在angular refresh token github repo中找到
另见:
使用最新版本的Angular(7.0.0)和rxjs(6.3.3),这就是我如何创建一个功能齐全的自动会话恢复拦截器,以确保如果401并发请求失败,那么它也应该只命中令牌刷新API一次,并将失败的请求管道到使用SwitchMap和主题的响应。下面是我的拦截代码的样子。我省略了我的身份验证服务和存储服务的代码,因为它们是相当标准的服务类。
import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest
} from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, Subject, throwError } from "rxjs";
import { catchError, switchMap } from "rxjs/operators";
import { AuthService } from "../auth/auth.service";
import { STATUS_CODE } from "../error-code";
import { UserSessionStoreService as StoreService } from "../store/user-session-store.service";
@Injectable()
export class SessionRecoveryInterceptor implements HttpInterceptor {
constructor(
private readonly store: StoreService,
private readonly sessionService: AuthService
) {}
private _refreshSubject: Subject<any> = new Subject<any>();
private _ifTokenExpired() {
this._refreshSubject.subscribe({
complete: () => {
this._refreshSubject = new Subject<any>();
}
});
if (this._refreshSubject.observers.length === 1) {
this.sessionService.refreshToken().subscribe(this._refreshSubject);
}
return this._refreshSubject;
}
private _checkTokenExpiryErr(error: HttpErrorResponse): boolean {
return (
error.status &&
error.status === STATUS_CODE.UNAUTHORIZED &&
error.error.message === "TokenExpired"
);
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
if (req.url.endsWith("/logout") || req.url.endsWith("/token-refresh")) {
return next.handle(req);
} else {
return next.handle(req).pipe(
catchError((error, caught) => {
if (error instanceof HttpErrorResponse) {
if (this._checkTokenExpiryErr(error)) {
return this._ifTokenExpired().pipe(
switchMap(() => {
return next.handle(this.updateHeader(req));
})
);
} else {
return throwError(error);
}
}
return caught;
})
);
}
}
updateHeader(req) {
const authToken = this.store.getAccessToken();
req = req.clone({
headers: req.headers.set("Authorization", `Bearer ${authToken}`)
});
return req;
}
}
根据@安东-toshik的评论,我认为在一篇文章中解释这段代码的功能是个好主意。你可以在这里阅读我的文章,了解这段代码的解释和理解(它是如何工作的以及为什么工作的?)。希望有帮助。
我的最终解决方案。适用于并行请求。
更新:代码更新为Angular 9/RxJS 6,错误处理和refreshToken失败时的修复循环
import { HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS } from "@angular/common/http";
import { Injector } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, Observable, throwError } from "rxjs";
import { catchError, switchMap, tap} from "rxjs/operators";
import { AuthService } from "./auth.service";
export class AuthInterceptor implements HttpInterceptor {
authService;
refreshTokenInProgress = false;
tokenRefreshedSource = new Subject();
tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
constructor(private injector: Injector, private router: Router) {}
addAuthHeader(request) {
const authHeader = this.authService.getAuthorizationHeader();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": authHeader
}
});
}
return request;
}
refreshToken(): Observable<any> {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
return this.authService.refreshToken().pipe(
tap(() => {
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
}),
catchError(() => {
this.refreshTokenInProgress = false;
this.logout();
}));
}
}
logout() {
this.authService.logout();
this.router.navigate(["login"]);
}
handleResponseError(error, request?, next?) {
// Business error
if (error.status === 400) {
// Show message
}
// Invalid token error
else if (error.status === 401) {
return this.refreshToken().pipe(
switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
}),
catchError(e => {
if (e.status !== 401) {
return this.handleResponseError(e);
} else {
this.logout();
}
}));
}
// Access denied error
else if (error.status === 403) {
// Show message
// Logout
this.logout();
}
// Server error
else if (error.status === 500) {
// Show message
}
// Maintenance error
else if (error.status === 503) {
// Show message
// Redirect to the maintenance page
}
return throwError(error);
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
this.authService = this.injector.get(AuthService);
// Handle request
request = this.addAuthHeader(request);
// Handle response
return next.handle(request).pipe(catchError(error => {
return this.handleResponseError(error, request, next);
}));
}
}
export const AuthInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
};
我正在尝试实现401响应的捕获,并尝试在令牌刷新后基于Angular 4 Intericetor重试请求获取刷新令牌。我试图实现同样的事情,但我始终无法重试该请求,我真的不确定这是否是应用刷新令牌策略的最佳方法。这是我的代码: 问题是在中从未到达切换映射... 还有do运算符… 这让我想到了我的authService refreshToken方法... 它返回一个映射的可观察值,我知道如果我替换了
我有 401 拦截器,当access_token过期时,有一个请求的成功案例。拦截器重新加载令牌并返回 next.handle(customReq)。但是当同时发出 2 个或更多请求并且两个请求的令牌都已过期时,我遇到了问题,因为第二个请求尝试再次刷新,但现在刷新令牌无效。所以。。。。我尝试设置一个标志只执行一次,并使用自定义可观察量返回。问题是组件现在永远不会成功,我无法删除加载器。 HTTP
我在自己的Web API上使用Oauth2,在Web应用程序上使用ASP.NET C#来使用该API。在我的web应用程序上,我正在进行HttpWebRequests。当我的访问令牌过期时,我调用一个方法“refreshToken”,该方法发出请求以获取新的访问令牌。这工作很好,没有问题...除了我得到的响应包含一个新的刷新令牌???我在等新的访问令牌。我甚至认为在没有再次传递凭据的情况下这是不可
问题内容: 我有一个有角度的应用程序,有时每个状态会执行多个$ http.get请求。该应用将JWT用于带有刷新令牌的用户身份验证。API服务器会发送由于身份验证错误而失败的每个请求。我做了一个请求,该请求在401错误时请求带有刷新令牌的新令牌,然后重新发送原始请求。 问题是,如果一个状态发出例如2个$ http.get请求,并且都获得401响应,那么我将访问令牌更新两次。显然,我只想刷新一次令牌
我正在尝试从应用程序服务中获取Google的刷新令牌,但我不能。 日志说 2016-11-04T00:04:25 PID[500]收到的详细请求:获取https://noteappsvr.azurewebsites.net/.auth/login/google?access _ type = offline 2016-11-04t 00:04:25 PID[500]从https://account
我在刷新访问令牌时得到这个错误:访问令牌无法刷新。请重新验证 这一错误此前曾在2017年4月报告过。OneLogin文档声明刷新令牌可以使用45天左右。我的刷新令牌已经有20个小时了。是文档正确还是刷新令牌的寿命更短?我可以做获取访问令牌和撤销令牌罚款。 public RootObject RefreshToken(HttpRequesterDM rDM){restsharp.deserializ