问题
I'm trying to make use of the angular-jwt but I get the follower cyclic dependency error:
Cannot instantiate cyclic dependency! InjectionToken_JWT_OPTIONS
at NgModuleProviderAnalyzer.parse (compiler.js:19550)
at NgModuleCompiler.compile (compiler.js:20139)
at JitCompiler._compileModule (compiler.js:34437)
at eval (compiler.js:34368)
at Object.then (compiler.js:474)
at JitCompiler._compileModuleAndComponents (compiler.js:34366)
at JitCompiler.compileModuleAsync (compiler.js:34260)
at CompilerImpl.compileModuleAsync (platform-browser-dynamic.js:239)
at PlatformRef.bootstrapModule (core.js:5567)
at eval (main.ts:13)
In my first demo Angular 5 application, here is my setup:
import {NgModule} from '@angular/core';
import {HttpClientModule} from '@angular/common/http';
import {JwtModule, JWT_OPTIONS} from '@auth0/angular-jwt';
import {KeycloakService} from './keycloak.service';
// See https://github.com/auth0/angular2-jwt
export function jwtOptionsFactory(keycloakService) {
return {
whitelistedDomains: ['localhost:3001'],
blacklistedRoutes: ['localhost:3001/auth/'],
tokenGetter: () => {
return keycloakService.getJwtTokenFromLocalStorage();
}
};
}
@NgModule({
imports: [
HttpClientModule,
JwtModule.forRoot({
jwtOptionsProvider: {
provide: JWT_OPTIONS,
useFactory: jwtOptionsFactory,
deps: [KeycloakService]
}
})
],
providers: [
KeycloakService
]
})
export class AuthModule {}
Having an empty deps: property removes the issue. Of course, no service being injected is no solution either.
And my service is:
import {Injectable} from '@angular/core';
import {environment} from '../environments/environment';
import {Observable} from 'rxjs';
import {JwtHelperService} from '@auth0/angular-jwt';
import {HttpClient, HttpHeaders} from '@angular/common/http';
declare let Keycloak: any;
const JWT_TOKEN_NAME: string = 'token';
@Injectable()
export class KeycloakService {
static auth: any = {};
constructor(private httpClient: HttpClient, private jwtHelperService: JwtHelperService) {}
static init(): Promise<any> {
const keycloakAuth: any = Keycloak({
url: environment.KEYCLOAK_URL,
realm: environment.KEYCLOAK_REALM,
clientId: environment.KEYCLOAK_CLIENTID,
'ssl-required': 'external',
'public-client': true
});
KeycloakService.auth.loggedIn = false;
return new Promise((resolve, reject) => {
keycloakAuth.init({onLoad: 'check-sso'})
.success(() => {
console.log('The keycloak client has been initiated successfully');
KeycloakService.auth.loggedIn = true;
KeycloakService.auth.authz = keycloakAuth;
KeycloakService.auth.logoutUrl = environment.KEYCLOAK_URL
+ '/realms/' + environment.KEYCLOAK_REALM + '/protocol/openid-connect/logout?redirect_uri='
+ document.baseURI;
resolve();
})
.error(() => {
reject();
});
});
}
static hasRole(role: string): boolean {
return KeycloakService.auth.authz.tokenParsed.realm_access.roles.indexOf(role) > -1;
}
static getUsername(): string {
return KeycloakService.auth.authz.tokenParsed.preferred_username;
}
static getFullName(): string {
return KeycloakService.auth.authz.tokenParsed.name;
}
public login(ussername: string, password: string): Observable<any> {
console.log('Sending the login credentials to obtain a token');
const credentials = {username: ussername, password: password};
const url: string = environment.KEYCLOAK_URL + '/realms/' + environment.KEYCLOAK_REALM
+ '/protocol/openid-connect/token/generate-token';
return this.httpClient.post(url, credentials);
}
public logout(): void {
KeycloakService.auth.loggedIn = false;
KeycloakService.auth.authz = null;
window.location.href = KeycloakService.auth.logoutUrl;
}
public getToken(): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (KeycloakService.auth.authz && KeycloakService.auth.authz.token) {
KeycloakService.auth.authz
.updateToken(5) // Refresh the token if it will expire in n seconds or less
.success(() => {
resolve(<string>KeycloakService.auth.authz.token);
})
.error(() => {
reject('Failed to refresh the auth token');
});
} else {
reject('The auth token could not be retrieved because the user was not logged in');
}
});
}
public isAuthenticated(): boolean {
const token = this.getJwtTokenFromLocalStorage();
return (token && !this.jwtHelperService.isTokenExpired(token));
}
public getTokenExpirationDate() {
const token = this.getJwtTokenFromLocalStorage();
return this.jwtHelperService.getTokenExpirationDate(token);
}
public getDecodedToken() {
const token = this.getJwtTokenFromLocalStorage();
return this.jwtHelperService.decodeToken(token);
}
public getJwtTokenFromLocalStorage(): string {
return localStorage.getItem(JWT_TOKEN_NAME);
}
public setJwtTokenToLocalStorage(token: string): void {
localStorage.setItem(JWT_TOKEN_NAME, token);
}
public getJwtTokenName(): string {
return JWT_TOKEN_NAME;
}
}
All my dependencies are:
"@angular/animations": "^5.2.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"@auth0/angular-jwt": "^1.0.0",
"angular-in-memory-web-api": "^0.5.3",
"core-js": "^2.4.1",
"jwt-decode": "^2.2.0",
"keycloak-js": "^3.4.3",
"npm": "^5.6.0",
"rxjs": "^5.5.6",
"zone.js": "^0.8.19"
UPDATE: I split the service into two services, one KeycloakService
dealing with the Keycloak
server and an AuthService
sort of encapsulating it, so as to have smaller sized services. The issue remained though, and I had to go for David's solution and the workaround he mentioned. And my new AuthService
has the following constructor:
@Injectable()
export class AuthService {
// constructor(private jwtHelperService: JwtHelperService) {} TODO
jwtHelperService: JwtHelperService;
constructor(private injector: Injector) {
let jwtHelperService = this.injector.get(JwtHelperService);
}
回答1:
As a workaround (it's not really ideal though), you could directly inject the injector in your KeycloakService
export class KeycloakService {
static auth: any = {};
constructor(private injector: Injector) {}
Then when you need access to HttpClient
or JwtHelperService
, then you can just call resolve in your service (not in the constructor !)
let httpClient = this.injector.get<HttpClient>(HttpClient);
Edit: From your comment on the question it looks like it's JwtHelperService
causing the issue, so you could only onject Injector
and HttpClient
in the service's constructor and resolve JwtHelperService
when needed
来源:https://stackoverflow.com/questions/49240232/getting-a-cyclic-dependency-error