@@ -0,0 +1,162 @@
import { Injectable , Injector } from '@angular/core' ;
import {
HttpInterceptor ,
HttpRequest ,
HttpHandler ,
HttpEvent
} from '@angular/common/http' ;
import { Observable } from 'rxjs/Observable' ;
import { BehaviorSubject } from 'rxjs/BehaviorSubject' ;
import { finalize } from 'rxjs/operators/finalize' ;
import { catchError } from 'rxjs/operators/catchError' ;
import { switchMap } from 'rxjs/operators/switchMap' ;
import { take } from 'rxjs/operators' ;
import { filter } from 'rxjs/operators' ;
import 'rxjs/add/observable/throw' ;
import { AuthService } from '@app/core/security/auth/auth.service' ;
/**
* Interceptador HTTP para adicionar o Token nas requisições ao serve
* e mapear os erros a fim de verificar erro de token expirado
* tentando renova-lo caso possivel, aceitando multiplas chamadas assincronas
* com o token expirado criando uma espece de "fila" até que a 1º chamada que
* solicitou o token seja resolvida.
*
* @author Filipe Mansano
* @version 1.0
* @since 2018-01-02
*/
@Injectable ( )
export class JWTInterceptor implements HttpInterceptor {
isRefreshingToken : boolean = false ;
tokenSubject : BehaviorSubject < string > = new BehaviorSubject < string > ( null ) ;
constructor ( private inj : Injector ) { }
/**
* Função que clona o request adicionando o token nos cabeçalhos
* @param request
* @param token
*/
addToken ( request : HttpRequest < any > , token : string ) : HttpRequest < any > {
return request . clone ( {
setHeaders : {
Authorization : `Bearer ${ token } `
}
} ) ;
}
intercept ( request : HttpRequest < any > , next : HttpHandler ) : Observable < HttpEvent < any > > {
const authService = this . inj . get ( AuthService ) ;
/**
* no login nao quero interceptar nada, então
* do continuidade no fluxo do request
*/
if ( request . url . includes ( "/auth/login" ) ) {
return next . handle ( request ) ;
}
const requestJWT = this . addToken ( request , authService . getToken ( ) . token ) ;
// capturando os possiveis erros do request
return next . handle ( requestJWT ) . pipe (
catchError ( err => {
/**
* Caso o erro seja um código 401
* inicio a logica pra o refresh do token
*/
if ( err . status === 401 ) {
// verifico se o motivo foi token expirado
if ( err . error && err . error . errorMessage . includes ( "expirado" ) ) {
// se o refresh de token não tiver em andamento, inicio ele
if ( ! this . isRefreshingToken ) {
this . isRefreshingToken = true ;
/**
* Reiniciando o valor do token aqui para que os proximos pedidos
* aguardem até que o token volte da chamada de atualização do token.
*/
this . tokenSubject . next ( null ) ;
return authService . refreshToken ( ) . pipe (
finalize ( ( ) => {
this . isRefreshingToken = false ;
} ) ,
switchMap ( ( newToken : string ) => {
if ( newToken ) {
this . tokenSubject . next ( newToken ) ;
return next . handle ( this . addToken ( request , newToken ) ) ;
}
/**
* se não for retornado o token, deu algum problema
* então desconecto o usuario e lanço a exceção pra frente
*/
authService . logoff ( ) ;
return Observable . throw ( "Não foi possivel obter o token" ) ;
} ) ,
/**
* Caso de qualquer erro no refresh do token
* não tem oque fazer, então deslogo o usuario
*/
catchError ( errTokenRefresh => {
authService . logoff ( ) ;
return Observable . throw ( errTokenRefresh ) ;
} )
) ;
}
/**
* Caso o token esteja em processe de atualização
*/
else {
return this . tokenSubject . pipe (
filter ( token => token != null ) ,
take ( 1 ) ,
switchMap ( token => {
return next . handle ( this . addToken ( request , token ) ) ;
} )
) ;
}
}
/**
* Caso o motivo do erro 401 não contenha a palavra expirado
* deslogo o usuario, pois é algum erro desconhecido...
*/
else {
authService . logoff ( ) ;
return Observable . throw ( err ) ;
}
}
// qualquer outro erro lanço uma exceção
else {
return Observable . throw ( err ) ;
}
} )
) ;
}
}