Когда я делаю запрос авторизации к своему API (войдите, обновите токен доступа), я хотел бы передать результат нескольким наблюдателям.
Наблюдатели - это либо компоненты (я показываю кнопку выхода из системы), например), или перехватчик http (при повторной попытке запроса после 401).
Мой текущий метод состоит в том, чтобы выдавать обновленные токены от субъектов в моей службе аутентификации. Компоненты наблюдают за субъектами, а мой компонент входа в систему и http-перехватчик делают запросы на вход в систему и обновление токена.
Проблема в том, что обновление токена находится в середине наблюдаемого потока внутри http-перехватчика, которыйУмирает, когда я вызываю subject.next (токен) из моей службы аутентификации.
Компонент заголовка:
export class HeaderComponent {
loggedIn: boolean;
ngOnInit() {
this.authenticationService
.isLoggedIn()
.pipe(takeUntil(this.destroyed))
.subscribe((loggedIn: boolean) => {
this.loggedIn = loggedIn;
this.detectChanges();
});
}
}
Компонент входа:
export class LoginComponent {
onFormSubmit(credentials): void {
this.authenticationService
.login(credentials)
.subscribe((token: string) => {
if (token) {
// navigate away from login page
}
});
}
}
HttpInterceptor:
export class HttpAuthInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
request = this.setAuthenticationHeader(request);
return next.handle(request)
.pipe(
catchError((err: any) => {
// assume HttpErrorResponse for brevity
if (err.status !== 401) {
return throwError(err);
}
return this.authenticationService
.refreshToken()
.pipe(
// a token request has been made, but the stream has now died
tap(() => console.log('I am not called')),
switchMap(token => {
request = this.setAuthenticationHeader(request);
return next.handle(request);
}),
);
})
);
}
}
Упрощенная версия моей текущей службы аутентификации:
export class AuthenticationService {
constructor(private http: HttpClient) {
this.tokenSubject = new BehaviorSubject<string>(this.token);
}
private refreshToken: string; // from storage in reality
private token: string; // from storage in reality
private tokenSubject: Subject<string>;
isLoggedIn(): Observable<boolean> {
return this.tokenSubject
.pipe(
map(token => !!token)
);
}
login(credentials): Observable<string> {
return this.http
.post('https://myapi/token', mycredentials, myloginheaders)
.pipe(
map(response => {
this.setToken(response.access_token, response.refresh_token);
return this.token;
}),
catchError(err => {
this.setToken(null);
return of(this.token);
})
);
}
refreshToken(): Observable<string> {
return this.http
.post('https://myapi/token', mytoken, myrefreshheaders)
.pipe(
map(response => {
this.setToken(response.access_token, response.refresh_token);
return this.token;
}),
catchError(err => {
this.setToken(null);
return of(this.token);
})
);
}
refreshTokenAttempt1(): Observable<string> {
const request = this.http
.post('https://myapi/token', mytoken, myrefreshheaders)
.pipe(
map(response => {
this.setToken(response.access_token, response.refresh_token);
return this.token;
}),
catchError(err => {
this.setToken(null);
return of(this.token);
}),
multicast(this.tokenSubject)
);
// Add multicast into the pipeline and call connect.
// My guess is that calling connect from here AND subscribe from the client is not good
(request as ConnectableObservable<string>).connect();
return request;
}
refreshTokenAttempt2(): Observable<string> {
const request = this.http
.post('https://myapi/token', mytoken, myrefreshheaders)
.pipe(
map(response => {
this.setToken(response.access_token, response.refresh_token);
return this.token;
}),
catchError(err => {
this.setToken(null);
return of(this.token);
}),
multicast(this.tokenSubject),
// Add publish to the pipeline in an attempt to push the result into the token subject. Not working - because it's still cold?
publish()
);
return request;
}
private setToken(token: string, refreshToken: string): void {
this.refreshToken = refreshToken;
this.token = token;
// this seems to kill any observable stream that we are currently inside
this.tokenSubject.next(token);
}
}
Мне кажется, что мне нужно как-то передать результат запроса токена в мою тему токена в службе аутентификации.
Я думаю, что мне нужна многоадресная рассылка, но я не могу найти примеров трансляции результата одноразового наблюдаемого события (например, http-запроса) на субъектов с активными наблюдателями (например, моего токена).