منتقضی کردن jwt


سلام . چند وقت پیش ، توی یه پروژه من نیاز به این پیدا کردم که jwt token های یک کاربر رو منقضی کنم . ولی اگر با jwt آشنایی داشته باشید می دونید که توکن ها اصلا هیچ جا ذخیره نمیشن و هر کاربر میتونه چندین توکن valid (معتبر) داشته باشه .

پس در ابتدای راه با چند تا چالش همراه بودم : ۱- اینکه هر کاربر چند تا توکن داره ۲- توکن ها جایی ذخیره نمیشن که بشه اون ها رو گرفت ۳- توکن ها فقط و فقط وقتی منقضی میشن که expire بشن

من با یه تریک تونستم این مشکل رو حل کنم که اینجا براتون توضیح میدم .

اگر که میپرسید اصلا چرا نیاز به منقضی کردن توکن هست ، پایین تر توضیح دادم .

اگر نمیدونی jwt چیه روی ایموجی روبرو کلیک کن ??

من از node استفاده کردم و توضیحاتی هم که میدم با node هست

برای ساخت token هم از پکیج jsonwebtoken استفاده میکنم که میتونید از اینجا ببینید

خوب اول از همه توکن خودم رو میسازم :

jwt.sign({ user_id: item._id , permissions : this.getPerms(item).permissions}, config.secret , { expiresIn: '110h'});

توکن من شامل user_id و permission های کاربر هست که شما کاری به مقدارش نداشته باشید . ولی اصلا دلیل این که من نیاز به منقضی کردن توکن های کاربر داشتم همین بود که میخواستم دسترسی های کاربر رو توی توکن بفرستم که نیازی نباشه هر بار با دیتابیس چک کنم . ولی اگر تغییری توی دسترسی های کاربر میدادم ، توکن های اون کاربر هنوز هم معتبر بود و دسترسی های قبلی تغییری نمیکرد . ممکن هست که جا های دیگه هم شما نیاز به این روش داشته باشید که امیدوارم که بدرد بخور باشه براتون ?

یه توضیح میدم که این توکن شامل چی هست :

اول user_id هست که با توجه با توضیحاتی که بالا دادم توکن شما حتما باید داشته باشه آی دی یوزر رو ،

دوم من permission ها رو گذاشتم که دلیلش رو بالا گفتم

هiflcd:

هم که باید یه رشته از کاراکتر های مختلف تعریف کنید ، و مورد آخر هم که تایم منقضی شدن توکن هست

خوب تا اینجا برای یوزرمون ، توکن هایی رو ساختیم .

خوب از اینجا به بعد روند کار به این شکل هست که ما اول توی دیتابیس یه جدول میسازیم که user_id کاربر هایی که بلاک میخوان بشن رو داخل اون ذخیره کنیم . یه مثال میزنم که قضیه رو بهتر درک کنید :

مثلا من دسترسی های کاربر با آی دی ۱ رو تغییر دادم . بعد از اینکه دسترسی ها تغییر کرد میام و آی دی کاربر رو توی جدول کاربران بلاک شده ای که ساختم ذخیره میکنم .

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const timestamps = require('mongoose-timestamp');
const BlockedUsers = new Schema({
user: { type: Schema.Types.ObjectId, ref: 'User' },
});
BlockedUsers.plugin(timestamps);
module.exports = mongoose.model('BlockedUsers' , BlockedUsers);

کد بالا اسکیما جدول BlockedUsers من هست .

و جایی که تغییرات رو انجام میدم به شکل زیر آی دی کاربر رو به جدول اضافه میکنم :

 BlockedUsers.findOne({user : req.user._id} , (err , res)=>{
 if(!res){
    GlobalBlocked.push(req.user._id)
    new BlockedUsers({user : req.user._id}).save() }
})

خوب پس تا اینجا ما کاربری که میخواستیم توکن هاش رو منقضی کنیم رو توی جدول داریم

من برای اینکه نخوام هر بار که میخواد کاربر درخواستی بده به سمت سرور ، با دیتابیس چک بشه که این کاربر توی کاربران بلاک شده هست یا نه اومدم و در زمان run شدن برنامه یک متغیر گلوبال تعریف کردم که زمان اجرا مقدار رو از دیتابیس میگیره :

global.GlobalBlocked = [];
BlockedUsers.find({}, (err, res) => {
    res.forEach(item => {
        GlobalBlocked.push(item.user)
})
})

پس الان توی متغیری که گذاشتم ، آی دی کاربران بلاک شده هست

مرحله بعد این هست که یه میدلویر تعریف کنید که چک بشه توکنی که اومده ، یوزر آی دی داخل توکن توی این متغیر نباشه و اگر بود باید از اول لاگین کنه .

پس برای اینکار اول باید توکن رو decode کنیم بعد از داخلش user_id رو بگیریم و با متغیرمون مقایسه کنیم :

jwt.verify(token, config.secret, (err, decode) => {
    if(GlobalBlocked.find(user => decode.user_id)){
        return res.status(401).json({
            data: 'شما مجددا باید احراز هویت شوید',
            success: false
})
}
)}

خوب فقط یه مرحله دیگه مونده اون هم اینه که وقتی که کاربر لاگین کرد اون رو از متغیر و دیتابیسی که برای کاربران بلاک شده ساختیم بیاریم بیرون چون توکن جدیدی گرفته که شامل اطلاعات درسته :

if(GlobalBlocked.find(user => user._id)){
    for( var i = 0; i < GlobalBlocked.length; i++){
        if (JSON.stringify(user._id) === JSON.stringify(GlobalBlocked[i])) {
            GlobalBlocked.splice(i, 1);
}
}
BlockedUsers.deleteOne({user: user._id} , (err , res)=>{
//deleted
})

تکه کد بالا باید جایی باشه که کاربر میخواد لاگین کنه

ممنون که تا اینجا خوندی . اگر که نظر ، پیشنهاد یا روش بهتری داری ممنون میشم که مطرح کنی . ✌