در مقالاتی که در سایت منتشر شدهاست، آشنایی و همچنین نحوه پیاده سازی
Json Web Token را فرا گرفتیم. در اینجا میخواهیم با استفاده از توکن تولید شده، برنامههای Angular2 یا هر نوع فریمورک spa را با آن ارتباط دهیم. در سایت جاری قبلا در مورد
نحوه پیاده سازی آن صحبت شدهاست و میخواهیم از آن در یک پروژه Angular 2 صحبت کنیم.
پروژه دات نت را از طریق این آدرس دریافت کرده و آن را در حالت اجرا بگذارید.
سپس یک سرویس جدید را در پروژه Angular خود اجرا کنید و متد زیر را به آن اضافه کنید:
login():any{
let urlSearchParams = new URLSearchParams();
urlSearchParams.append('username', 'Vahid');
urlSearchParams.append('password', '1234');
urlSearchParams.append('grant_type', 'password');
let body = urlSearchParams.toString();
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
return this._http.post('http://localhost:9577/login', body, { headers: headers })
.map(res => res.json());
}
در متد بالا ابتدا از کلاس URLSearchParams یک شیء میسازیم. این کلاس در ماژول Http قرار دارد و وظیفه آن تبدیل پارامترهای داده شده به شکل زیر میباشد:
username=vahid&password=1234
دلیل اینکه ما در اینجا همانند jquery از JSON.stringify استفاده نکردیم این است که در حالتیکه خصوصیت Content-Type هدر را بر روی application/x-wwww-form-urlencoded قرار میدهیم، شیء ایجاد شده از کلاس Http در اینجا، کل عبارت را تبدیل به کلید بدون مقدار میکند و باعث میشود که پارامترها به درستی به سمت Owin هدایت نشوند. بعد از آن هدری که ذکر شد را در درخواست قرار داده و آن را ارسال میکنیم.
از آنجاکه پروژه انگیولار ساخته شده در آدرسی دیگر و جدا از پروژهی دات نت قرار دارد و همینطور که میبینید آدرس کامل آن را به این دلیل قرار دادم، ممکن است خطای CORS را دریافت کنید که میتوانید آن را با
نصب یک بسته نیوگتی حل کنید.
همچنین برای تست و انجام یک عمل مرتبط با توکن، متد زیر را هم به آن اضافه میکنیم:
ApiAdmin(token:any):any{
let headers = new Headers();
headers.append('Authorization', 'bearer ' + token);
return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers })
.map(res => res.json());
}
در اینجا با استفاده از روش Http Bearer که در اعتبارسنجی در سطح OAuth کاربرد زیادی دارد، توکن تولید شده را که در پارامتر ورودی متد دریافت کردهایم، به هدر اضافه کرده و آن را ارسال میکنیم.
کد کل سرویس، الان به شکل زیر شده است:
import { Http, Headers, URLSearchParams } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from "rxjs/Observable";
import "rxjs/Rx";
@Injectable()
export class MemberShipService {
constructor(private _http: Http) { }
ApiAdmin(token: any): any {
let headers = new Headers();
headers.append('Authorization', 'bearer ' + token);
return this._http.get('http://localhost:9577/api/MyProtectedApi', { headers: headers })
.map(res => res.json());
}
login(): any {
let urlSearchParams = new URLSearchParams();
urlSearchParams.append('username', 'Vahid');
urlSearchParams.append('password', '1234');
urlSearchParams.append('grant_type', 'password');
let body = urlSearchParams.toString();
let headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
return this._http.post('http://localhost:9577/login', body, { headers: headers })
.map(res => res.json());
}
}
سپس سرویس ساخته شده را در ngModule معرفی میکنیم. در کامپوننت هدف دو دکمه را قرار میدهیم؛ یکی برای لاگین و دیگری برای دریافت اطلاعاتی که نیاز به اعتبار سنجی دارد. رویداد کلیک دکمهها را به متدهای زیر متصل میکنیم:
Login(){
this._service.login()
.subscribe(res => {
localStorage.setItem('access_token', res.access_token);
localStorage.setItem('refresh_token', res.refresh_token);
}
, error => console.log(error));
}
در اینجا ما اطلاعات لاگین را به سمت سرور فرستاده و در صورتیکه پاسخ صحیحی را دریافت کردیم، متد Subscribe اجرا خواهد شد و مقادیر دریافتی را باید به نحوی در سیستم و بین کامپوننتهای مختلف، ذخیره و نگهداری کنیم. در اینجا من از روش
Local Storage که در سایت جاری قبلا به آن پرداخته شدهاست، استفاده میکنم. access_token و refresh_token جاری و اطلاعات دیگری را چون رولها و ... هر یک را با یک کلید ذخیره میکنیم تا بعدا به آن دسترسی داشته باشیم.
در متد بعدی که قرار است توسط آن صحت اعتبارسنجی مورد آزامایش قرار بگیرد، کدهای زیر را مینویسیم:
CallApi()
{
let item = localStorage.getItem("access_token");
if (item == null) {
alert('please Login First.');
return;
}
this._service.ApiAdmin(item)
.subscribe(res => {
alert(res);
}
, error => console.log(error));
}
در اینجا ابتدا توکن ذخیره شده را از Local Storage درخواست میکنیم. اگر نتیجه این درخواست نال باشد، به این معنی است که کاربر قبلا لاگین نکرده است؛ در غیر این صورت درخواست را با توکن دریافتی میفرستیم و پاسخ موفق آن را در یک alert میبینیم. در صورتیکه توکن ما اعتبار نداشته باشد، خطای بازگشتی در کنسول نمایش خواهد یافت.
اعتبارسنجی در مسیریابی
یکی از روشهایی که انگیولار باید بررسی کند این است که کاربر جاری با توجه به نقشهایی که ممکن است داشته باشد، یا اعتبار سنجی شده است یا خیر و میزان دسترسی او به کامپوننتها، باید مشخص گردد. برای این مورد خصوصیتی به مسیریابی اضافه شده است به نام CanActivate که از شما یک کلاس را که در آن اینترفیس CanActivate پیاده سازی شده است، درخواست میکند. در اینجا ما یک Guard را با نام LoginGuard ایجاد میکنیم، تا تنها کاربرانی که لاگین کردهاند، به این صفحه دسترسی داشته باشند:
import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';
@Injectable()
export class LoginGuard implements CanActivate {
constructor() { }
canActivate() {
let item = localStorage.getItem('access_token');
if (item == null)
return false;
return true;
}
}
در متد Activate باید خروجی boolean بازگردد. در صورتیکه true بازگشت داده شود، عملیات هدایت به کامپوننت مقصد با موفقیت انجام خواهد شد. در صورتیکه خلاف این موضوع اتفاق بیفتد، کامپوننت هدف نمایش داده نمیشود. در اینجا بررسی کردهایم که اگر توکن مورد نظر در localStorage قرار داشت، به معنی این است که لاگین شدهاست. ولی این موضوع روشن است که در یک مثال واقعی باید صحت این توکن بررسی شود. این Guard در واقع یک سرویس است که باید در فایل ngModule معرفی شده و آن را در فایل مسیریابی به شکل زیر استفاده کنیم:
{ path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard]}
در معرفی یک مسیر به فایل مسیریابی، خصوصیاتی چون مسیری که نوشته میشود و کامپوننت مربوط به آن مسیر ذکر میشود. خصوصیت دیگر، CanActivate است که به آن کلاس LoginGuard را معرفی مکنیم. در اینجا شما میتواند به هر تعداد گارد را که دوست دارید، معرفی کنید ولی به یاد داشته باشید که اگر یکی از آنها در اعتبارسنجی ناموفق باشد، دیگر کامپوننت جاری در دسترس نخواهد بود. به معنای دیگر تمامی گاردها باید نتیجهی true را بازگردانند تا دسترسی به کامپوننت هدف ممکن شود.
{ path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard]}
در یک گارد ممکن است کاربر لاگین نکرده باشد و شما نیاز دارید او را به صفحه لاگین هدایت کنید. در این صورت گارد لاگین را به شکل زیر بازنویسی میکنیم:
import { Router } from '@angular/router';
import { CanActivate } from '@angular/router';
import { Injectable } from '@angular/core';
@Injectable()
export class LoginGuard implements CanActivate {
constructor(public router: Router) { }
canActivate() {
let item = localStorage.getItem('access_token');
if (item == null) {
this.router.navigate(['/app']);
return false;
}
return true;
}
}
در اینجا ما از سازنده کلاس، شیءایی از نوع Router را ساختیم که امکانات و متدهای خوبی را در باب مسیریابی به همراه دارد و از آن برای انتقال به مسیری دیگر که میتواند صفحه لاگین باشد، استفاده کردیم.
همچنین گارد میتواند اطلاعات مسیر درخواست شده را خوانده و بر اساس آن به شما پاسخ بدهد. به عنوان مثال پارامترهایی را که به سمت مسیر مورد نظر هدایت میشود، بخواند و بر اساس آن، تصمیم گیری کند. به عنوان نمونه کاربر به مسیر ذکرشده دسترسی دارد، ولی با Id که در مسیر ارسال کرده است، دسترسی ندارد:
import { Router } from '@angular/router';
import { CanActivate, ActivatedRouteSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
@Injectable()
export class SecondGuard implements CanActivate {
constructor(public router: Router) { }
canActivate(route: ActivatedRouteSnapshot) {
let id = route.params['id'];
if (id == 1) {
return false;
}
return true;
}
}
متد CanActivate میتواند پارامترهای زیادی را به عنوان ورودی دریافت کند که یکی از آنها
ActivatedRouteSnapshot است که اطلاعات خوب و مفیدی را در مورد مسیر ارسال شده از طرف کاربر دارد و با استفاده از آن در اینجا میتوانیم پارامترهای ارسالی را دریافت کنیم. در اینجا ذکر کردهایم که اگر پارامتر Id که بر مبنای مسیر زیر است، برابر 1 بود، مقدار برگشتی برابر false خواهد بود و دسترسی به کامپوننت در اینجا ممکن نخواهد بود.
{ path: 'component-one/:id', component: Component1Component,canActivate:[LoginGuard,SecondGuard] }