فصل ۷-۴: امنیت در مسیردهی (Routing Guard) در انگولار ۴

25 تیر 1396
امنیت در مسیردهی (Routing Guard) در انگولار ۴

در سه بخش گذشته از فصل ۷ با تمام مباحث موردنیاز برای مسیردهی در انگولار آشنا شدید. حال در این بخش به عنوان آخرین بخش از فصل ۷ می‌خواهیم مبحث امنیت در مسیردهی یا Routing Guard را با یکدیگر بررسی کرده تا علاوه بر بهینه‌تر شدن کدهای شما، امنیت نرم‌افزارتان نیز در بالاترین سطح ممکن باشد. با ما همراه باشید.

امنیت در مسیردهی یعنی چی؟

در پاسخ به این سوال باید بگوییم که امنیت در مسیردهی یعنی اعمال یک سری محدودیت‌ها قبل از بارگذاری هر کامپوننت. به عبارت دیگر می‌خواهیم قبل از اینکه یک کامپوننت بکار گرفته شدن، تمهیداتی روی آن اعمال گردد.

بنابراین برای همراهی با این مطلب، ابتدا مثال قبل را باز کرده و سپس در پوشه روت app یک فایل به نام auth-guard.service.ts ایجاد کنید. سپس مجموعه‌ی کد زیر را درون آن قرار دهید تا به توضیحات تک تک خطوط بپردازیم:

import {CanActivate} from "@angular/router";

export class AuthGuard implements CanActivate{
    
}

همانطور که ملاحظه می‌کنید یک کلاس با نام AuthGaurd ایجاد کرده‌ایم و سپس آن را بر اساس کلاس CanActivate که از کلاس‌های مورد استفاده در angular/router است پیاده‌سازی کرده‌ایم:

import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";

export class AuthGuard implements CanActivate{
    canActivate(route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
        
    }
}

همانطور که ملاحظه می‌کنید درون کلاس AuthGuard ابتدا متد canActivate را که به صورت خودکار درون angular/router و کلاس CanActivated تعریف شده‌ است، ایجاد می‌کنیم. توجه داشته باشید که این متد دو آرگومان به نام‌های route و state می‌پذیرد که به ترتیب نمایانگر مسیر فعال و وضعیت این مسیر می‌باشد. سپس نوع خروجی این متد را به صورت Observable یا Promise یا مقدار ساده‌ی boolean تعریف کرده‌ایم. داخل پرانتز اشاره کنیم که Observable برای پاسخ به یک یا چند رویداد مناسب است درحالیکه Promise تنها برای پاسخ به یک رویداد مورد استفاده قرار می‌گیرد. در حالت کلی Observable به Promise برتری دارد. حال که این مفاهیم را یاد گرفتید باید یک فایل به نام auth.service.ts درون پوشه روت app ایجاد کنید و سپس یک سیستم فیک برای ورود و عضویت به صورت زیر درون آن تعریف کنیم:

export class Auth{
    loggedIn = false;

    isAuthenticated(){
        const promise = new Promise(
            (resolve, reject) =>{
                setTimeout(()=>{
                    resolve(this.loggedIn)
                },800)
            }
        )
        return promise
    }

    logIn(){
        this.loggedIn = true;
    }

    logOut(){
        this.loggedIn = false;
    }
}

همانطور که مشاهده می‌کنید یک سرویس به نام Auth تعریف کردیم و درون آن یک شبیه‌سازی فیک از ورود و عضویت کاربر قرار دادیم. سپس درون متد isAuthenticate یک متغییر از نوع promise ایجاد و پس از ۸۰۰ میلی‌ثانیه مقدار this.loggIn را باز می‌گردانیم. حال می‌خواهیم از این سرویس درون سرویس AuthGuard استفاده کنیم بنابراین مطابق آموزشهای گذشته ابتدا باید از مفسر Injectable@ استفاده کنیم و در نهایت درون متد canActivate دستور فعال‌سازی یا عدم آن را برای کاربران ارسال کنیم. بنابراین تغییرات موجود در این فایل به صورت زیر خواهد بود:

