본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


앵귤러, 리엑트, 뷰/Angular Tutorial(new)

앵귤러 튜토리얼(Angular tutorial) - 17 : 가드(인터셉터)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 8. 5.

이번시간에는 사용자의 로그인 여부를 확인하여주는 기능에 대해서 살펴보려 합니다.
일반적으로 "인터셉터" 라는 역할로 많이 알려져 있으며, 앵귤러에서는 해당 기능을 가드(Guard) 라고 합니다.
이름만 들어도 왠지 무언가로부터 보호(?)를 해줄 것 같은 기능처럼 보입니다.

앵귤러에서의 가드는 컴포넌트에게 요청을 전달하기 전 먼저(pre) 동작을 하는 클래스 입니다.
가드를 만들기 위해서는 아래 명령어를 입력하면 쉽게 만들 수 있습니다.
ng g guard auth

여기서 "auth"는 사용자가 입력하는 가드의 이름 입니다. (그러므로 마음데로 부여해도 상관 없습니다.)

원하는 이름으로 바꾸셔도 됩니다. ^^

선택하는 부분에서는 첫번째 항목을 선택합니다.

 

명령어를 입력하면 뭔가 선택을 해야되는데 여기서는 첫번째 내용을 선택한 뒤 엔터를 눌러줍니다.
그러면 파일이 만들어지게됩니다.

요런 모양의 클래스가 만들어지게 됩니다.

 

가드와 관련된 내용입니다.
서비스를 만들 때 사용하였던 Injectable 데코레이터가 나온것으로 보아하니 요녀석도 사용하려면 왠지 app모듈에 등록해야 될 것 같습니다.
canActivate라는 부분에서의 내용이 참 많습니다..
next와 state라는 값은 라우터의 정보와 관련된 내용을 담고 있으며, canActivate 는 리턴하는 값의 형태가 대략적으로 4개나되는 것을 볼 수 있습니다.
영 복잡하면 아래처럼 바꾸어 사용해도 무방합니다.
* 대상 : auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return true;
  }
  
}

 

만들어진 가드를 적용하려면 역시나 모듈에 등록하여 주어야 합니다.

* 대상 : app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { BoardComponent } from './board/board.component';

import { RouterModule,Routes} from '@angular/router'; //라우터
import { AuthGuard } from './auth.guard';


const router : Routes = [  //라우팅
  {path : 'login' , component : LoginComponent},  
  {path : 'board' , component : BoardComponent, canActivate:[AuthGuard]},  //가드 추가
  {path : '', redirectTo : '/login',  pathMatch : 'full'}
]

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(router,{enableTracing:false}),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

라우터 속성에 만들어준 guard를 추가하여주었습니다.
그리고 앱을 한번 동작하여 봅니다.
딱히 달라진것이 없어보여서 아래 코드를 guard에 추가하여 보았습니다.

* 대상 : auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    console.log('값이 참 입니다.',true);  //요기를 추가합니다!
    return true;
  }
  
}

 

그리고 다시접속하여 봅니다.

콘솔문구가 출력됩니다!

 

login컴포넌트에서 보이지 않는 문구가 board 컴포넌트만 가면 나타나는 것을 볼 수 있습니다.
app모듈에서 우리는 board컴포넌트에게 라우팅을 해 줄때 auth가드를 동작하게 하였습니다.
login컴포넌트는 누구든 접근 할 수 있습니다.
그러나 board 컴포넌트는 사용자가 설정한 가드(guard)를 고려하여 접근이 됩니다.
이해를 위해 가드에서 리턴값을 false로 변경한 뒤 board 주소로 접속하여봅니다.

* 대상 : auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    console.log('값이 참 입니다.',true);  //요기를 추가합니다!
    return false;  //요기를 바꿉니다!!!
  }
  
}

 

문구는 출력이되고 있지만 board컴포넌트의 내용이 나오지 않고 있습니다.
가드에서의 결과 값의 형태가 거짓(false)을 의미하면 해당 컴포넌트로 갈 수 없음을 의미 합니다.
가드에서의 이러한 방법을 토대로 로그인 여부를 판별하거나 허용되지 않는 인원의 페이지 이동을 제한 할 수 있습니다.

아무런 내용이 나오지 않습니다.

 

자, 이제 본인의 생각대로 한번 만들어보도록 합니다!

가드는 일종의 서비스와 같은 개념입니다. 또한 Injectable 데코레이터가 존재하므로 앵귤러의 모듈에 의해서 다른 컴포넌트에게 계속해서 같은 내용을 제공 할 수 있습니다.

