Android Token的原理和本地安全存储

发布于:2025-03-24 ⋅ 阅读:(29) ⋅ 点赞:(0)

Android Token的原理和本地安全存储

前言

在移动应用开发中,Token是实现用户身份验证和授权的重要机制。本文将深入介绍Token的原理,以及在Android平台上如何安全地存储Token,帮助开发者构建可靠的身份验证系统。

基础知识

1. Token概述

1.1 Token的作用
  • 身份验证
  • 授权访问
  • 无状态设计
1.2 Token类型
  • Access Token
  • Refresh Token
  • JWT (JSON Web Token)

实战案例

1. 电商App用户认证场景

在电商App中,Token主要用于用户登录认证和授权。以下是一个完整的实现示例:

public class ECommerceTokenManager {
    private static final long SESSION_TIMEOUT = 1800000; // 30分钟会话超时
    private final TokenManager tokenManager;
    private final UserPreferences userPreferences;

    public ECommerceTokenManager(Context context, ApiService apiService) {
        this.tokenManager = new TokenManager(context, apiService);
        this.userPreferences = new UserPreferences(context);
    }

    public void handleLogin(String username, String password, LoginCallback callback) {
        // 1. 登录验证
        apiService.login(username, password, new Callback<LoginResponse>() {
            @Override
            public void onSuccess(LoginResponse response) {
                // 2. 保存Token
                tokenManager.saveToken(response.getAccessToken());
                userPreferences.saveLastLoginTime(System.currentTimeMillis());
                callback.onLoginSuccess();
            }

            @Override
            public void onError(Exception e) {
                callback.onLoginError(e);
            }
        });
    }

    public void checkSessionStatus() {
        // 检查会话状态
        long lastLoginTime = userPreferences.getLastLoginTime();
        if (System.currentTimeMillis() - lastLoginTime > SESSION_TIMEOUT) {
            // 会话超时,需要重新登录
            handleSessionTimeout();
        }
    }

    private void handleSessionTimeout() {
        tokenManager.removeToken();
        // 跳转到登录页面
        navigateToLogin();
    }
}

2. 社交App多设备登录控制

社交应用通常需要处理多设备登录的情况,以下是一个Token管理示例:

public class SocialAppTokenManager {
    private static final int MAX_DEVICES = 3; // 最大登录设备数
    private final TokenManager tokenManager;
    private final DeviceManager deviceManager;

    public SocialAppTokenManager(Context context, ApiService apiService) {
        this.tokenManager = new TokenManager(context, apiService);
        this.deviceManager = new DeviceManager(context);
    }

    public void handleDeviceLogin(String deviceId, LoginCallback callback) {
        // 1. 检查设备数量
        deviceManager.getActiveDevices(new Callback<List<DeviceInfo>>() {
            @Override
            public void onSuccess(List<DeviceInfo> devices) {
                if (devices.size() >= MAX_DEVICES) {
                    // 超过最大设备数,强制登出最早的设备
                    DeviceInfo oldestDevice = findOldestDevice(devices);
                    forceLogout(oldestDevice.getDeviceId());
                }
                
                // 2. 注册新设备
                registerNewDevice(deviceId, callback);
            }
        });
    }

    private void registerNewDevice(String deviceId, LoginCallback callback) {
        deviceManager.registerDevice(deviceId, new Callback<String>() {
            @Override
            public void onSuccess(String token) {
                tokenManager.saveToken(token);
                callback.onLoginSuccess();
            }

            @Override
            public void onError(Exception e) {
                callback.onLoginError(e);
            }
        });
    }

    public void handleForceLogout(String deviceId) {
        // 处理被其他设备强制登出的情况
        tokenManager.removeToken();
        deviceManager.removeDevice(deviceId);
        // 通知用户被其他设备登出
        notifyUserForceLogout();
    }
}

3. 支付场景安全验证

在支付场景中,Token的安全性要求更高,需要额外的安全措施:

public class PaymentTokenManager {
    private static final long PAYMENT_TOKEN_EXPIRE = 300000; // 5分钟支付有效期
    private final TokenManager tokenManager;
    private final SecurityManager securityManager;

