最近一段时间开发公司一款MacOS平台的App时需要用到管理员权限,于是乎上网查询了MacOS App使用代码提权的方式,目前主要有以下几种:
1.通过ServiceManagement注册LaunchdDaemon
这种方法是目前苹果官方推荐的一种提权方式,官方也提供了一个SMJobBless的Demo,但是需要用苹果开发者账号编译(我会在我博客后面的文章单独介绍一种不需要开发者账号编译的方式,也能通过这种方式达到高权限的需求,在本文末尾会提供思路!),而且使用起来很复杂。带来的好处是,将高权限任务封装到独立的子程序,将按需调用,不会让整个程序处于高权限的状态会相对安全一些,子程序便能轻松实现开机自启、常驻后台、高权限的需求。
我通过研究官方SMJobBless的Demo,发现其实是通过launchd工具加载一个与Daemon程序相关的标准的plist文件,由于launchd需要高权限运行,所以启动的子程序自然而然也是高权限运行。程序运行之后,会将子程序放到/Library/PrivilegedHelperTools
这个目录,需要的plist文件会被放到/Library/LaunchDaemons
,这样launchd加载plist时,会去启动子程序。
在SMJobBless的Demo中是通过ServiceManagement这个框架的API来完成提权操作的,而这框架在10.6就出现了,所以不需要担心兼容性的问题。本文在这里不详细介绍如何使用相关API了,只是在这里简单说说编译方法吧!在Demo中的ReadMe.txt中虽然讲述的很清楚,但是新版xcode编译之后的目录有所不同。(注意:程序虽然编译成功,但在默认的SMJobBlessHelper-Launchd.plist
中并没有RunAtLoad
这一属性,所以launchd不会启动子程序,只会将子程序放和plist文件到相关目录,如需启动子程序,需在SMJobBlessHelper-Launchd.plist
中添加RunAtLoad
属性,Boolean类型,值为YES
)
1.在xcode中编译项目 (Product > Build或者command+b
)
2.使用终端进入项目根目录,运行以下命令:
./SMJobBlessUtil.py setreq <SMJobBlessApp.app:path> SMJobBlessApp/SMJobBlessApp-Info.plist SMJobBlessHelper/SMJobBlessHelper-Info.plist
<SMJobBlessApp.app:path>
是指在xcode左边的Navigator的product下的编译的APP的路径
脚本运行成功会输出:
SMJobBlessApp/SMJobBlessApp-Info.plist: updated
SMJobBlessHelper/SMJobBlessHelper-Info.plist: updated
3.clean项目(Product > Clean),然后再次编译(Product > Build)。
4.在项目根目录下,终端执行:
./SMJobBlessUtil.py check <SMJobBlessApp.app:path>
脚本运行成功没有输出任何东西,说明成功了。
5.运行编译的APP。
这时会弹出需要输入密码的认证提示框,输入密码之后,xcode在控制台打印Job is available!
,App上有显示The Helper Tool is available!
字样,表示成功运行了。
官方Demo:SMJobBless.zip
2.AuthorizationExecuteWithPrivileges函数
这个函数是Security.framework中的一函数,使用很方便,而且还有一个封装非常好的库STPrivilegedTask,接口和NSTask几乎一样。但是AuthorizationExecuteWithPrivileges函数在MacOS 10.7的时候就开始被弃用了,在新版的10.13.3中发现某些时候会提权失败,所以不推荐使用这种方法,在本文也不做过多介绍!
3.使用AppleScript提权
AppleScript是苹果独有的脚本语言,通过OC或者Swift都可以调用AppleScript,在这里提供一个OC写的方法:
-(NSDictionary*)doAppleScript:(NSString*)cmd{
cmd=[NSString stringWithFormat:@"do shell script \"%@\" with administrator privileges",cmd];
NSAppleScript *script= [[NSAppleScript alloc] initWithSource:cmd];
NSDictionary *scriptError = nil;
NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&scriptError];
NSMutableDictionary *dicResult=[NSMutableDictionary dictionary];
if(scriptError) {
[dicResult setObject:@NO forKey:@"id"];
[dicResult setObject:[scriptError objectForKey:NSAppleScriptErrorMessage] forKey:@"result"];
} else {
NSAppleEventDescriptor *unicode = [descriptor coerceToDescriptorType:typeUnicodeText];
NSData *data = [unicode data];
NSString *result = [[NSString alloc] initWithCharacters:(unichar*)[data bytes] length:[data length] / sizeof(unichar)];
[dicResult setObject:@YES forKey:@"id"];
[dicResult setObject:result forKey:@"result"];
}
return dicResult;
}
AppleScript方法固然简单,但是还有一些缺点,比如每次需要管理员权限时,都需要提示输入密码,会让用户感觉你总是在获取权限,造成用户对你程序的不信任,在执行需要等待结果的命令时,会造成UI卡住,一直占有线程,所以建议在子线程中运行!
4.自创的提权方法(思路)
提供一个通过AppleScript启动launchd子程序的思路(我博客后面的文章单独做介绍),这样也可以实现高权限,常驻后台的需求。
AppleScript可以运行shell脚本(方法3提供的OC语言的方法就可以执行脚本),而shell脚本可以传入参数,知道这两点,我们就可以在自己项目中建立子程序和需要plist文件,通过AppleScript执行管理员权限运行shell脚本,shell脚本把plist文件和子程序移动到方法1中的所提到的目录中,这样是不是可以达到一样的方法呢?感兴趣的童鞋可以试试!
本文来源:http://blog.iiwo.vip/2018/03/10/MacOS-App%E4%BB%A3%E7%A0%81%E6%8F%90%E6%9D%83%E8%AF%A6%E8%A7%A3/ 未经博主同意不得转载!
网友评论