어느정도 만들어 본 다음에 아래 내용을 한번 따라하여 보세요.

 

* 아래 내용은 조금 어렵습니다.
* 위 내용을 전부 이해하고 아래내용을 진행 하여야 합니다.
* rxjs와 관련된 내용을 한번 읽어보셔도 좋습니다.


 

가드의 기능을 채우기 전에 서버에 응답과 요청역할을 담당하는 서비스를 만들어 보겠습니다.
물론 여기서는 데이터베이스와 연결된 서버가 없으므로 비슷한 역할을 하는 서비스가 될 것 입니다.
ng g service ask

서비스 이름이 ask 입니다.

 

 

ask라는 서비스를 만들었습니다.
이곳에는 요청과 응답에 대한 함수를 만들 예정입니다.
서비스를 만들기 전에 login컴포넌트에서 id와 비밀번호를 입력받는 기능을 만들어 줍니다.

* 대상 : login.component.ts

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  id : string;
  passwd : string;
  constructor() { }
  ngOnInit(): void {
 
  }

  login() {
    console.log(this.id, this.passwd);
  }
}

 

* 대상 : login.component.html

<input type="text" placeholder="id입력" [(ngModel)]='id' />
<input type="text" placeholder="password 입력" [(ngModel)]='passwd'/>
<input type="button" value="로그인" (click)='login()'/>

 

손쉬운 데이터 바인딩을 위해 ngModel 디렉티브가 사용되었습니다.
아마 오류가 나는 분들도 있을텐데..어디서 설정해야 해당 오류가 사라지게 되는지 기억을 잘 해내셨으면 좋겠습니다. ^^
login컴포넌트의 login 함수를 통해서 이제 아이디와 비밀번호를 가져올 수 있게 되었습니다.

 

그러면, ask서비스에 로그인을 시도하는 함수를 만들어 줍니다.
아직 데이터베이스 서버에 접속을 시도할 수 없으므로 해당 부분은 서비스에 private한 변수로 표기하여 줍니다.

* 대상 : ask.service.ts

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AskService {

  //저장소(나중에 데이터베이스 서버)
  private readonly storage = {
    id : 'admin',
    passwd : '1234'
  }
  constructor() { 

  }

  //로그인을 시도하는 함수
  tryToLogin(param : any){
    return new Observable( arg=>{  //관측대상 생성
      if(param.id == this.storage.id && param.passwd == this.storage.passwd){  //로그인 정보가 맞다면
        arg.next({status: true});  //성공
        localStorage.setItem('status',"true");  //로컬저장소에 저장!
      } else {
        arg.next({status: false, reason : 'wrong information'});  //실패
      }
      arg.complete();
    });
  }

}

 

Observable 이라는 클래스가 나타났습니다.
해당 클래스는 튜토리얼 15번에서 설명하였던 내용입니다.

lts0606.tistory.com/365

 

해당 객체를 login컴포넌트에서는 subscribe이라는 함수를 통해서 구독을 통해 콜백 행위를 정의할 수 있습니다.
중간에 localStorage에 정보를 set을 통해 저장하는 부분이 보입니다.
localStorage는 쿠키와 비슷한 개념의 저장소 개념입니다.
큰 특징으로는 만료기간이 없으며, 5메가까지의 정보를 저장 할 수 있다는 점 입니다.

이제 login컴포넌트를 수정하여 봅니다.

* 대상 : login.component.ts

import { Component, OnInit } from '@angular/core';
import { AskService } from '../ask.service';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  id : string;
  passwd : string;
  constructor(private service : AskService) { 

  }
  ngOnInit(): void {
 
  }
  login() {  //subscribe을 통해 구독하여 콜백행위를 하게 하였습니다.
    this.service.tryToLogin({id : this.id, passwd : this.passwd}).subscribe( arg=>{
      console.log(arg);
    });
  }
}

 

생성자로 service를 private하게 모듈로부터 받았습니다.

private라는 접근제어자를 붙여주면 해당 서비스는 글로벌하게 사용가능한 변수로 바뀌게 됩니다.

login함수에 서비스의 tryToLogin 함수를 통해 Observable 클래스를 구독(subscribe) 하게 하였습니다.
구독이라는 표현이 어렵다면 콜백행위, 그다음 행동으로 이해하면 될 것 같습니다.
이제 로그인버튼을 누르면 아래 사진처럼 내용이 출력됩니다.

