Я впервые работаю с угловыми перехватчиками, и у меня почти есть то, что я хочу, но есть кое-что, что я не могу понять, даже после того, как некоторое время погуглил. Я храню токен обновления локально, а срок действия токена доступа истекает каждые 15 минут; Я хочу иметь возможность использовать токен обновления для автоматического обновления своего токена аутентификации по истечении срока его действия.
Моя первая попытка была такой:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.toLowerCase().includes('/auth')) {
// It's an auth request, don't get a token
return next.handle(req);
}
// Not an auth endpoint, should have a token
this.authService.GetCurrentToken().subscribe(token => {
// Make sure we got something
if (token == null || token === '') {
return next.handle(req);
}
// Have a token, add it
const request = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(request);
});
}
Похоже, это не сработало, и я не мог понять, почему (я новичок в Angular и довольно новичок в JS, так что извините, если это очевидно для других). Подозревая, я задавался вопросом, было ли это наблюдаемым, что все испортило, и ему не нравится ждать, пока наблюдаемое вернется, поэтому я попробовал это:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.toLowerCase().includes('/auth')) {
// It's an auth request, don't get a token
return next.handle(req);
}
const token = this.authService.GetAccessTokenWithoutRefresh();
const request = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
return next.handle(request);
}
И теперь это, кажется, работает! Это говорит о том, что я, возможно, был прав в своей догадке (или что это что-то еще внутри другого кода, которого я не вижу). В любом случае, работает хорошо, но это оставляет меня с вопросом, как обновить. Первоначальная причина, по которой я использовал наблюдаемую из службы аутентификации, заключалась в том, что ее нужно было обновить. В основном служба аутентификации будет смотреть на свой текущий токен и смотреть, истек ли срок его действия или нет. В противном случае он просто вернет of(token), но если срок его действия истек, он будет обращаться к серверу через http-сообщение, которое можно наблюдать, поэтому строка будет приходить всякий раз, когда сервер отвечает.
Поэтому я думаю, что мой вопрос двоякий:
РЕДАКТИРОВАТЬ
Вот логика в методе токена авторизации:
GetCurrentToken(): Observable<string> {
if (this.AccessToken == null) {
return null;
}
if (this.Expiry > new Date()) {
return of(this.AccessToken);
}
// Need to refresh
return this.RefreshToken().pipe(
map<LoginResult, string>(result => {
return result.Success ? result.AccessToken : null;
})
);
}
и метод обновления:
private RefreshToken(): Observable<LoginResult> {
const refreshToken = localStorage.getItem('rt');
if (refreshToken == null || refreshToken === '') {
const result = new LoginResult();
// Set other stuff on result object
return of(result);
}
const refresh = new RefreshTokenDto();
refresh.MachineId = 'WebPortal';
refresh.TokenId = refreshToken;
return this.http.post(ApiData.baseUrl + '/auth/refresh', refresh)
.pipe(
tap<AuthResultDto>(authObject => {
this.SetLocalData(authObject);
}),
map<AuthResultDto, LoginResult>(authObject => {
const result = new LoginResult();
// Set other stuff on the result object
return result;
}),
catchError(this.handleError<LoginResult>('Refresh'))
);
}
РЕДАКТИРОВАТЬ
Итак, с помощью ответа ниже, а также вопроса это вот что я придумал:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.toLowerCase().includes('/auth')) {
// It's an auth request, don't get a token
return next.handle(req.clone());
}
return this.authService.GetCurrentToken().pipe(
mergeMap((token: string) => {
if (token === null || token === '') {
throw new Error('Refresh failed to get token');
} else {
return next.handle(req.clone({setHeaders: {Authorization: `Bearer ${token}`}}));
}
}),
catchError((err: HttpErrorResponse) => {
if (err.status === 401) {
this.router.navigateByUrl('/login');
}
return throwError(err);
})
);
}
В общем, моя первая попытка была не так уж и далека, «секрет» заключался в том, чтобы использовать конвейер и карту слияния вместо того, чтобы пытаться подписаться.
В вашем первом фрагменте кода в случае запроса на авторизацию вы возвращаете HttpEvent вместо Observable<HttpEvent<any>>. Итак, попробуйте обернуть этот первый возврат в эмиттер of() (Learnrxjs.io/operators/creation/of.html), чтобы он выглядел как return of(next.handle(req)); (ТОЛЬКО В ЗАПРОСЕ AUTH)
@Jota.Toledo Хорошо, я думаю, это имеет смысл, поскольку наблюдаемая будет возвращаться и продолжаться в отдельное время, поэтому перехватчик не сможет сразу вернуть результат? Так что это должно быть довольно распространенной вещью... как тогда вы справляетесь с автоматическим обновлением токена? Я разместил код своих методов получения и обновления токена.
Однако запрос @Andriy Auth работает ... Глядя на код для «дескриптора», он также возвращает Observable<HttpEvent<any>>, так что не будет ли это действительным и предпочтительнее просто вернуть это? Из примеров, которые я нашел в Интернете до сих пор, они, похоже, не обертывают его таким образом и просто возвращают его напрямую, однако они также не обновляют токен автоматически...
@ Jota.Toledo спасибо за правки и направление, но не лучше ли оставить оригинал и дать ответ, чтобы его можно было отследить? Также в вашем новом методе GetCurrentToken он всегда либо возвращает текущий токен, либо обновляется; также возможно, что пользователь никогда не входил в систему и не имеет токена обновления (что было бы нулевым состоянием токена в исходном методе). Насколько я могу судить, ваш новый метод не справляется с этим. Наконец, почему вы изменили «handleError» с LoginResult на строку? Метод должен возвращать LoginResult...
Плохо, я откатил изменения :). Думаю, в моем подходе отсутствует и этот случай.