    public PaymentTokenManager(Context context, ApiService apiService) {
        this.tokenManager = new TokenManager(context, apiService);
        this.securityManager = new SecurityManager(context);
    }

    public void generatePaymentToken(PaymentInfo paymentInfo, TokenCallback callback) {
        // 1. 获取设备安全信息
        String deviceFingerprint = securityManager.getDeviceFingerprint();
        String secureRandom = securityManager.generateSecureRandom();

        // 2. 生成支付Token
        PaymentTokenRequest request = new PaymentTokenRequest.Builder()
                .setAmount(paymentInfo.getAmount())
                .setOrderId(paymentInfo.getOrderId())
                .setDeviceFingerprint(deviceFingerprint)
                .setSecureRandom(secureRandom)
                .build();

        apiService.generatePaymentToken(request, new Callback<String>() {
            @Override
            public void onSuccess(String paymentToken) {
                // 3. 安全存储支付Token
                tokenManager.saveToken(paymentToken);
                startPaymentValidityTimer();
                callback.onTokenGenerated(paymentToken);
            }
        });
    }

    private void startPaymentValidityTimer() {
        new CountDownTimer(PAYMENT_TOKEN_EXPIRE, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                // 更新支付倒计时
                updatePaymentCountdown(millisUntilFinished);
            }

            @Override
            public void onFinish() {
                // 支付Token过期,清理Token
                tokenManager.removeToken();
                handlePaymentTimeout();
            }
        }.start();
    }

    public void verifyPaymentToken(String paymentToken, VerificationCallback callback) {
        // 验证支付Token
        String deviceFingerprint = securityManager.getDeviceFingerprint();
        
        PaymentVerificationRequest request = new PaymentVerificationRequest.Builder()
                .setPaymentToken(paymentToken)
                .setDeviceFingerprint(deviceFingerprint)
                .build();

        apiService.verifyPayment(request, new Callback<Boolean>() {
            @Override
            public void onSuccess(Boolean isValid) {
                if (isValid) {
                    callback.onVerificationSuccess();
                } else {
                    callback.onVerificationFailed("Invalid payment token");
                }
            }
        });
    }
}

4. JWT Token实现

public class JWTUtils {
    private static final String SECRET_KEY = "your_secret_key";
    private static final long EXPIRATION_TIME = 86400000; // 24小时

    public static String generateToken(String userId) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME);

        return Jwts.builder()
                .setSubject(userId)
                .setIssuedAt(now)
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                .compact();
    }

    public static String validateToken(String token) {
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(SECRET_KEY)
                    .parseClaimsJws(token)
                    .getBody();

            return claims.getSubject();
        } catch (SignatureException ex) {
            throw new SecurityException("Invalid JWT signature");
        } catch (MalformedJwtException ex) {
            throw new SecurityException("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            throw new SecurityException("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            throw new SecurityException("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            throw new SecurityException("JWT claims string is empty");
        }
    }
}

2. Token安全存储实现

public class SecureTokenStorage {
    private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
    private static final String KEY_ALIAS = "TokenEncryptionKey";
    private static final String SHARED_PREFS_NAME = "SecureTokenPrefs";
    private static final String TOKEN_KEY = "encrypted_token";

    private final Context context;
    private final KeyStore keyStore;

    public SecureTokenStorage(Context context) throws Exception {
        this.context = context;
        keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
        keyStore.load(null);
        createKeyIfNotExists();
    }

    private void createKeyIfNotExists() throws Exception {
        if (!keyStore.containsAlias(KEY_ALIAS)) {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES,
                    KEYSTORE_PROVIDER);

            KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(
                    KEY_ALIAS,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                    .setRandomizedEncryptionRequired(true)
                    .build();

            keyGenerator.init(keySpec);
            keyGenerator.generateKey();
        }
    }

    public void saveToken(String token) throws Exception {
        SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, key);

