[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report <!-- Please search GitHub for a similar issue or PR before submitting -->
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
尝试做AuthInterceptor的相同示例(如官方文档) ,以在所有请求上添加Authorization标头。
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const authHeader = this.auth.getAuthorizationHeader();
const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
return next.handle(authReq);
}
}
问题:对于真实代码, AuthService
需要在登录,登录等时请求服务器,因此它本身会注入HttpClient
。
@Injectable()
export class AuthService {
constructor(private http: HttpClient) {}
}
它在控制台中创建此致命错误:
Uncaught Error: Provider parse errors:
Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1
应该可以正常工作。 否则,拦截器只能使用不带有Http的服务。
Angular version: 4.3.0
会这样吗?
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(@Inject(forwardRef(() => AuthService)) private auth: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const authHeader = this.auth.getAuthorizationHeader();
const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
return next.handle(authReq);
}
}
@tytskyi我试过了,没有用
constructor(inj: Injector) {
this.auth = inj.get(AuthService)
}
虽然有效
我确认@tytskyi解决方案无效。
对于信息,到目前为止,我已经分为两个服务,一个用于登录/登录/ ...(使用HttpClient),另一个仅用于管理AuthToken(不使用Http)。
但是更进一步,我添加了一个错误处理程序来捕获401错误(当令牌无效时)。 但是在这种情况下,我需要调用注销,因此我需要使用HttpClient进行服务,因此我又陷入了最初的问题,因此这是非常有问题的。
这是一个真正的循环依赖关系, @ Toxicable的示例是处理它的正确方法。
但是,我会担心AuthService
正在发出自己的HTTP请求,而AuthInterceptor
是否在未检查该请求的详细信息的情况下将标头添加到了每个请求(如编写的那样)。 这意味着一个请求将通过拦截器,然后该拦截器进入auth服务,该服务将发出一个请求,该请求通过拦截器,并无限递归。
不, AuthService
包含需要执行一些HTTP请求的方法(例如login
),但是AuthInterceptor
确实不使用它们,只需要AuthService
以获取令牌。
我的代码与@cyrilletuzi非常相似,我尝试了@Toxicable解决方案,没有更多的循环依赖关系,但得到:
ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded
RangeError: Maximum call stack size exceeded
at _createProviderInstance$1 (core.es5.js:9480)
...
我使用HttpClient并创建此拦截器来添加jwt令牌。 一切工作都很完美,但是我做得不好。 我在HttpClient拦截器内使用Http。 如果我改变
private http: Http,
至private http: HttpClient
我收到这个周期错误
无法实例化循环依赖! InjectionToken_HTTP_INTERCEPTORS(“ [错误->]”)
有什么想法可以使它起作用吗?
import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {Http} from "@angular/http";
import {SiteService} from "../services/site.service";
import {Router} from "@angular/router";
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private http: Http,
private router: Router,
private siteService: SiteService
) {}
refreshToken() {
return this.http.get(this.siteService.apiDomain() + '/api/token?token=' + localStorage.getItem('JWToken'), {})
.map((response: any) => {
let data = response.json();
return {
token: data.token,
permissions: data.permissions,
user: data.user,
};
})
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedRequest = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken'))
});
return next.handle(clonedRequest).catch((res) => {
if (res.status === 401 || res.status === 403) {
return this.refreshToken().flatMap((data) => {
if (data.token !== '') {
localStorage.setItem('currentUser', JSON.stringify(data.user));
localStorage.setItem('currentUserPermissions', JSON.stringify(data.permissions));
localStorage.setItem('JWToken', data.token);
} else {
localStorage.removeItem('currentUser');
localStorage.removeItem('currentUserPermissions');
localStorage.removeItem('JWToken');
this.router.navigate(['./auth/login']);
return Observable.throw(res);
}
const clonedRequestRepeat = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken'))
});
return next.handle(clonedRequestRepeat);
})
} else {
return Observable.throw(res);
}
});
}
}
对于那些将这个拦截器用于他们的项目但与当前问题无关的人来说,另一件事是将头设置为刷新令牌响应至少几秒钟。
->标头('Cache-Control','public,max-age = 45')
-> header('Expires',date('D,d MYH:i:s',time()+ 45)。'GMT');
这很有趣,
我认为拥有像AuthService这样的服务是一种常见的用例,该服务可以管理令牌的可访问性并执行登录和刷新请求。
所以基本上这是场景。 我们有AuthService,它依赖于HttpClient(显然)。 它有几种方法:getAccessToken,getRefreshToken,login,refreshSession和logout。
另外,我们还需要有AuthInterceptor,如果令牌存在,它将向请求添加令牌。 显然,这取决于AuthService,因为服务可以访问令牌。
所以这是问题所在:HttpClient依赖于HTTP_INTERCEPTORS(基本上是AuthInterceptor),AuthInterceptor依赖于AuthService,而AuthService依赖于HttpClient。 和循环错误。
因此,在当前实现中不可能使用这种通用用例。
但是,为什么角度小组无法以第一个角度拦截器的工作方式类似的方式实现拦截器? 例如,具有额外的商店服务,我们可以使用该服务来注册新的拦截器,例如httpInterceptors.push(interceptor)
?
我写了一个类似的库ng4-http ,它对第一个angular实现了类似的方法。 它使用附加服务来存储注册的拦截器。 在这种情况下,我们可以在其中注入任何类型的服务并正确使用它们。 当前库使用旧的http模块。
所以问题是,我错了吗? 如果是,那在哪里?
我的代码与此处发布的示例非常相似,并且遇到了相同的问题。 实际上,这似乎非常类似于@cyrilletuzi试图做的事情,我只是想获取我在AuthService中设置的令牌属性的值,我不会尝试以其他任何方式使用它。 只需在拦截器中访问它即可。 将服务添加到拦截器的构造函数中后,会得到周期性依赖项错误,基本上是@mixalistzikas所得到的,直到消息为止,同时尝试提供的@perusopersonale所遇到的相同问题,即也存在问题大量递归,应用崩溃。
最终,我决定直接从我存储该令牌的会话存储中直接访问该令牌,而不是从服务中检索该令牌,但是似乎似乎没有任何实际的循环依赖性时就出现了此问题。 我描述的变通办法现在对我而言有效,但是肯定在很多情况下您需要在某处访问服务,而这种方式似乎是不可能的
这个示例也对我有用,但是我担心,因为将来可能不建议使用http,并且对于应用程序为同一事物注入2个不同的库也不利。
如果有人找到解决方案,请回答...
作为一种临时解决方案,可以将Auth令牌作为静态属性存储在AuthService类上。 然后,您将不需要实例化它。
您也可以尝试使用一些技巧来使服务无效,例如:
constructor(private injector: Injector) {
setTimeout(() => {
this.authService = this.injector.get(AuthService);
})
}
这样,您将不会获得最大调用堆栈超出错误。
我解决了简单的问题,不是在构造函数中设置authService而是设置了拦截函数。
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Get the auth header from the service.
const auth = this.inj.get(AuthenticationService);
const authToken = auth.getAuthorizationToken();
...
}
完美@perusopersonale .....您解决了我的问题...
这是具有某些记录功能的最终拦截器,并保持加载程序动画的加载状态
import {Injectable, Injector} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor, HttpResponse} from "@angular/common/http";
import {HttpRequest} from "@angular/common/http";
import {Observable} from "rxjs/Observable";
import {SiteService} from "../services/site.service";
import {Router} from "@angular/router";
import {LoadingService} from "../../components/loading/loading.service";
import {AuthenticationService} from "../services/authentication.service";
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private router: Router,
private siteService: SiteService,
private loadingService: LoadingService,
private injector: Injector) {
}
private fixUrl(url: string) {
if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0)
return url;
else
return this.siteService.apiDomain() + url;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedRequest = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken')),
url: this.fixUrl(req.url)
});
let authenticationService = this.injector.get(AuthenticationService);
this.loadingService.start();
const started = Date.now();
return next.handle(clonedRequest)
.do(event => {
if (event instanceof HttpResponse) {
const elapsed = Date.now() - started;
console.log('%c Request for ' + this.fixUrl(req.urlWithParams) + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
}
})
._finally(() => {
this.loadingService.stop();
})
.catch((res) => {
if (res.status === 401 || res.status === 403) {
this.loadingService.start();
return authenticationService.refreshToken().flatMap((data: any) => {
this.loadingService.stop();
if (data.token !== '') {
localStorage.setItem('currentUser', JSON.stringify(data.user));
localStorage.setItem('currentUserPermissions', JSON.stringify(data.permissions));
localStorage.setItem('JWToken', data.token);
} else {
localStorage.removeItem('currentUser');
localStorage.removeItem('currentUserPermissions');
localStorage.removeItem('JWToken');
this.router.navigate(['./auth/login']);
return Observable.throw(res);
}
let clonedRequestRepeat = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + localStorage.getItem('JWToken')),
url: this.fixUrl(req.url)
});
return next.handle(clonedRequestRepeat).do(event => {
if (event instanceof HttpResponse) {
const elapsed = Date.now() - started;
console.log('%c Request for ' + req.urlWithParams + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow');
}
});
})
} else {
return Observable.throw(res);
}
});
}
}
别忘了...
对于那些将这个拦截器用于他们的项目的人来说,另一重要的事情是将头设置为刷新令牌响应至少几秒钟。
->标头('Cache-Control','public,max-age = 45')
-> header('Expires',date('D,d MYH:i:s',time()+ 45)。'GMT');
也...
这是我的loadingService
```从'@ angular / core'导入{可注射};
@Injectable()
导出类LoadingService {
public count = 0;
constructor() { }
start(): void {
this.count++;
}
stop(): void {
this.count--;
}
}
```
只是提醒一下,如果有人不知道。 仅当主令牌未过期时才可以检索RefreshToken。 您可以调用令牌端点并发布刷新令牌以获取新的accessToken(例如,主令牌os过期前1小时)。问题如下。 在这种情况下,我们的方法getAccessToken()将返回可观察到的结果,因为它公开了http端,必要时使用刷新令牌来获取新令牌。
那么,您将如何处理以下问题:处理循环依赖性问题并将可观察到的注入注入拦截器?
@perusopersonale ,堆栈溢出错误表示在构建时已解决了循环依赖性,但在运行时仍然存在,您正在从拦截器中发出新请求,该请求也通过拦截器,并且将永远重复。
我建议使用next.handle()
进行从属请求,而不是尝试在那里保留HttpClient
。
我想注入服务的目的是从服务中获取存储的值,而不是发出请求,但是该服务确实利用HttpClient发出请求。 我认为这个问题不应该解决。
@nazarkryp在使用手动注入方法时,似乎正在获得该服务的另一个实例。 这可能是我为服务设置提供商的错(尽管我认为我只提供了一次)。 您看到的是同一件事吗?
我遇到了同样的问题,并且似乎上面没有提供令人满意的答案。 这不应该关闭。
同意@ will-copperleaf。 我仍然认为这通常是HttpClient
/ Interceptors的设计问题。 拦截器需要自己发出请求是一个很常见的情况(尤其是当它们用于身份验证时,我怀疑这里的大多数情况都是如此)。 临时注入Injector和请求服务似乎有些棘手。
获取Injector
实例对我不起作用。
错误:
AppComponent_Host.html:1 ERROR TypeError: Cannot set property 'injector' of undefined
at AuthHttp (authhttp.interceptor.ts:10)
at eval (module.ngfactory.js? [sm]:1)
at _callFactory (core.es5.js:9550)
at _createProviderInstance$1 (core.es5.js:9489)
at resolveNgModuleDep (core.es5.js:9471)
at _callFactory (core.es5.js:9550)
at _createProviderInstance$1 (core.es5.js:9489)
at resolveNgModuleDep (core.es5.js:9471)
at _createClass (core.es5.js:9514)
at _createProviderInstance$1 (core.es5.js:9486)
AuthHttp类:
1. import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
2. import { Injectable, Injector } from '@angular/core';
3. import { Observable } from 'rxjs/Rx';
4. import { AuthService } from './auth.service';
5.
6. @Injectable()
7. export class AuthHttp implements HttpInterceptor {
8. constructor(private injector: Injector) { }
9.
10. intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
11. return next.handle(req);
12. }
13. }
@ will-copperleaf您尝试了哪个示例?
例如,您应该能够执行此操作:
import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { AuthService } from '../shared/auth.service';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private authService: AuthService;
constructor(
private injector: Injector
) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.authService = this.injector.get(AuthService); // get it here within intercept
const authRequest = request.clone({
headers: request.headers.set('Authorization', this.authService.getHeaders())
});
return next.handle(authRequest);
}
}
因此,基本上,如果我的authService需要执行http请求以同时检索令牌和刷新令牌,然后将它们存储在localStorage中,您将如何实现呢?
我在上面的示例中看到,intercept方法中的人员从authService添加标头,或者直接从localStorage添加令牌(这意味着您已经有一个令牌)。
如果我的getHeaders()方法是异步的怎么办?
据我了解,没有办法从拦截器中排除特定请求,以使它们不会被拦截,也没有办法为特定组件或模块加载拦截器(必须在应用程序模块中定义) 。
我在auth.interceptor.ts
文件中使用此解决方案:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.includes('/token')) {
return next.handle(req);
}
this.tokenService = this.injector.get(TokenService);
...
}
我做了@twoheaded所做的事情,但是由于某种原因,它对我不起作用。 我的TokenService传递HttpClient作为其构造函数内部的依赖项之一。
我使TokenService依赖于Http。 那是唯一对我有用的解决方案,尽管有点笨拙。
我感到整个Interceptors概念目前还不能真正投入生产。
@twoheaded的解决方案适用于我,并且我确实使用HttpClient。
同样的问题,继续受到关注
以下带有@twoheaded解决方案的设置对我return next.handle(req)
可以提供帮助的地方。 除非您返回,否则整个过程将无限循环。 这就是@alxhub在上面也建议的内容。
import { AuthService } from './auth.service';
import { Injectable, Injector } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private authService: AuthService;
constructor(private inj: Injector) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const headers = {'Content-Type': 'application/json'};
if (req.url.includes('/token-refresh')) {
console.log("Refresh token request");
return next.handle(req);
}
this.authService = this.inj.get(AuthService);
// Example of the token-refresh call
if (this.authService.getToken()) {
this.authService.refreshToken().subscribe(
res => console.log(res)
)
}
const copiedReq = req.clone({setHeaders: headers});
console.log("Last");
return next.handle(copiedReq);
}
}
值得注意的是,如果您需要从拦截器发送http请求以获取一些数据(即重新验证逻辑),而又不想使用裸xhr / fetch,则可以始终使用类接口HttpBackend来省略拦截器并以这种方式发送请求。 另外,它在模拟后端进行测试时也可以使用:smiley:
import { Inject, Injectable, InjectionToken } from '@angular/core';
import { HttpBackend } from '@angular/common/http';
import {
HttpInterceptor,
HttpEvent,
HttpHandler,
HttpRequest,
HttpEventType,
HttpErrorResponse,
} from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/retryWhen';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import 'rxjs/add/observable/of';
export const MY_REQUEST = new InjectionToken<HttpRequest<any>>('MY_REQUEST');
@Injectable()
export class MyInterceptor implements HttpInterceptor {
constructor(
@Inject(MY_REQUEST) private myRequest: HttpRequest<any>,
private backend: HttpBackend
) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).retryWhen(errors => {
return errors.mergeMap((error: HttpEvent<any>) => {
if (error instanceof HttpErrorResponse && error.status === 401) {
return (
this.backend
.handle(this.myRequest)
// Get response.
.filter(event => event.type === HttpEventType.Response)
// Retry on success.
.mergeMap(() => Observable.of(null))
// Stop and throw on error.
.catch(() => Observable.throw(error))
);
}
// Throw error otherwise.
return Observable.throw(error);
});
});
}
}
在某些情况下,对于第三方库,使用HttpHandler
可能会有问题,因为用户可以添加更多拦截器,从而可以修改您的请求。
提供程序解析错误:无法实例化循环依赖! InjectionToken_HTTP_INTERCEPTORS(“ [ERROR->]”):在./ AppModule @ -1中的NgModule AppModule中:-1
当使用具有httpclient的其他服务时。
我刚试过角^ 5.0.0-rc.2,问题仍然存在:(((((((((
@perusopersonale完美地工作。 直接从注入器获取身份验证服务是一个不错的选择。
我认为HttpClient
应该可以选择直接调用HttpBackend
,而无需任何拦截器。
作为additioanl功能,它可以选择使用自定义拦截器。
你怎么看待这件事?
@vadjs
我认为HttpClient应该可以选择直接调用HttpBackend,而无需任何拦截器。
它现在可以执行此操作-注入HttpBackend
。 或者,它可以使用next: HttpHandler
进行呼叫,而无需通过链中较早的拦截器。
@alxhub(如果已经可以),也许应该将其记录下来。
同时,我已经实现了拦截器,以使用“ fetch”刷新我的authToken ...
@bogomips https://github.com/angular/angular/issues/18224#issuecomment -334813773
在构造器中注入路由器存在相同的问题:
@Injectable()
导出类ForbiddenInterceptor实现HttpInterceptor {
构造函数(专用路由器:路由器){
}
拦截(要求:HttpRequest
返回next.handle(req).do(event => {
console.log(“拦截器被调用”);
},err => {
console.log(“拦截器调用错误”);
});
}
}
无法实例化循环依赖! ApplicationRef(“ [ERROR->]”):在./ AppModule @ -1中的NgModule AppModule中:-1
需要路由器重定向以登录令牌到期(HTTP 403)。 手动注入路由器,例如injector.get(Router)
不起作用(返回未定义)
好了,通过注入注入器并添加get方法来修复它:
公用获取router():路由器{
返回this.injector.get(Router);
}
不幸的是,在Angular 4.3.0
之前,无法在intercept
方法内手动使用Injector
:
ERROR Error: Uncaught (in promise): RangeError: Maximum call stack size exceeded
但是使用rxjs
还有一种解决方法:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return Observable.create((observer: any) => {
setTimeout(() => {
const authService = this.injector.get(AuthenticationService)
observer.next(authService.getAuthorizationHeader())
observer.complete()
})
})
.mergeMap((Authorization: string) => {
let authReq = req
if (Authorization) {
authReq = req.clone({
setHeaders: {
Authorization
}
})
}
return next.handle(authReq)
})
}
@olegdunkan “在Angular 4.3.0
”? 我正在运行4.4.6
,但遇到了同样的问题。 我现在使用@perusopersonale的解决方法。
我在主模块中添加了{ deps:Http ],它对我
提供者:[
{
提供:HTTP_INTERCEPTORS,
useClass:AuthTokenInterceptor,
多:是的,
部门:[Http]
}],
它完美地工作在@perusopersonale上。 太感谢了 !
我已经尝试过@perusopersonale的解决方案,但是它不起作用。 我收到Maximum call stack size exceeded
错误。 我正在运行5.0.0
。 还有其他人可以在版本5中使用吗?
@ vlad-ursan确保您在拦截器中注入了AuthService,并在AuthService中手动注入了HttpClient。 还要仔细检查您使用的是HttpClient而不是不推荐使用的Http对象。 由于这两个原因,它将在5.0.0中失败。
Angular团队实际上是在寻求解决此问题的方法,以便人们不必将某些东西注入到其他东西中,而通过其他东西注入这些东西来规避此问题,或者它是大量积压的东西的底部可能永远不会被关注,因此我们可以永远依靠我们自己的解决方案吗?
如果第二件事是正确的,那么文档中的注释可能会有所帮助,该注释中的注释说明拦截器不应接受任何依赖于HttpClient的异步依赖项。
@ vlad-ursan您的意思是您遇到了超出最大调用堆栈大小的错误,尝试这样做吗?
@ rbj325我实际上在使用注入了使用`HttpClient的AuthService的另一个服务(ApiService)。 我不确定我手动注入它是什么意思。
@luillyfe在我的拦截器中,我使用的是完全相同的代码。
@mixalistzikas我尝试使用您的拦截器,实际上我一直在使用类似的拦截器,但是如果该路线有解决方案,则会出现问题。 你能建议我如何解决这个问题吗? :(您的拦截器也不会处理解析程序链中的第二个请求。https://stackoverflow.com/questions/47751566/angular-4-jwt-http-interceptor-doesnt-work-on-chain-requests可以帮我,我给你买啤酒))))花了太多时间
大家好,我提出了使用刷新令牌的解决方案,并将其发布在这里: https :
任何反馈都将受到欢迎。
##我也是
当我使用带有自定义标头的http时,我的AuthService可以工作。 但是当我添加HttpClient时,它不起作用,并给出相同的问题。
问:我是否必须将响应和标头从@ angular / http更改为任何其他库或需要进行任何其他更改?
第二名
与服务器其余服务交互时,是否无需映射到json,observable等? 想知道....
只需添加一条注释,说明这仍是5.1.1版的问题
我已经通过在AuthProvider和AuthTokenProvider中拆分AuthProvider解决了我们应用程序上的这个问题。
AuthTokenProvider的唯一作用是从存储中获取(或不获取)令牌。
拦截器使用AuthTokenProvider,它不使用HTTP,因此不会创建循环依赖项。
AuthProvider还使用AuthTokenProvider。
@Injectable()
export class AuthTokenProvider {
private tokenSubject: ReplaySubject<AuthToken> = new ReplaySubject(1);
private fetchTokenObservable: Observable<AuthToken>;
readonly TOKEN_STORAGE_KEY = 'auth.token';
constructor(
public storage: StorageProvider,
) {
this.fetchToken().subscribe(() => {});
}
/**
* <strong i="9">@returns</strong> {Observable<AuthToken>}
*/
observeToken(): Observable<AuthToken> {
return this.tokenSubject.asObservable();
}
/**
* Return an observable that resolves when authProvider
* is ready (token loaded from storage).
* <strong i="10">@returns</strong> {Observable<AuthToken>}
*/
observeReady(): Observable<AuthToken> {
return this.fetchTokenObservable;
}
/**
* <strong i="11">@param</strong> {AuthToken} token
*/
public setToken(token: AuthToken): Observable<any> {
return this.storage.set(this.TOKEN_STORAGE_KEY, token).map(() => {
this.tokenSubject.next(token);
return token;
});
}
/**
* Logout
* <strong i="12">@returns</strong> {Promise<any>}
*/
public removeToken(): Observable<any> {
return this.storage.remove(this.TOKEN_STORAGE_KEY).map(() => {
this.tokenSubject.next(null);
});
}
/**
* Fetch token from storage if available
*/
private fetchToken(): Observable<AuthToken> {
// This avoids multiple unnecessary storage calls
if (!this.fetchTokenObservable) {
this.fetchTokenObservable = this.storage.get(this.TOKEN_STORAGE_KEY).map((token) => {
this.tokenSubject.next(token);
return token;
});
return this.fetchTokenObservable;
}
}
}
嗨,我这样工作
`出口类SecurityFactory实现HttpInterceptor {
私人authService:AuthService;
构造函数(@Inject(forwardRef(()=> Injector))私有注入器:Injector){
}
拦截(要求:HttpRequest
this.authService = this.injector.get(AuthService);
返回next.handle(req).do(event => {
if(event ['status'] === 401){
this.authService.logout('expired');
}
});
}
}
导出函数tokenGetter(){
返回sessionStorage.getItem('token');
}
@NgModule({
进口:[
JwtModule.forRoot({
配置:{
tokenGetter:tokenGetter,
skipWhenExpired:true,
whitelistedDomains:['localhost:4200']
}
})
],
提供者:[
{提供:HTTP_INTERCEPTORS,useClass:SecurityFactory,多:true}
]
})
导出类AuthModule {
}`
对我来说,它就像这样。
this.injector.get(AuthService).logout();
嘿,也许我错过了一些事情,但我不建议您使用injector.get
因为这种方法会引入服务位置。
这使得拦截器更难测试。
现在,我有一个使用@angular/http
。 该服务由我的拦截器使用。
解决此问题后,我可以轻松切换到新的HttpClient
。
我通过将AuthService重构为独立于HttpClient的帮助程序服务(AuthHelper:仅与在本地存储中获取/设置令牌以及缓存失败的请求有关的类)和原始AuthService来解决了类似于@ iget-master的问题,现在只包含依赖于HttpClient的函数。
原始的依赖图:
HttpInterceptors --> AuthService --> HttpClient --> HttpInterceptors
固定依赖关系图:
HttpInterceptors --> AuthHelper <-- AuthService --> HttpClient -->HttpInterceptors
这不仅解决了循环依赖性,而且通过更好地分离关注点使代码更整洁。 AuthService既关注本地存储令牌的实现,也关注来自应用程序服务器的请求/处理令牌的实现。 现在只执行后者,并将前者的职责委托给助手类。
这不能解决想要在拦截器函数中发出http请求的用例(请参阅@serhiisol的注释),但这似乎是一个非常危险的用例。 当您的拦截器拦截其自己的请求时,您期望发生什么???
到目前为止,我以
我遇到此错误是因为令牌刷新请求再次被拦截。 来自内部拦截器的Http调用将再次触发拦截器,这将导致另一个HTTP调用,这将再次触发拦截器....直到堆栈溢出。
@alxhub发布了一个很好的解决方案,当您需要从内部发出HTTP请求时,可以防止递归调用拦截器
在您的从属服务调用中,注入HttpBackend并使用它而不是HttpClient。 根据文档
拦截器位于HttpClient接口和HttpBackend之间。 注入后,HttpBackend直接将请求调度到后端,而无需通过拦截器链。
我看不到该解决方案无法使用的用例,因此我认为,如果Http Interceptors上的文档已更新为指出HttpBackend
用法以在这种情况下绕过它们,则可以解决此问题。
你好
我对Angular有点陌生,所以请忍受我:)
升级到Angular 5.2.0后,我得到了循环引用问题,并开始遵循关于不通过构造函数而是使用Injector注入HttpClient的建议。
我有一个使用msal的javascript身份验证服务(我使用Azure B2C),该身份验证服务用于通过构造函数注入“ ConfigService”以通过Http get获得B2C策略信息等。
我也有一个拦截器,它在返回B2CCallback时设置标头“ bearer”令牌
我的配置服务通过构造函数注入HttpClient,并在方法中进行调用以从后端api加载数据。
我也将AuthenticationService中的ConfigService注入更改为也使用注入器,但是现在遇到了2个问题。
1)拦截器对我的Injector不太满意,出现错误:TypeError:this.inj.get不是一个函数。
@Injectable()
export class AuthenticationHttpInterceptor implements HttpInterceptor {
private authenticationService: AuthenticationService;
constructor(private inj: Injector) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
this.authenticationService = this.inj.get(AuthenticationService);
return Observable.fromPromise(this.authenticationService.getAuthenticationToken())
.switchMap(token => {
req = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(req);
});
}
}
2)我的配置服务不再通过AuthenticationService返回所有未定义类型的调用。 具体在这里:
`构造函数(专用注入器:Injector){
this.configService = inj.get(ConfigService);
this.b2CConfig = this.configService.getB2CConfig;
this.authority = `https://login.microsoftonline.com/tfp/${this.b2CConfig.tenant}/${this.b2CConfig.signUpSignInPolicy}`;`
任何建议将被认真考虑。
谢谢
麦克风
对于那些认为可以切换到新的HttpClient并将对其进行修复的人来说,我已经切换了几个月,但是在这里您将遇到问题:
我确实建议人们切换到新的HttpClient ...它将为您提供许多不错的功能。 但是我在整个应用程序中都有一个错误处理程序(ErrorHandler接口),当我尝试将HttpClient注入我的ErrorHandler时遇到了同样的错误:
constructor(http: HttpClient) {
}
由于ErrorHandler实际上也拦截了来自Http的错误,因此这就是问题所在。因此,jector.get方法是解决此问题的唯一方法...并且它是我使用Injector的大型应用程序中的唯一位置。 一旦找到更好的方法或有此修复程序,我想将其删除。
@Injectable()
export class ErrorHandlerService implements ErrorHandler {
private http: HttpClient;
constructor(private injector: Injector) {
// Cyclic reference if I inject httpClient through constructor
setTimeout(() => {
this.http = this.injector.get(HttpClient);
})
}
handleError(error: any) {
console.log('Client Error:', error);
}
}
我看到这在5.2.3发行版中已关闭(在变更日志中已提及)。
但这仍然行不通。
我刚刚更新到5.2.3(先将node_modules
和package-lock.json
替换node_modules
裸体)
然后,我替换了拦截器中的旧代码:
constructor(
private injector: Injector
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const authService = this.injector.get(AuthService);
const token = authService.getToken();
const reqCopy = req.clone({
headers: req.headers.append('Authorization', `Bearer ${token}`)
});
return next.handle(reqCopy);
}
应该是什么:
constructor(
private authService: AuthService
) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const token = this.authService.getToken();
const reqCopy = req.clone({
headers: req.headers.append('Authorization', `Bearer ${token}`)
});
return next.handle(reqCopy);
}
当我尝试使用--prod
标志进行构建时,出现循环依赖项错误。
@MrCroft我刚刚更新到角度5.2.3,它工作正常。
yarn upgrade --scope @angular
可能还有其他原因导致您遇到的问题。
当我尝试使用--prod标志进行构建时,还会收到循环依赖项错误。
使用angular 5.2.2时,我遇到了相同的循环错误。
在Visual Studio Code中运行npm update
,并将其更新为5.2.4。 没有更多的错误。 似乎Angular团队使用最新版本解决了该问题。
@jzahoor我想了解更多有关此修复程序的信息。
我的工作(很多个月前)是在拦截器内部使用“获取”,以便完全绕开有角度的...
谢谢
@melicerte很抱歉
主题:事实证明,如果您有通用应用程序,仍然会收到错误消息。 当然,直到我决定从头开始创建新应用程序时,我才注意到这一点😄
我什至不必构建通用应用程序,它只需要存在即可。
简单地运行ng serve --prod
或ng build --prod
会引发错误(这将构建apps数组中的第一个应用程序,在我的情况下是客户端)。
@alxhub我在这里做了仓库来重现它。
简单地运行npm install
和ng serve --prod
(或ng build --prod
来构建客户端应用程序)将产生错误。
奇怪的是,即使您只是在构建客户端应用程序,它也会提到app.server.module
。
不知道这是CLI而不是Angular的问题吗?
角CLI: 1.6.8
角度: 5.2.4
节点:8.9.4
操作系统:win32 x64
@marcincichocki :有兴趣知道如何将MY_REQUEST
发送到您的拦截器。 我喜欢您的解决方案,仅绕过某些诸如auth之类的拦截器,只是不确定如何将您的示例用于现有的拦截器。
@jdhines在该特定示例中,您将需要通过依赖注入提供MY_REQUEST
令牌:
@NgModule({
providers: [
{
provide: MY_REQUEST,
useValue: new HttpRequest() // set your HttpRequest here, or use factory function if you need DI
}
]
})
但是,如果您需要高度可移植且可重用的代码(例如,您有多个使用不同后端的应用程序),则这很好。 我不是,只需将HttpRequest
为拦截器本身的属性即可:
@Injectable()
export class MyInterceptor {
myRequest = new HttpRequest()
}
@marcincichocki :谢谢(并myRequest
是我的身份验证服务中http.get('userInfo')
的后端替代者吗? 我认为您实际上会将某些东西传递给new HttpRequest()
吗? 如果是这样,该怎么办?
@jdhines是的,您必须传递描述您的请求的参数。 该类几乎没有重载
最常见的情况是,您将使用以下内容:
const myRequest = new HttpRequest('POST', 'url/here', payload, options) // post
const myRequest2 = new HttpRequest('GET', 'url/here', options) // get
如果您在Ionic上遇到此问题,您可能会发现自己已超出最大呼叫堆栈大小。 只需在platform.ready()。then(()=> [...])内进行inj.get(...); 在拦截器里面!
我陷入了类似的周期性依赖关系,并采取了手动注入服务的解决方法https://stackoverflow.com/questions/49240232/getting-a-cyclic-dependency-error
我仍然有这个问题,有人播过吗?
我正在尝试创建一个拦截器,以便在标题中包含授权。
错误:ERROR错误:未捕获(承诺):RangeError:超出最大调用堆栈大小
我的代码:
```
拦截(请求:HttpRequest
让tokenProvider =
return Observable.create((observer: any) => {
setTimeout(() => {
observer.next(tokenProvider.getAsyncToken());
observer.complete();
});
}).mergeMap((token) => {
if(token){
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(request);
});
}
我的TokenProvider:
async getAsyncToken():承诺
返回新的Promise((resolve)=> {
让令牌= localStorage.getItem('token');
让时间= Math.round(+ new Date()/ 1000);
if(!token){
这.http.post${this.urlApi}/${this.urlAuth}
,{timestamp:time})。subscribe(data => {
resolve(data.token);
},err => {
console.log(err);
});
}其他{
解决(令牌);
}
});
}
@thiagodamico我现在
我也是。
我不确定为什么没有其他人指出这一点,但显然通过引入HttpInterceptingHandler
来解决Angular 6的问题,它本身会延迟加载拦截器并避免循环依赖。
HttpInterceptingHandler用法的任何示例吗?
@ ronen1malka您不应使用此类,因为它不是公共API的一部分。 只需升级到6.0.2版本并继续使用HttpInterceptor类。
@simeyla指出了有关HttpInterceptorHandler的观点,好一点!
但是我想知道,为什么Angular团队决定创建另一个具体的类来解决循环依赖问题,而不是仅仅添加带有HttpClient签名的接口
坏的部分是无法正常工作升级它! NPM上有很多污垢
如果您在Ionic上遇到此问题,您可能会发现自己已超出最大呼叫堆栈大小。 只需在platform.ready()。then(()=> [...])内进行inj.get(...); 在拦截器里面!
适用于离子3和角5
在angular 6上遇到了同样的问题,但使用此方法解决了https://github.com/angular/angular/issues/18224#issuecomment -329939990
@Toxicable谢谢您,您的解决方案对我
我在angular 5.0.x下遇到了同样的问题,但是似乎在5.2.11下已经解决了。 @Toxicable的Sulotion在5.0.x和5.2.11下都可以工作
这也适用:
import { Injectable } from '@angular/core';
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { AuthService } from '../auth/auth.service';
@Injectable({
providedIn: 'root'
})
export class JwtInterceptorService implements HttpInterceptor {
constructor(private authService: AuthService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Add authorization header with bearer token if available.
const token: string = this.authService.tokenValue;
if (token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(request).pipe(catchError((error: HttpErrorResponse) => {
// Auto logout if 401 response returned from api
if (error.status === 401) {
this.authService.unAuthUser();
}
return throwError(error);
}));
}
}
最有用的评论
我解决了简单的问题,不是在构造函数中设置authService而是设置了拦截函数。