import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";
import {Injectable} from "@angular/core";
import {Auth} from "./auth.service";

@Injectable()
export class AuthGuard implements CanActivate{

    constructor(private auth: Auth, private router: Router){}

    canActivate(route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
        return this.auth.isAuthenticated()
            .then(
                (authentication: boolean)=>{
                    if(authentication){
                        return true;
                    }else{
                        this.router.navigate(['/']);
                    }
                }
            );
    }
}

بسیار عالی در این فایل همانطور که مشاهده می‌کنید از سرویس Auth استفاده کرده و سپس مقدار ارزیابی شده برای ورودی کاربر را باز می‌گردانیم. حال باید به فایل app.Module.ts رفته و سپس مسیرهایی که می‌خواهیم این تست امنیت روی آنها صورت پذیرد را مشخص کنیم. برای اینکار از یک ویژگی به نام canActivate بهره می‌بریم و مقدار روبه‌روی آن را نام سرویسی که وظیفه‌ی تامین امنیت را به عهده دارد (یعنی سرویس AuthGuard) قرار می‌دهیم. بنابراین داریم:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';

import {AppComponent} from './app.component';
import {HomeComponent} from './home/home.component';
import {UsersComponent} from './users/users.component';
import {ServersComponent} from './servers/servers.component';
import {UserComponent} from './users/user/user.component';
import {EditServerComponent} from './servers/edit-server/edit-server.component';
import {ServerComponent} from './servers/server/server.component';
import {ServersService} from './servers/servers.service';
import {RouterModule, Routes} from "@angular/router";
import {PageNotFoundComponent} from './page-not-found/page-not-found.component';
import {AuthGuard} from "./auth-guard.service";
import {Auth} from "./auth.service";

const appRoutes: Routes = [
    {path: '', component: HomeComponent},
    {
        path: 'users', component: UsersComponent, children: [
        {path: ':id/:name', component: UserComponent},
    ]
    },
    {
        path: 'servers', canActivate:[AuthGuard],component: ServersComponent, children: [
        {path: ':id', component: ServerComponent},
        {path: ':id/edit', component: EditServerComponent}
    ]
    },
    {path: 'not-found', component: PageNotFoundComponent},
    {path: '**', redirectTo: '/not-found'}
];

@NgModule({
    declarations: [
        AppComponent,
        HomeComponent,
        UsersComponent,
        ServersComponent,
        UserComponent,
        EditServerComponent,
        ServerComponent,
        PageNotFoundComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        RouterModule.forRoot(appRoutes)
    ],
    providers: [ServersService, AuthGuard, Auth],
    bootstrap: [AppComponent]
})
export class AppModule {
}

بسیار عالی همانطور که ملاحظه می‌کنید اگر روی زبانه «سرور» کلیک کنید پس از ۸۰۰ میلی‌ثانیه به زبانه‌ی «صفحه اصلی» منتقل می‌شوید. یعنی شما با اعمال یک محدودیت توانستید مسیرهای خود را کنترل کنید.

حال فرض کنید می‌خواهید یک مسیر فرزند مثل http://localhost:4200/servers/1/edit را تحت حفاظت امنیتی قرار دهید. در این صورت باید از دستور canActivateChild استفاده کنید. بنابراین ابتدا فایل auth-guard.service.ts را باز کرده و سپس تغییرات زیر را در آن لحاظ کنید:

import {ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot} from "@angular/router";
import {Observable} from "rxjs";
import {Injectable} from "@angular/core";
import {Auth} from "./auth.service";

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild{

    constructor(private auth: Auth, private router: Router){}

    canActivate(route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
        return this.auth.isAuthenticated()
            .then(
                (authentication: boolean)=>{
                    if(authentication){
                        return true;
                    }else{
                        this.router.navigate(['/']);
                    }
                }
            );
    }
    canActivateChild(route: ActivatedRouteSnapshot,
                     state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
            return this.canActivate(route, state);
    }
}

