Posts Angular Interceptor로 로그인 상태 확인하기
Post
Cancel

Angular Interceptor로 로그인 상태 확인하기

사용자가 로그인을 성공했을 시 생성되는 Token 값을 이용해서 페이지 접근권한을 관리할 때 Interceptor라는 것을 사용한다고 들었습니다. 실제로는 localStorage에 저장한 token값을 사용하여 임시프로젝트를 수행했습니다. 나중에 Interceptor를 사용할 수도 있다고 생각되어서 코드 흐름을 파악하는 정도로만 기록해두려고 합니다.







  • Development Env.
  • post date : 2020. 06. 12
  • OS : macOS Catalina 64bit, Windows 10 64bit
  • Node : ‘’
  • Angular : ‘’
  • WebStorm : 2017.2.6
  • JRE: 1.8.0_152-release-915-b12 amd64
  • JVM: OpenJDK 64-Bit Server VM by JetBrains s.r.o






이전의 포스팅에서 사용자가 가입할 수 있고 게시판이 있는 사이트를 만들어본다고 했습니다. 그래서 ‘users’ App과 ‘boards’ App이 필요했고, 다음 명령어를 통해서 같이 추가했습니다.

Interceptors

HttpInterceptor was introduced with Angular 4.3. It provides a way to intercept HTTP requests and responses to transform or handle them before passing them along.

HttpInterceptor는 Angular 4.3에서 도입되었습니다. 전달하기 전에 HTTP 요청 및 응답을 가로 채서 변환하거나 처리하는 방법을 제공합니다.

주로 AuthToken을 사용한 Api 요청이나 전역적으로 Http 에러를 처리할 때 자주 쓰입니다.

Interceptor를 사용하여 할 수 있는 것들은 다음과 같습니다.

  • Add bearer token
  • Refresch Token
  • Redirect to the login page

지금 백앤드 측에서는 Django가 제공하는 rest-auth 를 통해서 유저가 로그인하면 유저 정보와 Token 값을 받고 있으므로 이를 활용하는 방법을 알아보겠습니다.

Interceptor Service 간단한 예제

출처: https://alexband.tistory.com/54 [GoodBye World]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// app/src/services/http.interceptor.service.ts
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators'; 

@Injectable()
export class HttpInterceptorService implements HttpInterceptor { 
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        let request: HttpRequest<any> = req.clone();
        return next.handle(request).pipe(
            catchError(e => {
                /** * 여기서 Error 원하는 방식으로 에러를 처리 */
                alert(e.error.error.message); return throwError(e);
            })
        )
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// src/app/app.module.ts 
import { BrowserModule } from '@angular/platform-browser'; 
import { NgModule } from '@angular/core'; 
import { AppComponent } from './app.component'; 
import { CommonModule } from '@angular/common'; 
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 
import { HttpInterceptorService } from '../services/http-interceptor.service'; 

@NgModule({ 
    declarations: [ 
        AppComponent 
        ], 
    imports: [ 
        BrowserModule, 
        CommonModule,
        HttpClientModule, 
    ], 
    providers: [ 
        { provide: HTTP_INTERCEPTORS, useClass: HttpInterceptorService, multi: true },
    ], 
    bootstrap: [AppComponent]
})

export class AppModule { }

여러가지 상황을 고려한 코드 이해하기

출처 : https://medium.com/angular-in-depth/top-10-ways-to-use-interceptors-in-angular-db450f8a62d6 [Michael Karén, medium]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import { Injectable } from "@angular/core";
import { 
  HttpEvent, HttpInterceptor, HttpHandler,
  HttpRequest, HttpErrorResponse
} from "@angular/common/http";
import { throwError, Observable, BehaviorSubject, of } from "rxjs";
import { catchError, filter, take, switchMap } from "rxjs/operators";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private AUTH_HEADER = "Authorization";
  private token = "secrettoken";
  private refreshTokenInProgress = false;
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

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

    req = this.addAuthenticationToken(req);

    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error && error.status === 401) {
          // 401 errors are most likely going to be because we have an expired token that we need to refresh.
          if (this.refreshTokenInProgress) {
            // If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value
            // which means the new token is ready and we can retry the request again
            return this.refreshTokenSubject.pipe(
              filter(result => result !== null),
              take(1),
              switchMap(() => next.handle(this.addAuthenticationToken(req)))
            );
          } else {
            this.refreshTokenInProgress = true;

            // Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
            this.refreshTokenSubject.next(null);
            
            return this.refreshAccessToken().pipe(
              switchMap((success: boolean) => {               
                this.refreshTokenSubject.next(success);
                return next.handle(this.addAuthenticationToken(req));
              }),
              // When the call to refreshToken completes we reset the refreshTokenInProgress to false
              // for the next time the token needs to be refreshed
              finalize(() => this.refreshTokenInProgress = false)
            );
          }
        } else {
          return throwError(error);
        }
      })
    );
  }

  private refreshAccessToken(): Observable<any> {
    return of("secret token");
  }

  private addAuthenticationToken(request: HttpRequest<any>): HttpRequest<any> {
    // If we do not have a token yet then we should not set the header.
    // Here we could first retrieve the token from where we store it.
    if (!this.token) {
      return request;
    }
    // If you are calling an outside domain then do not add the token.
    if (!request.url.match(/www.mydomain.com\//)) {
      return request;
    }
    return request.clone({
      headers: request.headers.set(this.AUTH_HEADER, "Bearer " + this.token)
    });
  }
}

interceptor는 intercept() 함수를 통해 구현됩니다.

intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent> req : Header 값이 들어가는데, 기존 값을 바로 사용하지 못하므로 clone하여 사용합니다. next : HttpHandler로 액션 정의를 해주는 부분입니다. Return : Observable로 주어집니다. HTTP 클래스들이 대부분 Observable로 주는데, 이전에 HttpClient를 사용했을 때와 비교해서 이해하면 될 것 같습니다.

참조

Top 10 ways to use Interceptors in Angular https://medium.com/angular-in-depth/top-10-ways-to-use-interceptors-in-angular-db450f8a62d6

Angular Http Interceptor 사용하기 https://alexband.tistory.com/54

https://angular.io/api/common/http/HttpInterceptor

This post is licensed under CC BY 4.0 by the author.

Python & Django App 생성 및 설계하기

Angular Http 400 에러 처리하기 (Observabel 데이터 처리)

Comments powered by Disqus.