刷新令牌OAuth身份验证Angular 4+

Jul*_*rra 0 http interceptor angular-http-interceptors angular

我正在使用HttpAngular HttpClient的解决方案,但是我决定进行迁移并使用新的解决方案,并且我试图创建一种解决方案Interceptors来管理需要刷新令牌以及需要将标头修改为放置授权令牌。

Jul*_*rra 5

首先,我找到了这些文章以及其他许多文章:

...但是如果您只想处理放置授权标头的操作,那么这些解决方案是完美的。然后我想出了这个解决方案

@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {

  constructor(private injector: Injector, private authService: Auth) {
  }


  private getRequestWithAuthentication(request: HttpRequest<any>, next: HttpHandler, auth: OAuthService): Observable<HttpEvent<any>> {
    const  req = request.clone({
        headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
      });
    return next.handle(req);
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // To avoid cyclic dependency
    const auth = this.injector.get(OAuthService);

    if (auth.hasAuthorization()) {
      return this.getRequestWithAuthentication(request, next, auth);
    } else if (auth.hasAuthorizationRefresh() && request.url !== AUTHORIZE_URL) {
      return auth.refreshToken().flatMap(
        (res: any) => {
          auth.saveTokens(res);
          return this.getRequestWithAuthentication(request, next, auth);
        }
      ).catch(() => {
        return next.handle(request);
      });
    } else if (request.url === AUTHORIZE_URL) {
      return next.handle(request);
    }

    return this.getRequestWithAuthentication(request, next, auth);
  }
}
Run Code Online (Sandbox Code Playgroud)

这样做的主要思想很简单:

  • 首先,我要注入一个服务,如果我有令牌刷新令牌,则可以确定所有逻辑,当然还有保存和获取它的操作。
  • 如果我有授权​​(这是放在标头中的令牌),则只返回带有授权标头的请求,否则,请检查是否有刷新令牌,然后尝试从服务器获取它。我一直等到我有令牌来传递请求。
  • 常量AUTHORIZE_URL是一个字符串,其中包含我用来获取令牌或刷新令牌的服务器路由。因为我检查的原因是因为我想提出一个请求,以HttpClientOAuthService这样它会自拦截传球也和它的会是一个无限循环,如果我不签。

在某些情况下,此解决方案可以正常工作,但是事情是,例如,当令牌过期并且您有多个请求时,每个请求都将尝试刷新令牌。


在此之后,我找到了这个解决方案,但我想知道您如何看待代码和执行方式。

好的,首先,我创建了一个服务来保存刷新令牌请求的状态,并且可以观察到该请求何时完成。

这是我的服务:

@Injectable()
export class RefreshTokenService {
  public processing: boolean = false;
  public storage: Subject<any> = new Subject<any>();

  public publish(value: any) {
    this.storage.next(value);
  }
}
Run Code Online (Sandbox Code Playgroud)

我注意到最好有两个拦截器,一个拦截器刷新令牌并处理令牌,另一个拦截器放置授权头(如果存在)。

这是刷新令牌的拦截器:

@Injectable()
  export class RefreshTokenInterceptor implements HttpInterceptor {

    constructor(private injector: Injector, private tokenService: RefreshTokenService) {
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      const auth = this.injector.get(OAuthService);
      if (!auth.hasAuthorization() && auth.hasAuthorizationRefresh() && !this.tokenService.processing && request.url !== AUTHORIZE_URL) {
        this.tokenService.processing = true;
        return auth.refreshToken().flatMap(
          (res: any) => {
            auth.saveTokens(res);
            this.tokenService.publish(res);
            this.tokenService.processing = false;
            return next.handle(request);
          }
        ).catch(() => {
          this.tokenService.publish({});
          this.tokenService.processing = false;
          return next.handle(request);
        });
      } else if (request.url === AUTHORIZE_URL) {
        return next.handle(request);
      }

      if (this.tokenService.processing) {
        return this.tokenService.storage.flatMap(
          () => {
            return next.handle(request);
          }
        );
      } else {
        return next.handle(request);
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

因此,在这里,我等待刷新令牌可用或失败,然后释放需要授权标头的请求。

这是放置授权标头的拦截器:

@Injectable()
  export class TokenInterceptor implements HttpInterceptor {
    constructor(private injector: Injector) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      const auth = this.injector.get(OAuthService);
      let req = request;
      if (auth.hasAuthorization()) {
        req = request.clone({
          headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
        });
      }

      return next.handle(req).do(
        () => {},
        (error: any) => {
          if (error instanceof HttpErrorResponse) {
            if (error.status === 401) {
              auth.logOut();
            }
          }
        });
    }
  }
Run Code Online (Sandbox Code Playgroud)

我的主要模块是这样的:

@NgModule({
  imports: [
    ...,
    HttpClientModule
  ],
  declarations: [
    ...
  ],
  providers: [
    ...
    OAuthService,
    AuthService,
    RefreshTokenService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: RefreshTokenInterceptor,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}
Run Code Online (Sandbox Code Playgroud)

请提供任何反馈意见,如果我做错了什么,请告诉我。我正在使用Angular 4.4.6进行测试,但是我不知道它是否可以在Angular 5上正常工作。