틀리면 status가 false입니다. 맞으면? true갑이 나옵니다~

 

 

* 위 내용을 이해한 뒤에 아래 내용을 살펴보세요.


 

다음으로 가드의 역할을 다듬어 보겠습니다.
사용자가 로그인이 되면 서버에서는 로그인 정보를 가지고 있게 됩니다.(아마 세션형태가 될 것 입니다)
그러면 가드는 사용자가 페이지를 이동할 때 해당 서버로부터 정보를 가져와 확인하는 방식이 되어야 할 것 입니다.
위 개념을 토대로 ask서비스에 로그인 여부를 확인하는 함수를 추가하여 보겠습니다.

* 대상 : ask.service.ts

  //생략.. 

  //가드가 구독하는 대상
  readonly isLogged: BehaviorSubject<boolean> = new BehaviorSubject(false);

  //로그인이 되었는지 확인하는 함수
  isLogIn() : void{
    if(localStorage.getItem('status') == 'true'){  //저장소에 로그인정보가 있는지 확인
      this.isLogged.next(true);
    } else {
      this.isLogged.next(false);
    }
  }

 

이번에는 BehaviorSubject가 사용되었습니다.
마찬가지로 튜토리얼 15번에서 설명하였던 내용입니다.
로그인에 대한 여부는 localStorage에서 가져와서 값이 있고 없음을 판별하도록 하였습니다.
값이 있으면 true값을 발행(next)하고 없으면 false를 발행(next)하게 하였습니다.
이에 가드는 서비스에서 만들어진 isLogged 객체를 구독(subscribe) 할 것이며, 인터셉트(canActivate)하는 곳에서는 isLogin함수를 호출하여 로그인 여부를 판별 할 것 입니다.

* 대상 : auth.guard.ts

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AskService } from './ask.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  private status : boolean = false;  //글로벌 변수

  constructor(private serv : AskService){
    serv.isLogged.subscribe( result=>{  //로그인 여부가 담길 대상을 구독합니다. (콜백행위정의)
      console.log('서버에 물어보고온 결과', result);
      this.status = result;  //결과값을 업데이트
    });
  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    this.serv.isLogIn();  //로그인 되었는지 확인하여 봅니다.
    return this.status;
  }
  
}

 

이제 남은 것은 login 컴포넌트에서 로그인이 되면 board컴포넌트로 이동을 하는 것입니다.
해당 기능을 위해서 라우터(Router)를 주입받도록 합니다.
라우터 객체는 이미 우리가 app모듈에 등록하였으므로 생성자를 통해서 받기만 하면 됩니다.
* 대상 : login.component.ts

import { Component, OnInit } from '@angular/core';
import { AskService } from '../ask.service';
import { Router } from '@angular/router';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
  id : string;
  passwd : string;
  constructor(private service : AskService, private rout: Router) { 
    //생성자에 private를 붙여서 의존성을 주입받으면 글로벌하게 사용가능 합니다.
    //private가 있으므로 다른 컴포넌트나 html파일에서는 접근할 수 없습니다.
  }
  ngOnInit(): void {
 
  }

  login() {
    this.service.tryToLogin({id : this.id, passwd : this.passwd}).subscribe( (arg:any)=>{
      console.log(arg);
      if(arg.status == true){
        alert('로그인 성공!')
        this.rout.navigate(['/board']);
      }
    });
  }
}

 

라우터 객체를 통해서 다른 컴포넌트로 이동하는 방법은 navigate라는 함수에 원하는 경로(path)를 입력하면 됩니다.
내용이 다소 복잡하지만, 이제 로그인 여부에 따라 페이지 통제가 되면서 요청(path)에 따라 컴포넌트가 동작하는 앱의 기초 기능이 완성되었습니다.

Observable의 개념은 앵귤러를 통해 개발을 하는 동안 계속해서 만나게 되는 내용 입니다.
앵귤러에서 get, post, put, delete 등등 http(https) 통신을 하는 함수나 파이어베이스와 관련된 함수는 많은 부분이 Observable 형태로 되어 있습니다.
앵귤러에서 Observable 및 구독(subscribe)과 관련된 내용은 앞으로도 계속 나오므로 해당 내용을 한번쯤은 꼭 살펴보아야 합니다.

다음시간에는 로그인과 관련되어 아직 완성하지 못한 기능을 추가하여 보도록 하겠습니다.

thirdStudy.zip
0.01MB

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글