        byte[] encryptedBytes = cipher.doFinal(token.getBytes());
        byte[] iv = cipher.getIV();

        // 将IV和加密数据组合
        ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + encryptedBytes.length);
        byteBuffer.put(iv);
        byteBuffer.put(encryptedBytes);

        String encryptedToken = Base64.encodeToString(byteBuffer.array(), Base64.DEFAULT);
        SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
        prefs.edit().putString(TOKEN_KEY, encryptedToken).apply();
    }

    public String getToken() throws Exception {
        SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
        String encryptedToken = prefs.getString(TOKEN_KEY, null);
        if (encryptedToken == null) return null;

        byte[] encryptedData = Base64.decode(encryptedToken, Base64.DEFAULT);
        ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedData);

        byte[] iv = new byte[12]; // GCM的IV长度为12字节
        byteBuffer.get(iv);

        byte[] ciphertext = new byte[byteBuffer.remaining()];
        byteBuffer.get(ciphertext);

        SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null);
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);
        cipher.init(Cipher.DECRYPT_MODE, key, spec);

        byte[] decryptedBytes = cipher.doFinal(ciphertext);
        return new String(decryptedBytes);
    }

    public void removeToken() {
        SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
        prefs.edit().remove(TOKEN_KEY).apply();
    }
}

3. Token管理器实现

public class TokenManager {
    private static final long TOKEN_REFRESH_THRESHOLD = 300000; // 5分钟
    private final SecureTokenStorage tokenStorage;
    private final ApiService apiService;

    public TokenManager(Context context, ApiService apiService) throws Exception {
        this.tokenStorage = new SecureTokenStorage(context);
        this.apiService = apiService;
    }

    public String getValidToken() throws Exception {
        String token = tokenStorage.getToken();
        if (token == null) return null;

        // 验证Token
        String userId = JWTUtils.validateToken(token);
        if (userId == null) {
            tokenStorage.removeToken();
            return null;
        }

        // 检查是否需要刷新Token
        Claims claims = Jwts.parser()
                .setSigningKey(JWTUtils.SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();

        Date expiration = claims.getExpiration();
        if (expiration.getTime() - System.currentTimeMillis() < TOKEN_REFRESH_THRESHOLD) {
            // 刷新Token
            token = refreshToken(token);
        }

        return token;
    }

    private String refreshToken(String oldToken) throws Exception {
        // 调用API刷新Token
        String newToken = apiService.refreshToken(oldToken);
        if (newToken != null) {
            tokenStorage.saveToken(newToken);
        }
        return newToken;
    }
}

最佳实践

  1. Token设计

    • 合理设置过期时间
    • 使用Refresh Token机制
    • 加入必要的Token信息
  2. 安全存储

    • 使用Android Keystore
    • 加密存储Token
    • 及时清理过期Token
  3. Token管理

    • 实现Token自动刷新
    • 处理Token失效情况
    • 多设备登录控制

实战调试技巧

  1. 使用JWT Debugger分析Token结构
  2. 使用Android Studio的Database Inspector查看存储数据
  3. 使用Charles监控Token交互过程

常见面试题

  1. JWT Token的组成部分是什么?

    • Header(头部)
    • Payload(负载)
    • Signature(签名)
  2. 为什么要使用Refresh Token?

    • 减少Access Token的有效期
    • 提高安全性
    • 改善用户体验
  3. Android Keystore的优势是什么?

    • 硬件级别的密钥保护
    • 防止密钥提取
    • 系统级别的安全保障

开源项目实战

  1. JJWT

    • Java JWT实现
    • 完整的JWT处理流程
    • 丰富的加密算法支持
  2. secure-preferences

    • 加密的SharedPreferences实现
    • 密钥安全存储
    • 简单易用的API

总结

本文详细介绍了Android平台上Token的原理和安全存储方案,包括JWT实现、安全存储机制和Token管理策略。通过实战案例和最佳实践,读者可以掌握如何在Android应用中实现安全可靠的Token认证系统。在实际开发中,要根据应用的具体需求,选择合适的Token策略,同时确保Token的安全存储和管理。