Angular Interceptor로 무엇을 노출하는지 주의하십시오.

27554 단어 securityangular
Twitter에서 팔로우 | 구독하기 Newsletter | timdeschryver.dev에 원래 게시되었습니다.


아마도 이미 Angular Interceptor를 사용하고 있을 것입니다.
작업 중인 애플리케이션에서 하나를 찾을 수 없더라도 특히 인증 헤더를 처리하는 경우 추가된 라이브러리 중 하나가 인터셉터를 사용할 가능성이 큽니다.

Angular Interceptor을 좋아하는 회의의 출입문과 비교할 수 있습니다.
대회장에 도착하면 입구를 통해 들어가야 하며, 행사장에 들어가기 전에 기념품을 받을 수 있습니다.
하루가 끝나고 행사장을 나가고 싶으면 다시 문을 통과해야 하고 스웩을 더 받을 수도 있습니다.

Angular 인터셉터로 돌아가서 HTTP 클라이언트를 통해 시작된 모든 HTTP 요청은 서버로 전송되기 직전에 등록된 인터셉터에 의해 처리되고 서버가 응답으로 응답할 때 다시 처리됩니다.
따라서 인터셉터는 HTTP 트래픽과 함께 작동하는 공통 논리를 배치하기에 이상적인 장소입니다.

인터셉터는 다양한 형태로 제공되며 특정 작업을 담당하는 경우가 많습니다.
예를 들어 캐싱 레이어 역할을 하는 인터셉터, 로더 표시기 표시 및 숨기기를 담당하는 인터셉터 또는 만장일치로 HTTP 오류를 처리하는 다른 인터셉터를 작성할 수 있습니다.
그러나 가장 많이 사용되는 인터셉터는 HTTP 요청에 요청 헤더를 추가하는 인터셉터입니다. 예를 들어 사용자의 토큰과 함께 요청 헤더에 인증 헤더를 추가합니다.

제가 여러분과 이야기하고 싶은 것은 이러한 인터셉터에 관한 것입니다.
이를 수행하는 일반적인 인터셉터는 종종 아래 중 하나와 같이 구현됩니다.

Angular 문서에서:

import { AuthService } from '../auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private auth: AuthService) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // Get the auth token from the service.
        const authToken = this.auth.getAuthorizationToken();

        // Clone the request and replace the original headers with
        // cloned headers, updated with the authorization.
        const authReq = req.clone({
            headers: req.headers.set('Authorization', authToken),
        });

        // send cloned request with header to the next handler.
        return next.handle(authReq);
    }
}


첫 번째 Google 결과 중 하나로 제공되는 기사에서:

export class AuthInterceptorService implements HttpInterceptor {
    constructor(private authService: AuthService) {}
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const token = this.authService.getAuthToken();

        if (token) {
            // If we have a token, we set it to the header
            request = request.clone({
                setHeaders: { Authorization: `Authorization token ${token}` },
            });
        }

        return next.handle(request).pipe(
            catchError((err) => {
                if (err instanceof HttpErrorResponse) {
                    if (err.status === 401) {
                        // redirect user to the logout page
                    }
                }
                return throwError(err);
            }),
        );
    }
}


상단Stack Overflow 질문 및 해당 답변에서:

import { Injectable } from '@angular/core';

import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';

import { Observable } from 'rxjs/Observable';


@Injectable()
export class fwcAPIInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const authReq = req.clone({
      headers: req.headers.set('Content-Type', 'application/json')
    });

    console.log('Intercepted HTTP call', authReq);

    return next.handle(authReq);
  }
}



const authReq = req.clone({
    headers: req.headers.set('Content-Type', 'application/json')
    .set('header2', 'header 2 value')
    .set('header3', 'header 3 value')
});


여기서 무엇이 잘못될 수 있는지 알고 계십니까?

응용 프로그램이 타사 API에 HTTP 요청을 보내면 어떻게 될까요?

그렇다면 인터셉터에 의해 추가된 모든 추가 헤더도 타사 API로 전송됩니다.
이상적으로는 이런 일이 발생하지 않아야 하지만 우연히 발생하거나 발생하지 않는 경우 제3자에게 (민감한) 정보를 유출하고 싶지 않을 것입니다.

