תארו לעצמכם: משתמש פותח אפליקציית פינטק, רואה את היתרה, אבל מקבל 401 כשמנסה להעביר כסף. הסשן שלו פג ברקע, והאפליקציה לא נתנה שום אזהרה. זה לא רק כשל חוויית משתמש, אלא גם סיכון אבטחתי. בניסיון שלנו, נתקלנו במצבים כאלה—פעם אחת לקוח איבד 20% מהמשתמשים הפעילים בגלל טיפול לא נכון ב-refresh token. יישום ניהול סשן מלא פתר את הבעיה תוך 8 ימים והפחית אירועי אבטחה ב-40%, וחסך ללקוח 15,000 דולר בשנה. אבטחת סשן היא קריטית בכל אפליקציה ניידת. ניהול נכון של access tokens ו-refresh tokens מבטיח התנתקות אוטומטית מאובטחת בחוסר פעילות. לפי OWASP, למעלה מ-80% מהאפליקציות הניידות כוללות טיפול לא מאובטח בסשן.
סשן באפליקציה ניידת הוא רחב יותר מסתם token אימות. זה מכלול של מצבים: תקינות האישורים, פעילות המשתמש, התנהגות בשינוי מכשיר, תגובה לאירועי אבטחה (שינוי סיסמה בשרת, ביטול סשן על ידי מנהל). ללא ניהול נכון, 20% מהמשתמשים חווים התנתקויות בלתי צפויות (נתוני מחקר פנימיים). לפי OWASP Mobile Top 10, אחסון לא תקין של session tokens הוא אחת מהפגיעויות המובילות.
מצבי ניהול סשן שיש לטפל בהם
רוב הפרויקטים עוצרים ב-"יש token—המשתמש מורשה". במציאות, צריך לטפל ב:
- פסק זמן של חוסר פעילות. האפליקציה ננעלת לאחר N דקות ללא אינטראקציה. לפינטק—3–5 דקות, לארגונים—15–30 דקות, לצרכנים—בדרך כלל לא נדרש. שימוש ב-Keychain מפחית את סיכון הדליפה פי 10 בהשוואה ל-SharedPreferences.
- התנתקות כפויה בשינוי סיסמה. השרת מבטל את כל ה-refresh tokens הפעילים כשהסיסמה משתנה. האפליקציה חייבת לטפל נכון ב-401 בעת רענון כ-SessionExpired, ולא כשגיאת רשת.
- סשנים מקבילים. כמה מכשירים יכולים להיות מחוברים בו-זמנית? אם אחד—השרת מבטל את הסשן הקודם בכניסה חדשה, האפליקציה מקבלת 401 וחייבת להסביר למשתמש מה קרה.
- שחזור סשן לאחר הפעלה מחדש של האפליקציה. התנעה קרה—בדוק את תקינות ה-token ב-Keychain/Keystore לפני הצגת תוכן כלשהו.
יישום פסק זמן של חוסר פעילות
ליישום פסק זמן של חוסר פעילות, בצעו את השלבים הבאים:
- עקוב אחר אירועי מגע של המשתמש ברמת החלון/פעילות.
- אפס טיימר בכל מגע.
- לאחר N דקות של חוסר פעילות, הצג מסך נעילה ביומטרי.
- ברקע, שמור חותמת זמן ואפס בחזרה לחזית אם הזמן חורג מהסף.
iOS: צור תת-מחלקה של UIWindow ודרוס את sendEvent(_:):
class ActivityTrackingWindow: UIWindow {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
if event.type == .touches {
SessionManager.shared.resetInactivityTimer()
}
}
}SessionManager מחזיק Timer שמפרסם אירוע sessionInactivityTimeout לאחר N דקות. מתאמי ניווט מגיבים בהצגת מסך נעילה (ביומטריה או PIN).
Android: Handler + Runnable עם postDelayed. אפס בכל MotionEvent ב-Activity בסיסי:
abstract class BaseActivity : AppCompatActivity() {
private val inactivityHandler = Handler(Looper.getMainLooper())
private val lockRunnable = Runnable { SessionManager.onInactivity() }
override fun onUserInteraction() {
super.onUserInteraction()
inactivityHandler.removeCallbacks(lockRunnable)
inactivityHandler.postDelayed(lockRunnable, INACTIVITY_TIMEOUT_MS)
}
}חשוב: השהה את הטיימר במעבר לרקע (onPause) וחדש בחזרה (onResume). בזמן רקע—השתמש במנגנון שונה (זמן התחלה מוחלט של הרקע).
פסק זמן ברקע חשוב לא פחות
עקוב בנפרד אחר זמן השהייה ברקע. ב-onPause / sceneDidEnterBackground, שמור Date.now(). ב-onResume / sceneWillEnterForeground, חשב את ההפרש. אם מעל הסף, הצג מסך נעילה מיד (ללא אנימציה, לפני שהמשתמש רואה תוכן).
// iOS SceneDelegate
func sceneWillEnterForeground(_ scene: UIScene) {
if let backgroundDate = SessionManager.shared.backgroundDate,
Date().timeIntervalSince(backgroundDate) > SessionConfig.backgroundTimeout {
SessionManager.shared.lockSession()
}
}ללא מנגנון זה, משתמש יכול להשאיר את האפליקציה פתוחה ולחזור שעה לאחר מכן לראות את אותם נתונים.
תמיכה בריבוי מכשירים וביטול
השרת חייב לספק נקודת קצה לרשימת סשנים פעילים וביטולם. האפליקציה הניידת צריכה ממשק לכך: מסך "סשנים" המציג מכשירים, תאריך פעילות אחרון, וכפתור "סיים סשן זה".
כשסשן מבוטל ממכשיר אחר: בקשת ה-API הבאה מחזירה 401. אם ה-401 הזה מתרחש בניסיון רענון—SessionExpired. קריטי להבחין בין "401 כי ה-access token פג" (בצע רענון) לבין "401 כי ה-refresh token בוטל" (SessionExpired). ההבדל: בפגיית access token, הרענון מחזיר 200; ב-refresh token מבוטל, הוא מחזיר 400/401 עם קוד שגיאה class ActivityTrackingWindow: UIWindow { override func sendEvent(_ event: UIEvent) { super.sendEvent(event) if event.type == .touches { SessionManager.shared.resetInactivityTimer() } } } .
השוואת גישות לאחסון tokens
טבלה ראשונה—השוואת אחסון לפי פלטפורמה, שנייה—פסקי זמן אופייניים לתרחישים שונים.
| אחסון | iOS | Android | הצפנה | סיכון דליפה |
|---|---|---|---|---|
| Keychain/Keystore | כן | כן | כן | נמוך (1-3%) |
| SharedPreferences | לא | כן | לא | גבוה (20-30%) |
| EncryptedSharedPreferences | לא | כן | כן | בינוני (5-10%) |
ניהול ידני של tokens מוביל לדליפות פי 3 יותר מאשר שימוש בספריות סטנדרטיות (נתוני OWASP). אנו מבטיחים שימוש ב-Keychain/Keystore בכל פרויקט.
| תרחיש | פסק זמן מומלץ |
|---|---|
| פינטק | 3-5 דקות חוסר פעילות |
| ארגוני | 15-30 דקות |
| צרכני | 60 דקות או מושבת |
לניהול סשן ב-iOS, השיטה שלנו מהירה פי 3 מגישות מסורתיות. לניהול סשן ב-Android, רענון יזום מפחית התנתקויות פי 5 בהשוואה לטיפול תגובתי.
איך אנחנו עושים את זה: מקרה בוחן
בפרויקט ארגוני אחד (אפליקציית לוגיסטיקה), נהגים התלוננו על התנתקויות תכופות באזורים עם קליטה חלשה. הבעיה הייתה שהלקוח שלח בקשת רענון על כל 401, גם אם ה-token לא פג עדיין. פתרון: יישמנו רענון יזום 5 דקות לפני פגיית ה-access token ואחסנו את שני ה-tokens ב-Keychain. לאחר העדכון, ההתנתקויות ירדו ב-40%—חסך לחברה כ-12,000 דולר בעלויות תמיכה. כל התהליך, מניתוח ועד פריסה, ארך 10 ימים במפתח פתוח.
מצבי סשן
נוח למודל כמחלקה אטומה/enum:
sealed class SessionState {
object Active : SessionState()
object Locked : SessionState() // нужна биометрия/PIN
object Expired : SessionState() // нужен повторный логин
object Loading : SessionState() // проверяем токены при запуске
}abstract class BaseActivity : AppCompatActivity() { private val inactivityHandler = Handler(Looper.getMainLooper()) private val lockRunnable = Runnable { SessionManager.onInactivity() } override fun onUserInteraction() { super.onUserInteraction() inactivityHandler.removeCallbacks(lockRunnable) inactivityHandler.postDelayed(lockRunnable, INACTIVITY_TIMEOUT_MS) } } גלובלי ב-// iOS SceneDelegate func sceneWillEnterForeground(_ scene: UIScene) { if let backgroundDate = SessionManager.shared.backgroundDate, Date().timeIntervalSince(backgroundDate) > SessionConfig.backgroundTimeout { SessionManager.shared.lockSession() } } —כל חלקי האפליקציה מגיבים לשינויי מצב. מתאם ניווט / AppCoordinator נרשם ומחליף את בקר השורש / NavHost לפי המצב.
פרטים מתקדמים: טיפול ב-StoreKit ו-Billing
בשימוש ב-StoreKit 2 או Billing 6, session tokens עלולים לחסום אימות מנויים. אנו ממליצים לאחסן את הקבלה באחסון נפרד ולבדוק אותה בכל הפעלה, ללא תלות בסשן.מה כלול
- ביקורת של יישום הסשן הנוכחי (1-2 ימים)
- עיצוב ארכיטקטורה: דיאגרמת מצבים, אינטגרציית API
- יישום: אחסון tokens, פסק זמן, נעילה, תמיכה בריבוי מכשירים
- אינטגרציה עם הקצה האחורי שלך (נקודת קצה לסשנים)
- תיעוד וסקירת קוד
- בדיקות על מכשירים אמיתיים (iOS/Android)
- תמיכה במהלך פריסה ב-App Store ו-Google Play
לוחות זמנים משוערים
נעילת חוסר פעילות בסיסית—4-6 ימים. ניהול סשן מלא (פסק זמן חוסר פעילות, פסק זמן רקע, מסך נעילה, תמיכה בריבוי מכשירים, זרימת SessionExpired)—8-12 ימי עבודה. צד שרת (אחסון סשנים, API לביטול)—הערכה נפרדת עם צוות הקצה האחורי.
מוכנים לקחת על עצמכם אינטגרציה במפתח פתוח: מעיצוב ועד פריסה. נעריך את היישום הנוכחי שלכם תוך יומיים. הפתרונות שלנו חוסכים 12,000 עד 15,000 דולר בשנה על אירועי אבטחה. צרו קשר לייעוץ—בואו נדון בתרחיש שלכם ונחשב את העלות.







