我有一个angular应用程序(和一个nestjs后端),我试图处理用户登录与keycloak的帮助。正如这个充满流行语的问题标题所述,我有点"奇怪"。架构。
- 我想在angular (works) 的帮助下创建一个
- 我认为我不需要自己的模块为我构建的组件,所以它是一个
standalone component
(工程) - 我使用
keycloak-angular
在主。(工作,我得到一个令牌,用户名,角色,…) - 我将
HttpClient
用于该组件(自定义元素)对其后端的请求(请求确实有效,但未添加承载令牌)
custom element
根据keycloak-angular的自述:
"默认情况下,所有HttpClient请求都会添加Authorizationheader"
同样,当我在keycloakService.init
方法中设置的KeycloakOptions
中添加shouldAddToken
函数时,该函数永远不会被调用。
我想,也许组件中的HttpClient是另一个实例,而不是main中的实例。但我检查过了,是同一个实例。我设置了(httpService as any).mytest = 1;
在主。
你知道我会错过什么吗?
main.ts中的相关代码
(async () => {
// inspired by https://www.angulararchitects.io/aktuelles/angular-elements-web-components-with-standalone-components/
const app = await createApplication({
providers: [
provideHttpClient(),
KeycloakService,
{
// I guess this should not be necessary, but I tryed anyway
provide: HTTP_INTERCEPTORS,
useClass: KeycloakBearerInterceptor,
multi: true,
deps: [KeycloakService]
},
],
});
try {
await initializeKeycloak(app);
} catch (err: any) {
console.error('Seems like there was a problem with the login', err);
return;
}
// register my root component as CustomElement
customElements.define(
'my-component',
createCustomElement(AppComponent, { injector: app.injector })
);
})();
async function initializeKeycloak(app: ApplicationRef) {
const keycloakService = app.injector.get(KeycloakService);
return keycloakService.init({
// keycloak address and client data coming from environment file
config: environment.keycloak.config,
/* without this getUsername() returns the Error "User not logged in or user profile was not loaded." */
loadUserProfileAtStartUp: true,
initOptions: {
enableLogging: true,
onLoad: 'login-required',
// someone on the web had the same problem (no auth header) and fixed it with this (not sure if this is relevant, it does not help in my case)
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html',
},
shouldAddToken: (request) => {
// is never logged
console.log('XXXXXXXXXXX addToken?', request);
return true;
}
}).then(async loggedIn => {
if (loggedIn) {
// returns sane data
console.log("loggedIn: ", {
loggedIn,
username: keycloakService.getUsername(),
roles: keycloakService.getUserRoles().join(', '),
jwt: jwt_decode(await keycloakService.getToken())
});
// returns true
console.log("keycloakService.enableBearerInterceptor", keycloakService.enableBearerInterceptor);
} else {
console.log(`keycloak.init returned ` + loggedIn);
}
return loggedIn;
});
}
更新
有两件事我不确定,这可能导致Keycloak拦截器不添加承载令牌:
如果域名不同,可能没有添加它们?
- 我的keycloak是在http://test.local:8080
- 我的angular app在http://localhost:4200
- 我的后端应用程序在http://localhost:3333
也许HttpInterceptor需要被添加到每个模块做http请求?!已经尝试将以下内容添加到我的AppModule导入的一个模块中(没有成功),该模块通过HttpClient进行请求。
{
provide: HTTP_INTERCEPTORS,
useClass: KeycloakBearerInterceptor,
multi: true,
...
HttpClient在任何地方都是相同的实例(如前所述)。
问题解决
因为我使用的是一个独立的组件,所以我不能把HttpClientModule添加到AppModule的imports数组中。新方法是方法吗?provideHttpClient
.
而不是像这样添加结构到您的提供者数组
{
// I guess this should not be necessary, but I tryed anyway
provide: HTTP_INTERCEPTORS,
useClass: KeycloakBearerInterceptor,
multi: true,
deps: [KeycloakService]
},
你可以添加新的"函数拦截器";到provideHttpClient
像这样
provideHttpClient(
withInterceptors([ requestInterceptor ])
)
其中requestInterceptor是一个简单的函数而不是一个服务
function requestInterceptor(
req: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> {
...
}
你也可以添加">遗留拦截器";像以前一样,但是你必须告诉新的API去搜索它们,并通过withInterceptorsFromDi()
将它们注入到你的提供商中。而这正是我的代码中所缺少的。
现在的样子
const app = await createApplication({
providers: [
...,
provideHttpClient(
withInterceptors([
// just testing the new way of intercepting
requestInterceptor
]),
withInterceptorsFromDi()
),
KeycloakAngularModule,
KeycloakService,
KeycloakBearerInterceptor,
{
provide: HTTP_INTERCEPTORS,
useClass: KeycloakBearerInterceptor,
multi: true,
deps: [KeycloakService]
},
{
// testing own legacy interceptor
provide: HTTP_INTERCEPTORS,
useClass: LoggingInterceptor,
multi: true
},
],
});
所有3个拦截器都有效。
这篇文章是我穿着闪亮盔甲的骑士