在Android OkHttp中的正确姿势)
OneNET新版安全鉴权在Android OkHttp中的实战避坑指南当你在Android Studio中集成OneNET API时是否遇到过401未授权错误或者Token莫名其妙失效作为经历过这些坑的老司机我想分享一些实战经验。新版安全鉴权机制虽然更安全但实现细节上的小疏忽就可能导致整个功能瘫痪。本文将带你深入理解Token生成原理并给出OkHttp中的最佳实践方案。1. 理解OneNET新版安全鉴权机制OneNET新版鉴权采用基于HMAC的签名方案核心是通过AccessKey对特定字符串进行加密签名。整个过程涉及几个关键参数versionAPI版本号如2022-05-01resourceName资源路径格式为userid/设备IDexpirationTimeToken过期时间戳秒级signatureMethod签名算法支持SHA1、MD5、SHA256签名生成的伪代码流程String encryptText et \n method \n res \n version; byte[] sign HmacSHA1(encryptText, accessKey); String signature Base64.encode(sign);最容易出错的三个地方时间戳未使用秒级单位加密文本中各参数顺序错误URL编码未使用UTF-8字符集2. Android平台的特殊兼容性问题2.1 Base64编码的版本差异在Android 8.0API 26之前需要使用Android自带的Base64类// 兼容旧版本的Base64编码 if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { signature android.util.Base64.encodeToString(bytes, android.util.Base64.NO_WRAP); } else { signature java.util.Base64.getEncoder().encodeToString(bytes); }2.2 HMAC算法的实现差异不同Android版本对加密算法的支持程度不同建议在代码中显式指定ProviderSecretKeySpec secretKey new SecretKeySpec( android.util.Base64.decode(accessKey, android.util.Base64.NO_WRAP), HmacSHA1); Mac mac Mac.getInstance(HmacSHA1, BC); // 明确指定BouncyCastle Provider mac.init(secretKey);2.3 URL编码的坑必须使用UTF-8字符集进行编码且要对每个参数单独编码String encodedRes URLEncoder.encode(resourceName, UTF-8) .replaceAll(\\, %20) // 处理空格编码差异 .replaceAll(%7E, ~); // 特殊字符处理3. OkHttp中的最佳实践3.1 Token的缓存与刷新建议使用OkHttp的拦截器实现自动Token管理class AuthInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request chain.request() val authRequest if (shouldRefreshToken()) { request.newBuilder() .header(Authorization, refreshToken()) .build() } else { request } return chain.proceed(authRequest) } private fun shouldRefreshToken(): Boolean { // 实现Token过期检查逻辑 } }3.2 请求头设置的正确姿势避免直接在每次请求时生成Token而是应该预生成Token并缓存设置合理的过期时间建议1小时使用单例管理Token状态OkHttpClient client new OkHttpClient.Builder() .addInterceptor(new AuthInterceptor()) .build();3.3 错误处理与重试机制针对常见的401错误建议实现自动重试逻辑val client OkHttpClient.Builder() .retryOnConnectionFailure(true) .addInterceptor { chain - var response chain.proceed(chain.request()) if (response.code 401) { refreshToken() val newRequest chain.request().newBuilder() .header(Authorization, currentToken) .build() response chain.proceed(newRequest) } response } .build()4. 调试技巧与常见问题排查4.1 签名验证工具开发阶段可以使用这个在线工具验证签名结果https://www.liavaag.org/English/SHA-Generator/HMAC/4.2 常见错误代码对照表错误码可能原因解决方案401000Token格式错误检查version参数格式401001签名方法不支持使用SHA1/SHA256401002Token已过期检查系统时间是否同步401003签名验证失败检查加密文本拼接顺序4.3 日志调试技巧在OkHttp中添加日志拦截器可以快速定位问题implementation com.squareup.okhttp3:logging-interceptor:4.9.3HttpLoggingInterceptor logging new HttpLoggingInterceptor(); logging.setLevel(HttpLoggingInterceptor.Level.HEADERS); client.addInterceptor(logging);5. 性能优化与安全建议5.1 Token生命周期管理短期Token1小时 自动刷新机制使用Android的WorkManager处理后台刷新考虑实现双Token机制access_token refresh_token5.2 安全存储方案避免将AccessKey硬编码在代码中推荐方案使用Android Keystore系统服务端下发临时凭证代码混淆保护// 使用EncryptedSharedPreferences存储敏感数据 val masterKey MasterKey.Builder(context) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() val sharedPreferences EncryptedSharedPreferences.create( context, secret_shared_prefs, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM )5.3 网络通信安全强制使用HTTPS配置OkHttp的证书锁定开启HTTP/2支持使用DNS-over-HTTPS防止劫持OkHttpClient client new OkHttpClient.Builder() .certificatePinner(new CertificatePinner.Builder() .add(onenet.cn, sha256/...) .build()) .build();在项目实际开发中我发现很多开发者容易忽视Token的URL编码问题特别是当resourceName包含中文或特殊字符时。曾经有个案例因为设备ID中包含号导致签名一直验证失败最后发现是URL编码后