Angular Interceptor: 더 나은 대안
31235 단어 interceptorhttpclientangular
그런 다음 일반적으로 요청에 권한 부여 헤더를 추가하는 것을 의미하는 보안 리소스와 통신해야 하는 부분이 있습니다. 한 가지 방법은 아래와 같이 모든 개별 요청에 헤더를 추가하는 것이지만 많은 요청에 수동으로 헤더를 추가하는 것은 금세 귀찮아집니다.
initGetUserData(): any {
// Get the token & create headers
const token = this.authService.GetAccessToken();
const headers = new HttpHeaders(
{ Authorization: `Bearer ${token}` }
);
this.httpClient.get('Secure_Url', { headers }).subscribe(response => {
});
}
중복을 줄이기 위한 솔루션이 있습니다.
이것은 Angulars의 요청 인터셉터를 확장하는 일반적인 선택으로, 요청에 권한 부여 헤더를 추가하는 것과 같은 사전 처리 논리를 추가할 수 있습니다. 인터셉터에 토큰 새로 고침 논리를 추가하는 것도 좋은 방법이므로 사용자 경험이 원활하고 토큰이 새로 고쳐지면 원래 요청을 완료할 수 있습니다.
intercept(request: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
// Get token & add to request headers
let token = this.authService.GetAccessToken();
request = request.clone({
headers: request.headers
.set('Authorization', `Bearer ${token}`)
});
return next.handle(request).pipe(
catchError(err => {
if (err.status === 401) {
// Refresh tokens
return this.authService.InitRefresh().pipe(
switchMap((response) => {
// Get new token
token = this.authService.GetAccessToken();
request = request.clone({
headers: request.headers
.set('Authorization', `Bearer ${token}`)
});
// Continue original request
return next.handle(request);
})
);
}
}));
// Omitting error handling etc. for brevity
}
보라, 우리는 모든 설정을 가졌으니 피치는 무엇을 위한 것인가?
보안 리소스가 아닌 공개 API와 상호 작용하는 보안 모듈 내부에 구성 요소가 있을 때까지 모든 것이 잘 작동하고 예상대로 작동합니다. 일반적으로 발생하는 일은 인터셉터가 해당 요청에 권한 부여 헤더를 가로채서 추가하려고 시도하는 것입니다. 또한 토큰 새로 고침의 전체 오버헤드는 공용 리소스에 대해 실행됩니다.
더 나쁜 것은 사용자가 로그인하지 않고 구성 요소에 액세스하려고 하면 공개 보기로 작동해야 하고 로그인이 필요하지 않아야 하며 인터셉터가 추가/새로 고침을 시도할 때 오류(처리되지 않은 경우)가 발생한다는 것입니다. 토큰이지만 사용자가 로그인하지 않았기 때문에 사용할 수 있는 토큰이 없습니다.
그러나 그것을 해결할 방법도 있습니다.
사실입니다. 무시하고 싶은 요청을 처리하기 위한 솔루션이 있습니다. 요청에 사용자 정의 헤더를 추가하거나 인터셉터 인증 논리에서 생략해야 하는 URL 배열을 정의할 수 있습니다. 다시 말하지만, 우리는 이러한 모든 비정상적 구현을 추적하기 어려운 지점에 곧 도달합니다.
// Check for skip header
const isSkipHeader = request.headers.has('skip');
if (isSkipHeader) {
// Remove unnecessary header & proceed
request = request.clone({
headers: request.headers.delete('skip')
});
return next.handle(request);
}
따라서 제안된 솔루션
다음 시나리오를 처리할 Angulars의 HTTP 클라이언트 주위에 사용자 지정 래퍼를 만드는 것으로 시작합니다.
데이터 전송 및 검색을 위한 일반 공용 메서드를 사용하여 클래스를 만듭니다. 특정 시나리오에 매우 도움이 될 인증을 재정의하는 방법을 제공하고 호출을 실행하기 전에 토큰 만료를 확인하고 그에 따라 진행합니다.
A lot of code have been omitted for brevity (Such as HTTP post, put, delete methods & error handling but it should be very easy to extend this
/**
* Interface for HTTP options
*/
export interface AppHttpOptions<T = any> {
Headers?: HttpHeaders;
Body?: T;
RequestUrl: string;
QueryParams?: object;
}
/**
* Application HTTP Client wrapper to provide authorization mechanism
* or any customization of requests
*/
@Injectable({
providedIn: 'root'
})
export class AppHttpClient {
// Pass this from environment variable
private baseUrl = 'baseUrl';
/**
* Constructor for client class, can be used to inject
* required resources
* @param httpClient Angular HTTP Client
*/
constructor(private httpClient: HttpClient,
private authService: AuthService) {
}
/**
* Initiates authorized Get request to the api
* @param httpOptions HttpOptions containing request data
*/
public GetAuthorized<ResponseType>(httpOptions: AppHttpOptions):
Promise<ResponseType> {
return this.getResponsePromise(httpOptions, 'post');
}
/**
* Initiates Get request to the api
* @param httpOptions HttpOptions containing request data
*/
public Get<ResponseType>(httpOptions: AppHttpOptions):
Promise<ResponseType> {
return this.getResponsePromise(httpOptions, 'get', false);
}
/**
* Creates a promise that resolves into HTTP response body
* @param httpOptions HttpOptions containing request data
* @param requestType Type of request i.e Get, Post, Put, Delete
*/
private getResponsePromise<ResponseType>
(httpOptions: AppHttpOptions,
requestType: 'post' | 'get' | 'delete' | 'put',
isAuth: boolean = true):
Promise<ResponseType> {
return new Promise((resolve, reject) => {
// Process the subscription & resolve the response
// i.e the request body response
this.getProcessedSubscription(httpOptions, requestType, isAuth).
then((response: ResponseType) => {
resolve(response);
}).catch(err => reject(err));
});
}
/**
* Subscribes to http request & returns the response as promise
* @param httpOptions HttpOptions containing request data
* @param requestType Type of request i.e Get, Post, Put, Delete
*/
private getProcessedSubscription<ResponseType>
(httpOptions: AppHttpOptions,
requestType: 'post' | 'get' | 'delete' | 'put',
isAuth: boolean):
Promise<ResponseType> {
return new Promise((resolve, reject) => {
this.getHttpRequest<ResponseType>
(httpOptions, requestType, isAuth).then(response => {
// Subscribe to HTTP request & resolve with the result
response.subscribe(result => {
resolve(result);
},
err => reject(err)
);
}).catch(err => reject(err));
});
}
/**
* Creates a promise to get the HTTP request observable
* @param httpOptions HttpOptions containing request data
* @param requestType Type of request i.e Get, Post, Put, Delete
*/
private getHttpRequest<ResponseType>
(httpOptions: AppHttpOptions,
requestType: 'post' | 'get' | 'delete' | 'put',
isAuth: boolean):
Promise<Observable<ResponseType>> {
return this.getAuthHeaders(httpOptions.Headers, isAuth).
then((headers: HttpHeaders) => {
// Append the query parameters
const options = this.addQueryParams(httpOptions);
// Create a HTTP request with angular HTTP Client
const request = this.httpClient.request<ResponseType>
(requestType,
this.baseUrl + options.RequestUrl,
{ body: options.Body, headers });
return request;
}).catch(err => Promise.reject(err));
}
/**
* Creates a promise that adds the authentication header
* to the request headers. Token retrieve & refresh logic can
* be easily handled as it is async operation
* @param headers Headers passed in with request
*/
private getAuthHeaders(headers: HttpHeaders, isAuth: boolean):
Promise<HttpHeaders> {
return new Promise((resolve) => {
// Only add authentication headers if required
if (isAuth) {
const token = this.authService.GetAccessToken();
if (headers) {
// Append authorization header
// * This is the core portions.
// We can apply all logics for checking token expiry,
// refreshing it & appending it to the headers
// without worrying about any side effects as we can
// resolve promise after all the other actions
headers.append('Authorization', `Bearer ${token}`);
}
else {
// Create new headers object if not passed in
headers = new HttpHeaders({
Authorization: `Bearer ${token}`
});
}
}
resolve(headers);
});
}
/**
* @param httpOptions HttpOptions containing request data
* @param httpOptions Add
*/
private addQueryParams(httpOptions: AppHttpOptions): AppHttpOptions {
if (httpOptions.QueryParams) {
// Create the parameters string from the provided parameters
const query = Object.keys(httpOptions.QueryParams)
.map(k => k + '=' + httpOptions.QueryParams[k])
.join('&');
// Append the parameters to the request URL
httpOptions.RequestUrl = `${httpOptions.RequestUrl}?${query}`;
}
return httpOptions;
}
}
그리고 우리는 끝났습니다! 메소드를 사용하려면 클래스를 삽입하고 최소한의 구성으로 적절한 메소드를 호출하기만 하면 됩니다.
constructor(private httpClient: AppHttpClient) { }
initGetData(): any {
// Public resource request
this.httpClient.Get({ RequestUrl: 'Public_Url'}).
then(response => {
});
// Secured resource request
this.httpClient.GetAuthorized({ RequestUrl: 'Secure_Url' }).
then(response => {
});
}
위의 구현은 사용 사례에 따라 여러 옵션으로 수정할 수 있습니다. 통화를 시작하기 전에 토큰 만료 및 새로 고침 확인, 많은 번거로움 없이 특정 요청이 있는 사용자 정의 헤더 전달 등
그러한 시나리오나 더 영향력이 있을 수 있는 다른 대안을 처리하기 위해 무엇을 사용하는지 알려주십시오.
즐거운 코딩!
Reference
이 문제에 관하여(Angular Interceptor: 더 나은 대안), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/faisalejaz/angular-interceptor-a-better-alternative-166n텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)