網(wǎng)站首頁 編程語言 正文
系統(tǒng)應(yīng)用根據(jù)Uri授予權(quán)限的正確姿勢
在我們印象中,Android6.0以后訪問外部的媒體資源文件都是需要申請READ_EXTERNAL_STORAGE
才可以正常訪問,思考一個場景,假如我們不申請該權(quán)限,使用系統(tǒng)的Intent.ACTION_PICK
意圖跳轉(zhuǎn)系統(tǒng)圖庫選取圖片是否可以正常顯示該圖片?
答案是可以的,這是為什么呢?我們都沒有申請權(quán)限,或者說是誰給了我們這個權(quán)限?帶著這個疑問我們先來了解下UriPermission
UriPermission
Allow access on a per-URI basis
You can also grant permissions on a per-URI basis. When starting an activity or returning a result to an activity, set the?Intent.FLAG_GRANT_READ_URI_PERMISSION, intent flag, the?Intent.FLAG_GRANT_WRITE_URI_PERMISSION, intent flag, or both flags. This gives another app read, write, and read/write permissions, respectively, for the data URI that's included in the intent. The other app gains these permissions for the specific URI regardless of whether it has permission to access data in the content provider more generally.
上面是Android官網(wǎng)的解釋,大概意思是,你可以進一步對其他應(yīng)用如何訪問你應(yīng)用的Contete Provider
或者數(shù)據(jù)URI
進行精細控制,可以通過讀寫權(quán)限來保護自己,根據(jù) URI 授予權(quán)限。
在啟動 activity 或?qū)⒔Y(jié)果返回給 activity 時,請設(shè)置 Intent.FLAG_GRANT_READ_URI_PERMISSION
intent 標志、Intent.FLAG_GRANT_WRITE_URI_PERMISSION
intent 標志或者同時設(shè)置兩者。
這樣便可針對 intent 中包含的數(shù)據(jù) URI 分別向另一個應(yīng)用授予讀取權(quán)限、寫入權(quán)限和讀寫權(quán)限。
理解完UriPermission,上面的問題就可以解釋了,雖然我們沒有申請讀取的權(quán)限,但是系統(tǒng)圖庫在選圖后將結(jié)果返回activity時設(shè)置了Intent.FLAG_GRANT_READ_URI_PERMISSION
,這樣我們就具有了讀取該Uri指定圖片的權(quán)限。
理想很豐滿,現(xiàn)實很骨感。
背景
最近在項目中上線一款自研的圖庫應(yīng)用(systemUid),支持系統(tǒng)默認選圖action跳轉(zhuǎn),給調(diào)用者返回已選的Uri資源地址,因為安全合規(guī)整改的原因,一些第三方應(yīng)用去掉了讀寫權(quán)限的申請,問題就被暴露出來了,第三方應(yīng)用無法正常通過圖庫獲取到圖片資源。
分析
分析堆棧信息可以定位到,是調(diào)用者沒有訪問Uri的權(quán)限導(dǎo)致的異常,而我們自研圖庫在選圖回傳的時候是有設(shè)置FLAG_GRANT_READ_URI_PERMISSION,把URI的臨時訪問權(quán)限傳遞給調(diào)用者,且報錯的堆棧打印是在第三方應(yīng)用,所以可以初步判斷問題應(yīng)該是出自系統(tǒng)的權(quán)限授予過程。
我們可以通過context.grantUriPermission()
作為切入點,來分析下系統(tǒng)是如何授予Uri權(quán)限
最終調(diào)用的是UriGrantsManagerService$checkGrantUriPermission()
checkGrantUriPermission
int checkGrantUriPermission(int callingUid, String targetPkg, GrantUri grantUri, final int modeFlags, int lastTargetUid) { .... // Bail early if system is trying to hand out permissions directly; it // must always grant permissions on behalf of someone explicit. final int callingAppId = UserHandle.getAppId(callingUid); if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) { if ("com.android.settings.files".equals(grantUri.uri.getAuthority()) || "com.android.settings.module_licenses".equals(grantUri.uri.getAuthority())) { // Exempted authority for // 1. cropping user photos and sharing a generated license html // file in Settings app // 2. sharing a generated license html file in TvSettings app // 3. Sharing module license files from Settings app } else { Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission" + " grant to " + grantUri + "; use startActivityAsCaller() instead"); return -1; } } .... } 復(fù)制代碼
問題就是出現(xiàn)在這里,如果你的應(yīng)用是root用戶,或者是具有系統(tǒng)級權(quán)限(systemUid),并且提供的Uri的authority不是指定的,就會拒絕授權(quán) (return -1),也就是說這個Uri的權(quán)限并沒有傳遞成功。
這一點在上面的日志也有體現(xiàn)。
/system_process W UriGrantsManagerService: For security reasons, the system cannot issue a Uri permission grant to
系統(tǒng)為什么要這么做?
注釋里面有說,出于安全原因,系統(tǒng)應(yīng)用不能使用startActivityAsCaller()來直接授予Uri權(quán)限,它必須顯式地讓應(yīng)用自己授予權(quán)限。只有以下幾種情況才會豁免授權(quán)
- 裁剪用戶照片并共享生成的許可HTML文件的設(shè)置應(yīng)用程序
- 在TvSettings app中共享生成的許可html文件
- 從設(shè)置應(yīng)用程序共享模塊license文件
調(diào)用者端
分析完圖庫端,我們再來看下調(diào)用者端的異常堆棧打印
客戶端遠程調(diào)用服務(wù)端打開指定文件,然后服務(wù)端把文件描述符跨進程傳遞到客戶端(后面Binder驅(qū)動跨進程傳遞文件描述符就不展開分析)
通過分析時序圖,可以定位到報錯的堆棧信息是發(fā)生在enforceCallingPermissionInternal()
enforceCallingPermissionInternal()
private void enforceCallingPermissionInternal(Uri uri, boolean forWrite) { ... // 省略部分代碼 // First, check to see if caller has direct write access if (forWrite) { final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_UPDATE, uri, table, null); try (Cursor c = qb.query(db, new String[0], null, null, null, null, null)) { if (c.moveToFirst()) { // Direct write access granted, yay! return; } } } ... // 省略部分代碼 // Second, check to see if caller has direct read access final SQLiteQueryBuilder qb = getQueryBuilder(TYPE_QUERY, uri, table, null); try (Cursor c = qb.query(db, new String[0], null, null, null, null, null)) { if (c.moveToFirst()) { if (!forWrite) { // Direct read access granted, yay! return; } else if (allowUserGrant) { // Caller has read access, but they wanted to write, and // they'll need to get the user to grant that access final Context context = getContext(); final PendingIntent intent = PendingIntent.getActivity(context, 42, new Intent(null, uri, context, PermissionActivity.class), FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE); final Icon icon = getCollectionIcon(uri); final RemoteAction action = new RemoteAction(icon, context.getText(R.string.permission_required_action), context.getText(R.string.permission_required_action), intent); throw new RecoverableSecurityException(new SecurityException( getCallingPackageOrSelf() + " has no access to " + uri), context.getText(R.string.permission_required), action); } } } throw new SecurityException(getCallingPackageOrSelf() + " has no access to " + uri); }
在這個方法里面,它會根據(jù)參數(shù)forWrite
判斷當前調(diào)用者是否擁有讀寫權(quán)限,如果沒有則會拋出異常提示,和上面報錯的異常堆棧符合。
解決辦法
以下兩種方法都可以
- 去除應(yīng)用systemUid配置
- 修改framework源碼
考慮到修改系統(tǒng)源碼的影響面比較大,所以采用去除systemUid的方式,再次驗證跳轉(zhuǎn)選圖后可以正常加載。查看系統(tǒng)原生圖庫的清單文件配置,也是沒有設(shè)置systemUid,原生圖庫也是采用了這種方式。
擴展
系統(tǒng)原生圖庫既不是系統(tǒng)應(yīng)用,也沒有動態(tài)申請存儲權(quán)限,那它是怎么獲取系統(tǒng)的存儲權(quán)限的?
如果有了解過系統(tǒng)權(quán)限的授予流程,可以知道Android系統(tǒng)在開機后會對一些特殊的應(yīng)用進行自動授權(quán),而運行時權(quán)限的默認授予工作由DefaultPermissionGrantPolicy類的grantDefaultPermissions方法完成。
在這個方法里面可以看到它對圖庫進行默認授予存儲權(quán)限的代碼,具體是通過查找清單文件配置的category是Intent.CATEGORY_APP_GALLERY的應(yīng)用。
原文鏈接:https://juejin.cn/post/7138335779222355975
相關(guān)推薦
- 2022-03-14 stream實現(xiàn)list根據(jù)對象中多個屬性分組,并取分組后最新數(shù)據(jù)
- 2022-09-23 python?pandas創(chuàng)建多層索引MultiIndex的6種方式_python
- 2022-07-03 C#?Winform中DataGridView導(dǎo)出為Excel的實現(xiàn)示例_C#教程
- 2022-03-30 用C語言實現(xiàn)二分查找算法_C 語言
- 2022-04-25 在?Python?中進行?One-Hot?編碼_python
- 2023-05-08 Python中Generators教程的實現(xiàn)_python
- 2022-05-20 PyQt5實現(xiàn)數(shù)據(jù)的增刪改查功能詳解_python
- 2023-05-15 使用Bash讀取和處理CSV文件的方法_linux shell
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支