Android跨自有应用共享SharedPreference凭证实现免登方案
跨应用安全共享access_token实现方案(适配拉起自动登录场景)
以下方案按实现成本、安全优先级排序,优先选第一个即可完全匹配需求。
方案1:显式Intent + 签名级权限校验(推荐,零额外依赖)
这个方案完全贴合「点击A内按钮直接拉起B自动登录」的交互逻辑,不需要修改原有SP存储逻辑,也不需要额外实现数据库/Provider组件,安全性由系统签名校验机制保证。
实现步骤:
- 两个应用使用同一签名证书打包,这是所有同厂商应用安全通信的基础。
- 在应用B的
AndroidManifest.xml中声明签名级自定义权限,只有同签名应用能申请该权限,同时给接收自动登录请求的Activity配置权限限制,禁止其他应用拉起:
<!-- 声明自定义签名权限 --> <permission android:name="com.yourapp.AUTH_TOKEN_TRANSFER" android:protectionLevel="signature" /> <application ...> <!-- 自动登录入口Activity,仅持有对应权限的应用可拉起 --> <activity android:name=".AutoLoginEntryActivity" android:exported="true" android:permission="com.yourapp.AUTH_TOKEN_TRANSFER"> <intent-filter> <action android:name="com.yourapp.ACTION_AUTO_LOGIN" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application>
- 在应用A的
AndroidManifest.xml中声明使用上述权限:
<uses-permission android:name="com.yourapp.AUTH_TOKEN_TRANSFER" /> <!-- 如果targetSdkVersion >= 30,需要加包可见性声明 --> <queries> <package android:name="com.yourapp.appB" /> </queries>
- 应用A中按钮点击逻辑:构造显式Intent指定B的包名,把SP中读取到的access_token放入Extra直接拉起B:
// 按钮点击回调 binding.btnJumpToB.setOnClickListener { val token = getSharedPreferences("your_sp_name", Context.MODE_PRIVATE) .getString("access_token", "") ?: "" val jumpIntent = Intent("com.yourapp.ACTION_AUTO_LOGIN").apply { setPackage("com.yourapp.appB") // 显式指定目标包名,防止Intent被劫持 putExtra("access_token", token) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } startActivity(jumpIntent) }
- 应用B的
AutoLoginEntryActivity中从Intent取出token,校验有效性后直接完成登录逻辑跳转到主页,无需加载登录页。
方案2:ContentProvider封装SharedPreferences读取(适合需要随时双向读取凭证的场景)
你之前没找到SP对接ContentProvider的参考,是因为大部分教程默认用ContentProvider对接SQLite,实际上KV类的SP数据完全可以通过重写Provider的call()方法轻量实现,不需要写任何数据库逻辑:
- 同样先配置前面提到的签名级权限,保证只有同签名应用能访问Provider。
- 在应用A中实现自定义ContentProvider,仅暴露需要共享的SP字段读取能力:
class SPShareProvider : ContentProvider() { private lateinit var sharedPreferences: SharedPreferences override fun onCreate(): Boolean { // 直接读取你现有A应用里的私有SP文件,不需要修改原有存储逻辑 sharedPreferences = context!!.getSharedPreferences("your_sp_name", Context.MODE_PRIVATE) return true } // 重写call方法处理KV读取,不需要实现数据库相关方法 override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { if (method != "get_sp_value") return null val result = Bundle() when(arg) { "access_token" -> result.putString("value", sharedPreferences.getString(arg, "")) // 其他需要共享的SP字段可在这里扩展 } return result } // 以下数据库相关方法直接返回默认值即可,不需要实现 override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?) = null override fun getType(uri: Uri) = null override fun insert(uri: Uri, values: ContentValues?) = null override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0 override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0 }
- 在应用A的Manifest中注册Provider,绑定之前的签名权限:
<provider android:name=".SPShareProvider" android:authorities="com.yourapp.appA.sp_provider" android:exported="true" android:permission="com.yourapp.AUTH_TOKEN_TRANSFER" />
- 应用B需要读取token时,直接通过ContentResolver调用即可:
val resultBundle = contentResolver.call( "com.yourapp.appA.sp_provider", "get_sp_value", "access_token", null ) val accessToken = resultBundle?.getString("value") ?: "" // 拿到token后走自动登录逻辑
避坑说明
- 绝对不要把SP设置为
MODE_WORLD_READABLE/MODE_WORLD_WRITEABLE模式,这两个模式从Android 7.0开始直接抛出安全异常,且会导致所有应用都能读取你的私有数据,凭证泄露风险极高。 - 不要使用未指定目标包名的隐式Intent传输敏感凭证,会被第三方应用拦截读取。
- 传输凭证时如果需要更高安全等级,可以在两个应用内置约定的加解密密钥,对Intent/Provider传输的token做对称加密后再传递。
内容的提问来源于stack exchange,提问作者meazuri
相关产品推荐
相关产品推荐