이를 방지하기 위한 몇 가지 솔루션이 있습니다.
그것들을 살펴봅시다.

가장 쉽고 안전한 방법은 나가는 요청의 호스트를 확인하는 것입니다.
신뢰할 수 있는 호스트인 경우에만 헤더가 요청에 추가됩니다.

이것을 Angular 문서의 예에 적용하면 이제 인터셉터가 다음과 같이 보입니다.

import { AuthService } from '../auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private auth: AuthService) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const uri = new URL(req.url);
        if(uri.hostname !== 'trusted-domain.com') {
            return next.handle(req);
        }

        // Get the auth token from the service.
        const authToken = this.auth.getAuthorizationToken();

        // Clone the request and replace the original headers with
        // cloned headers, updated with the authorization.
        const authReq = req.clone({
            headers: req.headers.set('Authorization', authToken),
        });

        // send cloned request with header to the next handler.
        return next.handle(authReq);
    }
}


위 솔루션의 변형은 보다 유연하며 구성 가능한 호스트 이름 모음을 사용합니다.

Because practice makes the check configurable, many libraries use this technique.
For example, the Angular Auth OIDC Client uses configurable secure routes to only append the Authorization header to requests that are sent to the configured host names (secure routes).



import { AuthService } from '../auth.service';
import { Config } from '../config';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private auth: AuthService, private config: Config) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const uri = new URL(req.url);
        if(!config.trustedHostNames.includes(uri.hostname)) {
            return next.handle(req);
        }

        // Get the auth token from the service.
        const authToken = this.auth.getAuthorizationToken();

        // Clone the request and replace the original headers with
        // cloned headers, updated with the authorization.
        const authReq = req.clone({
            headers: req.headers.set('Authorization', authToken),
        });

        // send cloned request with header to the next handler.
        return next.handle(authReq);
    }
}


다른 솔루션은 HttpContext 을 사용하여 특정 요청을 무시합니다.
그러나 이렇게 하면 요청에 컨텍스트를 추가하는 것을 잊기 쉬워지므로 이상적이지 않습니다.

이것은 다음과 같이 보입니다.

먼저 HttpContextToken 를 정의합니다.

const IS_UNTRUSTED = new HttpContextToken<boolean>(() => false);


그런 다음 요청에 대한 토큰을 설정합니다.

import { IS_UNTRUSTED } from './token.ts';

@Injectable()
export class Some3rdPartyService {
    constructor(private http: HttpClient) {}

    getData() {
        return this.http.get('3rd-party-api',
            {
                context: new HttpContext().set(IS_UNTRUSTED, true),
            }
        )
    }
}


마지막으로 인터셉터에 토큰이 설정되어 있는지 확인합니다.

import { AuthService } from '../auth.service';
import { IS_UNTRUSTED } from './token.ts';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
    constructor(private auth: AuthService) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        if (req.context.get(IS_UNTRUSTED)) {
            return next.handle(req);
        }

        // Get the auth token from the service.
        const authToken = this.auth.getAuthorizationToken();

        // Clone the request and replace the original headers with
        // cloned headers, updated with the authorization.
        const authReq = req.clone({
            headers: req.headers.set('Authorization', authToken),
        });

        // send cloned request with header to the next handler.
        return next.handle(authReq);
    }
}


결론



HTTP 클라이언트에 의해 시작된 모든 HTTP 요청은 Angular 인터셉터에 의해 차단됩니다.
이는 나가는 모든 HTTP 요청에 논리를 추가할 수 있는 강력한 기능입니다.

그러나 이로 인해 타사 API에 (민감한) 정보가 쉽게 유출될 수 있습니다.
이러한 요청은 동일한 인터셉터에 의해 가로채기도 합니다.
이렇게 하면 알지 못하는 사이에도 원하는 요청에 더 많은 정보를 실수로 추가할 수 있습니다.
가장 일반적인 위반은 Authorization 헤더를 요청에 추가하는 것입니다.

가장 좋은 방법은 요청에 헤더를 추가하기 전에 나가는 요청이 신뢰할 수 있는 호스트로 전송되는지 항상 확인하는 것입니다.


Twitter에서 팔로우 | 구독하기 Newsletter | timdeschryver.dev에 원래 게시되었습니다.

좋은 웹페이지 즐겨찾기