上一篇介紹了Android權限驗證中的一點小知識,這裏我們解析源碼流程進一步探索權限驗證的過程和實現方法。
權限驗證時序圖
首先我們先來看看權限驗證的實現過程,閱讀源碼能夠發現很多地方都有權限校驗的代碼,ContextImpl,AMS中都有,不過最終都是調用ActivityManager的checkComponentPermission,如下代碼
ActivityManagerService.java
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
return ActivityManager.checkComponentPermission(permission, uid,
owningUid, exported);
}
在需要進行權限驗證的地方加入此段代碼,判斷訪問者是否擁有訪問此代碼的權限!
接下來我們重點分析ActivityManager的checkComponentPermission
public static int checkComponentPermission(String permission, int uid,
int owningUid, boolean exported) {
// Root, system server get to do everything.
//獲取調用者的PID,如果是ROOT_UID或者SYSTEM_UID則驗證通過
final int appId = UserHandle.getAppId(uid);
if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
return PackageManager.PERMISSION_GRANTED;
}
// Isolated processes don't get any permissions.
// 如果調用者是隔離的,驗證不通過
if (UserHandle.isIsolated(uid)) {
return PackageManager.PERMISSION_DENIED;
}
// If there is a uid that owns whatever is being accessed, it has
// blanket access to it regardless of the permissions it requires.
// 如果當前UID不小於0,驗證是否與調用者UID相同,相同則驗證通過,owningUid默認爲-1
if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
return PackageManager.PERMISSION_GRANTED;
}
// If the target is not exported, then nobody else can get to it.
// exported屬性,默認值根據intent-filter來定,存在intent-filter默認值爲true,
// 否則爲false,這裏如果爲false表示不能別其他應用調用,權限驗證默認不通過
if (!exported) {
/*
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
here);
*/
return PackageManager.PERMISSION_DENIED;
}
if (permission == null) {
return PackageManager.PERMISSION_GRANTED;
}
try {
return AppGlobals.getPackageManager()
.checkUidPermission(permission, uid);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
上面代碼一系列校驗過後,最終調用PackageManagerService的checkUidPermission方法
@Override
public int checkUidPermission(String permName, int uid) {
synchronized (mPackages) {
final String[] packageNames = getPackagesForUid(uid);
final PackageParser.Package pkg = (packageNames != null && packageNames.length > 0)
? mPackages.get(packageNames[0])
: null;
return mPermissionManager.checkUidPermission(permName, pkg, uid, getCallingUid());
}
}
@Override
public String[] getPackagesForUid(int uid) {
final int callingUid = Binder.getCallingUid();
final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
final int userId = UserHandle.getUserId(uid);
uid = UserHandle.getAppId(uid);
// reader
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(uid);
if (obj instanceof SharedUserSetting) {
if (isCallerInstantApp) {
return null;
}
final SharedUserSetting sus = (SharedUserSetting) obj;
final int N = sus.packages.size();
String[] res = new String[N];
final Iterator<PackageSetting> it = sus.packages.iterator();
int i = 0;
while (it.hasNext()) {
PackageSetting ps = it.next();
if (ps.getInstalled(userId)) {
res[i++] = ps.name;
} else {
res = ArrayUtils.removeElement(String.class, res, res[i]);
}
}
return res;
} else if (obj instanceof PackageSetting) {
final PackageSetting ps = (PackageSetting) obj;
if (ps.getInstalled(userId) && !filterAppAccessLPr(ps, callingUid, userId)) {
return new String[]{ps.name};
}
}
}
return null;
}
以上代碼會根據調用者的UID獲取到對應的安裝包對象,如果獲取不到則爲null,還有一種情況就如果調用者是即時應用(也就是Android的小程序,這個後面再細聊,有興趣的可以去了解)則返回null
此段代碼涉及的mPackages和mSettings.getUserIdLPr部分集合存儲所有應用包名信息,這裏大概說一下他的加載流程,有興趣的可以去仔細瞭解
SystemService.java
=============================================
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
PackageManagerService.java
=============================================
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
// Self-check for initial settings.
PackageManagerServiceCompilerMapping.checkProperties();
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
m.enableSystemUserPackages();
ServiceManager.addService("package", m);
final PackageManagerNative pmn = m.new PackageManagerNative();
ServiceManager.addService("package_native", pmn);
return m;
}
// Collect privileged system packages.
final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
scanDirTracedLI(privilegedAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
| SCAN_AS_PRIVILEGED,
0);
// Collect ordinary system packages.
final File systemAppDir = new File(Environment.getRootDirectory(), "app");
scanDirTracedLI(systemAppDir,
mDefParseFlags
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM,
0);
// Collect privileged vendor packages.
File privilegedVendorAppDir = new File(Environment.getVendorDirectory(), "priv-app");
try {
privilegedVendorAppDir = privilegedVendorAppDir.getCanonicalFile();
} catch (IOException e) {
// failed to look up canonical path, continue with original one
}
系統啓動時會初始化PackageManagerService包管理服務,此服務會加載當前系統app,priv-app目錄下的所有apk,並解析包數據存儲在集合mOtherUserIds和mUserIds中,寫入文件生成/data/system/packages.xml,監聽apk的安裝和卸載變化並且更新packages文件記錄
說回到上面代碼mPermissionManager.checkUidPermission調用到PermissionManagerService
PermissionManagerService.java
private int checkUidPermission(String permName, PackageParser.Package pkg, int uid,
int callingUid) {
final int callingUserId = UserHandle.getUserId(callingUid);
final boolean isCallerInstantApp =
mPackageManagerInt.getInstantAppPackageName(callingUid) != null;
final boolean isUidInstantApp =
mPackageManagerInt.getInstantAppPackageName(uid) != null;
final int userId = UserHandle.getUserId(uid);
if (!mUserManagerInt.exists(userId)) {
return PackageManager.PERMISSION_DENIED;
}
if (pkg != null) {
if (pkg.mSharedUserId != null) {
if (isCallerInstantApp) {
return PackageManager.PERMISSION_DENIED;
}
} else if (mPackageManagerInt.filterAppAccess(pkg, callingUid, callingUserId)) {
return PackageManager.PERMISSION_DENIED;
}
final PermissionsState permissionsState =
((PackageSetting) pkg.mExtras).getPermissionsState();
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
if (mSettings.isPermissionInstant(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
return PackageManager.PERMISSION_GRANTED;
}
}
// Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
.hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
ArraySet<String> perms = mSystemPermissions.get(uid);
if (perms != null) {
if (perms.contains(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && perms
.contains(Manifest.permission.ACCESS_FINE_LOCATION)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
return PackageManager.PERMISSION_DENIED;
}
到這裏就可以明白Android的權限驗證其實是當前APP中是否包含了你所驗證的權限preName,如果包含驗證通過,否則驗證失敗,pkg爲null,表示系統應用,驗證是否擁有相關係統權限。
總結:
Android系統啓動時會啓動PackageManagerService服務,加載並且監聽系統目錄下已經安裝的所有APP包信息變化,包括權限信息,存儲在對應集合列表中,程序運行時根據APP對應的UID獲取對應權限驗證。