سپس فایل app.module.ts را به صورت زیر ویرایش کنید:

const appRoutes: Routes = [
    {path: '', component: HomeComponent},
    {
        path: 'users', component: UsersComponent, children: [
        {path: ':id/:name', component: UserComponent},
    ]
    },
    {
        path: 'servers', canActivateChild:[AuthGuard],component: ServersComponent, children: [
        {path: ':id', component: ServerComponent},
        {path: ':id/edit', component: EditServerComponent}
    ]
    },
    {path: 'not-found', component: PageNotFoundComponent},
    {path: '**', redirectTo: '/not-found'}
];

بسیار عالی در این حالت اگر شما روی زبانه‌ی «سرور» کلیک کنید در دسترس است ولی وقتی وارد یکی از اسامی سرور می‌شوید پس از ۸۰۰ میلی‌ثانیه مجددا به زبانه «صفحه اصلی» منتقل می‌شود. علت این امر تعیین سطح امنیت برای مسیر‌های فرزند است.

برای بهبود این مثال دو دکمه در صفحه اصلی به نام‌های ورود و خروج اضافه می‌کنیم که با کلیک کردن روی آنها متدهای logIn و logOut در سرویس auth اجرا شوند. تا با این کار بتوانیم به صورت پویا تر ورود یا عدم ورود یک کاربر را بررسی کنیم. بنابراین فایل home.component.html را باز کرده و به صورت زیر ویرایش می‌کنیم:

<h4>به نرم افزار مدیریت سرور خوش آمدید</h4>
<p>در این بخش کاربران و سرورها را کنترل می کنید</p>
<button class="btn btn-primary" (click)="onLoadServer()">بارگذاری سرور</button>
<button class="btn btn-primary" (click)="onLogIn()">ورود</button>
<button class="btn btn-primary" (click)="onLogOut()">خروج</button>

سپس وارد فایل home.component.ts شده تا این متدها را تعریف کنیم:

import {Component, OnInit} from '@angular/core';
import {Router} from "@angular/router";
import {Auth} from "../auth.service";

