為什麼需要許可權保護
無條件接收廣播
比如說,一位將軍對軍隊下令說:“如果聽到衝啊,就發起攻擊”。結果軍隊出了個奸細,大喊一聲衝啊,所有士兵都開始衝鋒,掉入敵人的陷阱,將軍在軍帳裡傻了眼。
因為這位將軍只告訴軍隊聽到衝啊就發起攻擊,並沒有告知只有將軍自己喊的才有效。
在 Android 中,就好像你註冊了一個廣播接受者(軍隊),卻沒有說明廣播傳送者需要的許可權(將軍令),無論哪裡發來的廣播都會響應,當然前提是 action
一致(都是衝啊)。如果廣播接收者的響應涉及到敏感操作,那麼很容易就會被攻擊。
無條件傳送廣播
比如說,皇帝說:“五更時分,到太和殿上朝”。上朝時分一到,人叫那個多啊,不止是文武百官,後宮三千啊,太監啊,宮女啊,侍衛啊,全都擠進太和殿,皇帝在龍椅上傻了眼。
因為皇帝只告知五更時分就到太和殿上朝,並沒有說明誰可以去誰不可以去。
這裡說無條件傳送廣播,其實是不大恰當的,我們並沒有能力去限制廣播的傳送,就好像限制不了五更時分的到來,我們只能限制哪些能作為廣播接收者(文武百官)。
舉個 Android 的例子,開機或者切換到新使用者的時候都會傳送一個 BOOT_COMPLETED
廣播,如果這個廣播不檢查接收者的許可權,假如三方應用都去註冊這個廣播,而且收到這個廣播的時候把自己喚起,那麼一開機一大堆 app 自啟動,手機就得卡成馬了。
限制廣播傳送者
上面我們說到,不能讓廣播接收者無條件地接收廣播,只能接受擁有約定許可權的傳送者發來的廣播。
我們在廣播接收者一側定義傳送者應擁有的許可權,並且在註冊廣播接收者時指明該許可權:
<permission android:name="io.github.mindjet.SEND_PERMISSION" />
<receiver android:name="MyBroadcastReceiver"
android:permission="io.github.mindjet.SEND_PERMISSION">
<intent-filter>
<action android:name="io.github.mindjet.JUST_BROADCAST" />
</intent-filter>
</receiver>複製程式碼
在廣播傳送者一側使用該許可權:
<uses-permission android:name="io.github.mindjet.SEND_PERMISSION" />複製程式碼
之後便可以傳送廣播並接收了:
sendBroadcast(new Intent("io.github.mindjet.JUST_BROADCAST"));複製程式碼
其他不擁有 io.github.mindjet.SEND_PERMISSION
許可權的傳送者可以將廣播傳送出去,但是接收者對其不理不睬。
限制廣播接收者
上面說到,我們不能讓廣播傳送者的廣播隨隨便便地被接收者接收,只能讓那些擁有約定許可權的接收者接受。
我們在廣播傳送者一側定義接收者應擁有的許可權:
<permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />複製程式碼
然後,在傳送廣播時,宣告這是一條有許可權檢查的廣播(第二個引數代表接收者應擁有的許可權):
sendBroadcast(new Intent("io.github.mindjet.JUST_BROADCAST"), "io.github.mindjet.RECEIVE_PERMISSION");複製程式碼
接收者一側則需要使用該許可權:
<uses-permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />複製程式碼
其他不擁有 io.github.mindjet.RECEIVE_PERMISSION
的接收者無權接收這個廣播。
雙向許可權保護
雙向保護其實就是把上面兩種限制手段結合起來。
傳送方說,你得有 XXX 許可權才能接收我的廣播;接收方說,你也得有 YYY 許可權我才接收你的廣播。
也就是說,傳送方有接收方規定的傳送許可權的同時,接收方也得有傳送方規定的接收許可權。
那麼傳送方一側:
<!-- 宣告接收許可權 -->
<permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />
<!-- 使用傳送許可權 -->
<uses-permission android:name="io.github.mindjet.SEND_PERMISSION" />複製程式碼
同時傳送廣播時,使用帶許可權檢查的方式:
sendBroadcast(new Intent("io.github.mindjet.JUST_BROADCAST"), "io.github.mindjet.RECEIVE_PERMISSION");複製程式碼
相應地,接收方一側:
<!-- 宣告傳送許可權 -->
<permission android:name="io.github.mindjet.SEND_PERMISSION" />
<!-- 使用接收許可權 -->
<uses-permission android:name="io.github.mindjet.RECEIVE_PERMISSION" />
<receiver android:name="MyBroadcastReceiver"
android:permission="io.github.mindjet.SEND_PERMISSION">
<intent-filter>
<action android:name="io.github.mindjet.JUST_BROADCAST" />
</intent-filter>
</receiver>複製程式碼
這樣就能實現雙向的許可權保護了。
終於,將軍在軍帳裡運籌帷幄,天子在龍椅上君臨天下。
protectionLevel
看了上面的例子,有人說,如果我是廣播傳送者,那我在 AndroidManifest.xml
裡面宣告使用傳送許可權不就好了,那許可權保護還有什麼用。
情況確實是這樣,只要你能申請到,就有許可權傳送或者接收。所以為了更好地控制,我們需要宣告許可權的保護等級,即 protectionLevel
屬性。
protectionLevel
有以下幾個屬性值:
normal 預設值,只要申請便能使用
dangerous 同上
signature apk 簽名一致才能使用
signatureOrSystem apk 簽名一致或者系統應用才能使用複製程式碼
比如說,一家公司開發出了幾款產品(簽名一致),並且定義了廣播傳送和接收許可權,保護等級都是 signature
:
<permission android:name="io.github.mindjet.SEND_PERMISSION"
protectionLevel="signature" />
<permission android:name="io.github.mindjet.RECEIVE_PERMISSION"
protectionLevel="signature" />複製程式碼
那麼這兩個許可權,只有這家公司自家的產品可以申請到,也就避免了廣播被外部 app 利用的情況。
———
技術上的問題,歡迎討論。
最近在 Github 上維護的專案:
- LiteWeather [一款用 Kotlin 編寫,基於 MD 風格的輕量天氣 App],對使用 Kotlin 進行實際開發感興趣的同學可以看看,專案中會使用到 Kotlin 的委託機制、擴充套件機制和各種新奇的玩意。
- LiteReader [一款基於 MD 的極輕閱讀 App,提供知乎日報、豆瓣電影等資源],專案主要使用了 MVVM 設計模式,介面遵循 Material Design 規範,提供輕量的閱讀體驗。
- LiveMVVM [Kotlin 編寫的 Android MVVM 框架,基於 android-architecture],輕量 MVVM+Databinding 開發框架。
- AnkoUtil [Kotlin 編寫的 Android 擴充套件庫]。
歡迎 star/fork/follow 提 issue 和 PR。