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;
}
}
最佳实践
Token设计
- 合理设置过期时间
- 使用Refresh Token机制
- 加入必要的Token信息
安全存储
- 使用Android Keystore
- 加密存储Token
- 及时清理过期Token
Token管理
- 实现Token自动刷新
- 处理Token失效情况
- 多设备登录控制
实战调试技巧
- 使用JWT Debugger分析Token结构
- 使用Android Studio的Database Inspector查看存储数据
- 使用Charles监控Token交互过程
常见面试题
JWT Token的组成部分是什么?
- Header(头部)
- Payload(负载)
- Signature(签名)
为什么要使用Refresh Token?
- 减少Access Token的有效期
- 提高安全性
- 改善用户体验
Android Keystore的优势是什么?
- 硬件级别的密钥保护
- 防止密钥提取
- 系统级别的安全保障
开源项目实战
-
- Java JWT实现
- 完整的JWT处理流程
- 丰富的加密算法支持
-
- 加密的SharedPreferences实现
- 密钥安全存储
- 简单易用的API
总结
本文详细介绍了Android平台上Token的原理和安全存储方案,包括JWT实现、安全存储机制和Token管理策略。通过实战案例和最佳实践,读者可以掌握如何在Android应用中实现安全可靠的Token认证系统。在实际开发中,要根据应用的具体需求,选择合适的Token策略,同时确保Token的安全存储和管理。