@Component({
    selector: 'app-home',
    templateUrl: './home.component.html',
    styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

    constructor(private router: Router, private authService: Auth) {
    }

    ngOnInit() {
    }

    onLoadServer() {
        this.router.navigate(['/servers']);
    }

    onLogIn() {
        this.authService.logIn();
    }

    onLogOut() {
        this.authService.logOut();
    }
}

در صورتیکه این کار را به درستی انجام داده باشید خروجی شما همانند تصویر زیر خواهد بود. حال با کلیک روی دکمه‌ی ورود می‌توانید وارد بخش سرور شوید و اگر روی خروج کلیک کنید این بخش از دسترس خارج می‌شود:

افزودن ورود و عضویت به مثال

یک نکته‌ی بسیار مهم در انتهای این فصل خدمت شما عزیزان مطرح می‌کنیم. اگر توجه کرده باشید تا به اینجای کار تمام آدرسهای ما به صورت http://localhost:4200/servers بود یعنی پس از نام دامنه‌ی لوکال سریعا نام کامپوننت موردنظر آورده می‌شود. این ادرس از نظر مسیردهی اگرچه در سیستم‌های شخصی شما جواب می‌دهد ولی وقتی شما وارد سرور اصلی خود در بستر اینترنت شوید آدرس http://www.roxo.ir/servers دیگر به نرم‌افزار انگولار اشاره نمی‌کند بلکه به مسیر مستقیم سرور و backend شما که با PHP یا Python یا ASP.net k نوشته شده است، اشاره خواهد کرد. بنابراین برای حل این مشکل باید در فایل app.module.ts مقدار عبارت useHash را برای ویژگی RouterModule برابر true قرار دهید. تا در اینصورت آدرس شما به صورت http://localhost:4200/#/servers شود. در این حالت دیگر frontend و backend شما از هم جدا می‌شود بنابراین داریم:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';

import {AppComponent} from './app.component';
import {HomeComponent} from './home/home.component';
import {UsersComponent} from './users/users.component';
import {ServersComponent} from './servers/servers.component';
import {UserComponent} from './users/user/user.component';
import {EditServerComponent} from './servers/edit-server/edit-server.component';
import {ServerComponent} from './servers/server/server.component';
import {ServersService} from './servers/servers.service';
import {RouterModule, Routes} from "@angular/router";
import {PageNotFoundComponent} from './page-not-found/page-not-found.component';
import {AuthGuard} from "./auth-guard.service";
import {Auth} from "./auth.service";

const appRoutes: Routes = [
    {path: '', component: HomeComponent},
    {
        path: 'users', component: UsersComponent, children: [
        {path: ':id/:name', component: UserComponent},
    ]
    },
    {
        path: 'servers', canActivateChild:[AuthGuard],component: ServersComponent, children: [
        {path: ':id', component: ServerComponent},
        {path: ':id/edit', component: EditServerComponent}
    ]
    },
    {path: 'not-found', component: PageNotFoundComponent},
    {path: '**', redirectTo: '/not-found'}
];

@NgModule({
    declarations: [
        AppComponent,
        HomeComponent,
        UsersComponent,
        ServersComponent,
        UserComponent,
        EditServerComponent,
        ServerComponent,
        PageNotFoundComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        RouterModule.forRoot(appRoutes, {useHash: true})
    ],
    providers: [ServersService, AuthGuard, Auth],
    bootstrap: [AppComponent]
})
export class AppModule {
}

بسیارعالی به شما عزیزان تبریک می‌گوییم. اگرچه فصل ۷ یک مقداری طولانی بود و ممکنه شما اذیت شده باشید اما تسلط به مفاهیم مسیردهی و Routing بسیار مهم است زیرا تمام آنچه در نرم‌افزارهای خود استفاده خواهید کرد همین موضوع است. در فصل بعدی به صورت مفصل به مباحث مربوط به مشاهده کننده‌ها یا Observable ها می‌پردازیم. با ما همراه باشید.

توجه: دوستان عزیز آموزش ویدیویی انگولار ۵ از مقدماتی تا پیشرفته به زبان فارسی را می‌توانید با کلیک روی اینجا یاد بگیرید. (این دوره در حال برگزاری است)

آموزش حرفه ای انگولار ۵ به زبان فارسی

 


فصل ۱

فصل ۲

فصل ۳:‌ خطایابی (Debugging) در انگولار ۴

فصل ۴

فصل ۵

فصل ۶

فصل ۷

فصل ۸: معرفی Observable یا مشاهده کننده‌ها در انگولار ۴

فصل ۹

فصل ۱۰: معرفی فیلترها یا Pipes در انگولار ۴

فصل ۱۱: معرفی درخواست‌های Http در انگولار ۴

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش رایگان انگولار توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

علی
19 فروردین 1398
سئوالی که اینجا مطرح است این است که آیا با وجود تکنولوژیهای authentication نظیر jwt باز هم به guard احتیاج است یا نه؟

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
19 فروردین 1398
توجه کنید که jwt برای برقراری ارتباط با سرور است ولی guard در انگولار کمک می کند تا شما بر اساس یک توکن که از سمت سرور دریافت می کنید سایر مسیرها را مدیریت بفرمایید. یعنی به عبارت دیگر اگر شما از گارد استفاده نکنید باید برای هر صفحه یک درخواست به سرور (backend) بدهید ولی اگر guard را بکار ببرید این فرآیند تنها یکبار صورت می پذیرد.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

علی
26 فروردین 1398
از پاسخ شما سپاسگزارم.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.