در قسمت قبل، عملیات ورود به سیستم و خروج از آنرا تکمیل کردیم. پس از ورود شخص به سیستم، هربار انقضای توکن دسترسی او، سبب خواهد شد تا وقفهای در کار جاری کاربر، جهت لاگین مجدد صورت گیرد. برای این منظور، قسمتی از مطالب «
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity»
و یا «
پیاده سازی JSON Web Token با ASP.NET Web API 2.x»
به تولید refresh_token در سمت سرور اختصاص دارد که از نتیجهی آن در اینجا استفاده خواهیم کرد. عملیات به روز رسانی خودکار توکن دسترسی (access_token در اینجا) سبب خواهد شد تا کاربر پس از انقضای آن، نیازی به لاگین دستی مجدد نداشته باشد. این به روز رسانی در پشت صحنه و به صورت خودکار صورت میگیرد. refresh_token یک guid است که به سمت سرور ارسال میشود و برنامهی سمت سرور، پس از تائید آن (بررسی صحت وجود آن در بانک اطلاعاتی و سپس واکشی اطلاعات کاربر متناظر با آن)، یک access_token جدید را صادر میکند.
ایجاد یک تایمر برای مدیریت دریافت و به روز رسانی توکن دسترسی
در مطلب «
ایجاد تایمرها در برنامههای Angular» با روش کار با تایمرهای reactive آشنا شدیم. در اینجا قصد داریم از این امکانات جهت پیاده سازی به روز کنندهی خودکار access_token استفاده کنیم. در مطلب «
احراز هویت و اعتبارسنجی کاربران در برنامههای Angular - قسمت دوم - سرویس اعتبارسنجی»، زمان انقضای توکن را به کمک کتابخانهی jwt-decode، از آن استخراج کردیم:
getAccessTokenExpirationDateUtc(): Date {
const decoded = this.getDecodedAccessToken();
if (decoded.exp === undefined) {
return null;
}
const date = new Date(0); // The 0 sets the date to the epoch
date.setUTCSeconds(decoded.exp);
return date;
}
اکنون از این زمان در جهت تعریف یک تایمر خود متوقف شونده استفاده میکنیم:
private refreshTokenSubscription: Subscription;
scheduleRefreshToken() {
if (!this.isLoggedIn()) {
return;
}
this.unscheduleRefreshToken();
const expiresAtUtc = this.getAccessTokenExpirationDateUtc().valueOf();
const nowUtc = new Date().valueOf();
const initialDelay = Math.max(1, expiresAtUtc - nowUtc);
console.log("Initial scheduleRefreshToken Delay(ms)", initialDelay);
const timerSource$ = Observable.timer(initialDelay);
this.refreshTokenSubscription = timerSource$.subscribe(() => {
this.refreshToken();
});
}
unscheduleRefreshToken() {
if (this.refreshTokenSubscription) {
this.refreshTokenSubscription.unsubscribe();
}
}
کار متد scheduleRefreshToken، شروع تایمر درخواست توکن جدید است.
- در ابتدا بررسی میکنیم که آیا کاربر لاگین کردهاست یا خیر؟ آیا اصلا دارای توکنی هست یا خیر؟ اگر خیر، نیازی به شروع این تایمر نیست.
- سپس تایمر قبلی را در صورت وجود، خاتمه میدهیم.
- در مرحلهی بعد، کار محاسبهی میزان زمان تاخیر شروع تایمر Observable.timer را انجام میدهیم. پیشتر زمان انقضای توکن موجود را استخراج کردهایم. اکنون این زمان را از زمان جاری سیستم برحسب UTC کسر میکنیم. مقدار حاصل، initialDelay این تایمر خواهد بود. یعنی این تایمر به مدت initialDelay صبر خواهد کرد و سپس تنها یکبار اجرا میشود. پس از اجرا، ابتدا متد refreshToken ذیل را فراخوانی میکند تا توکن جدیدی را دریافت کند.
در متد unscheduleRefreshToken کار لغو تایمر جاری در صورت وجود انجام میشود.
متد درخواست یک توکن جدید بر اساس refreshToken موجود نیز به صورت ذیل است:
refreshToken() {
const headers = new HttpHeaders({ "Content-Type": "application/json" });
const model = { refreshToken: this.getRawAuthToken(AuthTokenType.RefreshToken) };
return this.http
.post(`${this.appConfig.apiEndpoint}/${this.appConfig.refreshTokenPath}`, model, { headers: headers })
.finally(() => {
this.scheduleRefreshToken();
})
.map(response => response || {})
.catch((error: HttpErrorResponse) => Observable.throw(error))
.subscribe(result => {
console.log("RefreshToken Result", result);
this.setLoginSession(result);
});
}
در اینجا هرزمانیکه تایمر اجرا شود، این متد فراخوانی شده و مقدار refreshToken فعلی را به سمت سرور ارسال میکند. سپس سرور این مقدار را بررسی کرده و در صورت تعیین اعتبار، یک access_token و refresh_token جدید را به سمت کلاینت ارسال میکند که نتیجهی آن به متد setLoginSession جهت ذخیره سازی ارسال خواهد شد.
در آخر چون این تایمر، خود متوقف شوندهاست (متد Observable.timer بدون پارامتر دوم آن فراخوانی شدهاست)، یکبار دیگر کار زمانبندی دریافت توکن جدید بعدی را در متد finally انجام میدهیم (متد scheduleRefreshToken را مجددا فراخوانی میکنیم).
تغییرات مورد نیاز در سرویس Auth جهت زمانبندی دریافت توکن دسترسی جدید
تا اینجا متدهای مورد نیاز شروع زمانبندی دریافت توکن جدید، خاتمهی زمانبندی و دریافت و به روز رسانی توکن جدید را پیاده سازی کردیم. محل قرارگیری و فراخوانی این متدها در سرویس Auth، به صورت ذیل هستند:
الف) در سازندهی کلاس:
constructor(
@Inject(APP_CONFIG) private appConfig: IAppConfig,
private browserStorageService: BrowserStorageService,
private http: HttpClient,
private router: Router
) {
this.updateStatusOnPageRefresh();
this.scheduleRefreshToken();
}
این مورد برای مدیریت حالتی که کاربر صفحه را refresh کردهاست و یا پس از مدتی مجددا از ابتدا برنامه را بارگذاری کردهاست، مفید است.
ب) پس از لاگین موفقیت آمیز
در متد لاگین، پس از دریافت یک response موفقیت آمیز و تنظیم و ذخیره سازی توکنهای دریافتی، کار زمانبندی دریافت توکن دسترسی بعدی بر اساس refresh_token فعلی انجام میشود:
this.setLoginSession(response);
this.scheduleRefreshToken();
ج) پس از خروج از سیستم
در متد logout، پس از حذف توکنهای کاربر از کش مرورگر، کار لغو تایمر زمانبندی دریافت توکن بعدی نیز صورت خواهد گرفت:
this.deleteAuthTokens();
this.unscheduleRefreshToken();
در این حالت اگر برنامه را اجرا کنید، یک چنین خروجی را که بیانگر دریافت خودکار توکنهای جدید است، پس از مدتی در کنسول developer مرورگر مشاهده خواهید کرد:
ابتدا متد scheduleRefreshToken اجرا شده و تاخیر آغازین تایمر محاسبه شدهاست. پس از مدتی متد refreshToken توسط تایمر فراخوانی شدهاست. در آخر مجددا متد scheduleRefreshToken جهت شروع یک زمانبندی جدید، اجرا شدهاست.
اعداد initialDelay محاسبه شدهای را هم که ملاحظه میکنید، نزدیک به همان 2 دقیقهی تنظیمات سمت سرور در فایل appsettings.json هستند:
"BearerTokens": {
"Key": "This is my shared key, not so secret, secret!",
"Issuer": "http://localhost/",
"Audience": "Any",
"AccessTokenExpirationMinutes": 2,
"RefreshTokenExpirationMinutes": 60
}
کدهای کامل این سری را از اینجا میتوانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژهی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o، برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشهی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.