Вы можете попробовать следующий подход. Возможно, я преувеличил количество FP в нем:
export class AuthInterceptor {
ctor(private authService: AuthService){}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return of(req.url.toLowerCase().includes('/auth')).pipe(
mergeMap(isAuthRequest => !isAuthRequest
// Missing: handle error when accessing the access token
? this.authService.accessToken$.pipe(map(addAuthHeader(req)))
: of(req)
),
mergeMap(nextReq => next.handle(nextReq))
);
}
}
function addAuthHeader(req: HttpRequest<any>): (token:string)=> HttpRequest<any> {
return token => req.clone({setHeaders: {Authorization: `Bearer ${token}`}})
}
И служба авторизации:
export class AuthService {
ctor(private http: HttpClient){}
get accessToken$(): Observable<string> {
return of(this.AccessToken).pipe(
mergeMap(token => token === null
? throwError("Access token is missing")
: of(this.Expiry > new Date())
),
mergeMap(accessTokenValid => accessTokenValid
? of(this.AccessToken)
: this.refreshToken()
)
);
}
refreshToken(): Observable<string> {
return of(localStorage.getItem('rt')).pipe(
mergeMap(refreshToken => !refreshToken
? of(extractAccessTokenFromLogin(createLoginResult())
: this.requestAccessToken(this.createRefreshToken(refreshToken))
)
);
}
private requestAccessToken(refreshToken: RefreshTokenDto): Observable<string> {
return this.http.post<AuthResultDto>(ApiData.baseUrl + '/auth/refresh', refreshToken)
.pipe(
tap(auth => this.SetLocalData(auth )),
map(auth => this.mapAuthObjToLoginRes(auth)),
map(extractAccessTokenFromLogin)
catchError(this.handleError<string>('Refresh'))
)
}
private createRefreshToken(tokenId: string): RefreshTokenDto{...}
private createLoginRes(): LoginResult {...}
private mapAuthObjToLoginRes(val: AuthResultDto): LoginResult{...}
}
function extractAccessTokenFromLogin(login: LoginResult): string
=> login.Success ? login.AccessToken : null;
Хорошо, чтобы убедиться, что я понимаю... По сути, вместо подписки, как я, вы используете pipe и mergeMap, которые, по сути, дают наблюдаемые «вещи, которые нужно сделать», когда они возвращаются с результатом, вместо того, чтобы фактически подписываться и пытаться внести изменения и вернуться туда, это правильно? Тогда перехватчик может продолжать свою цепочку, не пытаясь чего-то ждать, но все же выполняет преобразования при вызове? Сейчас попробую это...
Так что извините за задержку по этому поводу. Я пошел дальше и отметил это как ответ, хотя это немного отличается от того, что я в итоге сделал, поскольку это помогло привести к ответу, и я считаю, что это та же идея...
У вас есть логика обновить, заключенная в функцию? Если да, не могли бы вы добавить подпись?