Kov*_*aci 67 javascript rxjs angular
嗨,我试图弄清楚如何实现新的角度拦截器,并401 unauthorized通过刷新令牌和重试请求来处理错误.这是我一直关注的指南: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();
}
} );
}
}
} );
Run Code Online (Sandbox Code Playgroud)
authentication.service.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
} );
}
Run Code Online (Sandbox Code Playgroud)
上面的retryFailedRequests()文件是我无法弄清楚的.如何重新发送请求并在重试后通过解析程序将其提供给路由?
如果有帮助,这是所有相关代码:https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9
And*_*ski 72
我的最终解决方案 适用于并行请求.
export class AuthInterceptor implements HttpInterceptor {
authService;
refreshTokenInProgress = false;
tokenRefreshedSource = new Subject();
tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
constructor(private injector: Injector, private router: Router, private snackBar: MdSnackBar) {}
addAuthHeader(request) {
const authHeader = this.authService.getAuthorizationHeader();
if (authHeader) {
return request.clone({
setHeaders: {
"Authorization": authHeader
}
});
}
return request;
}
refreshToken() {
if (this.refreshTokenInProgress) {
return new Observable(observer => {
this.tokenRefreshed$.subscribe(() => {
observer.next();
observer.complete();
});
});
} else {
this.refreshTokenInProgress = true;
return this.authService.refreshToken()
.do(() => {
this.refreshTokenInProgress = false;
this.tokenRefreshedSource.next();
});
}
}
logout() {
this.authService.logout();
this.router.navigate(["login"]);
}
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).catch(error => {
if (error.status === 401) {
return this.refreshToken()
.switchMap(() => {
request = this.addAuthHeader(request);
return next.handle(request);
})
.catch(() => {
this.logout();
return Observable.empty();
});
}
return Observable.throw(error);
});
}
}
Run Code Online (Sandbox Code Playgroud)
Sam*_*pan 13
使用最新版本的Angular(7.0.0)和rxjs(6.3.3),这就是我创建一个功能齐全的自动会话恢复拦截器的方法,确保如果并发请求失败,那么它应该只会命中令牌刷新API一次并使用switchMap和Subject将失败的请求传递给该响应.下面是我的拦截器代码的样子.我已经省略了我的身份验证服务和存储服务的代码,因为它们是非常标准的服务类.
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;
}
}
Run Code Online (Sandbox Code Playgroud)
小智 9
我也遇到了类似的问题,我认为收集/重试逻辑过于复杂.相反,我们可以使用catch运算符来检查401,然后监视令牌刷新,并重新运行请求:
return next.handle(this.applyCredentials(req))
.catch((error, caught) => {
if (!this.isAuthError(error)) {
throw error;
}
return this.auth.refreshToken().first().flatMap((resp) => {
if (!resp) {
throw error;
}
return next.handle(this.applyCredentials(req));
});
}) as any;
Run Code Online (Sandbox Code Playgroud)
...
private isAuthError(error: any): boolean {
return error instanceof HttpErrorResponse && error.status === 401;
}
Run Code Online (Sandbox Code Playgroud)
Andrei Ostrovski的最终解决方案非常有效,但如果刷新令牌也已过期(假设您正在进行api调用以刷新),则无效.经过一番挖掘,我意识到刷新令牌API调用也被拦截器截获.我必须添加一个if语句来处理这个问题.
intercept( request: HttpRequest<any>, next: HttpHandler ):Observable<any> {
this.authService = this.injector.get( AuthenticationService );
request = this.addAuthHeader(request);
return next.handle( request ).catch( error => {
if ( error.status === 401 ) {
// The refreshToken api failure is also caught so we need to handle it here
if (error.url === environment.api_url + '/refresh') {
this.refreshTokenHasFailed = true;
this.authService.logout();
return Observable.throw( error );
}
return this.refreshAccessToken()
.switchMap( () => {
request = this.addAuthHeader( request );
return next.handle( request );
})
.catch((err) => {
this.refreshTokenHasFailed = true;
this.authService.logout();
return Observable.throw( err );
});
}
return Observable.throw( error );
});
}
Run Code Online (Sandbox Code Playgroud)
我必须解决以下要求:
因此,为了在 Angular 中刷新令牌,我收集了不同的选项:
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);
})
);
}
Run Code Online (Sandbox Code Playgroud)
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
)),
);
}
Run Code Online (Sandbox Code Playgroud)
所有这些选项都经过了全面测试,可以在angular-refresh-token github repo 中找到
也可以看看:
基于这个例子,这是我的作品
@Injectable({
providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {
constructor(private loginService: LoginService) { }
/**
* Intercept request to authorize request with oauth service.
* @param req original request
* @param next next
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
const self = this;
if (self.checkUrl(req)) {
// Authorization handler observable
const authHandle = defer(() => {
// Add authorization to request
const authorizedReq = req.clone({
headers: req.headers.set('Authorization', self.loginService.getAccessToken()
});
// Execute
return next.handle(authorizedReq);
});
return authHandle.pipe(
catchError((requestError, retryRequest) => {
if (requestError instanceof HttpErrorResponse && requestError.status === 401) {
if (self.loginService.isRememberMe()) {
// Authrozation failed, retry if user have `refresh_token` (remember me).
return from(self.loginService.refreshToken()).pipe(
catchError((refreshTokenError) => {
// Refresh token failed, logout
self.loginService.invalidateSession();
// Emit UserSessionExpiredError
return throwError(new UserSessionExpiredError('refresh_token failed'));
}),
mergeMap(() => retryRequest)
);
} else {
// Access token failed, logout
self.loginService.invalidateSession();
// Emit UserSessionExpiredError
return throwError(new UserSessionExpiredError('refresh_token failed'));
}
} else {
// Re-throw response error
return throwError(requestError);
}
})
);
} else {
return next.handle(req);
}
}
/**
* Check if request is required authentication.
* @param req request
*/
private checkUrl(req: HttpRequest<any>) {
// Your logic to check if the request need authorization.
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
您可能想检查用户是否启用了Remember Me使用刷新令牌进行重试或仅重定向到注销页面。
仅供参考,LoginService有以下方法:
- getAccessToken():字符串 - 返回当前值access_token
- isRememberMe():布尔值 - 检查用户是否有refresh_token
-freshToken():Observable / Promise - 向 oauth 服务器请求新的access_token使用refresh_token
- invalidateSession(): void - 删除所有用户信息并重定向到注销页面
| 归档时间: |
|
| 查看次数: |
32377 次 |
| 最近记录: |