Service安全:前台服务、绑定服务、权限检查

Service 是 Android 四大组件里最容易被忽视的安全短板。我见过太多应用,Activity 层防护做得滴水不漏,结果 Service 接口直接裸奔。说白了,Service 就像你家后门——前门锁得再好,后门开着也没用。

今天咱们就聊聊 Service 安全的三个核心点:前台服务、绑定服务、权限检查。每个点我都踩过坑,希望能帮你少走弯路。

前台服务:别让通知成为摆设

前台服务(Foreground Service)从 Android 8.0 开始就成了刚需。后台 Service 一杀就死,只有前台服务能活下来。但很多人只把它当「保活手段」,忽略了安全层面的东西。

前台服务的核心安全风险有两个:

  • 通知欺骗:恶意应用可以伪造前台服务通知,诱导用户点击
  • 资源耗尽:未授权的前台服务可能长期占用系统资源

我个人习惯,每次启动前台服务前,都会做两件事:

  1. 检查调用方是否有 FOREGROUND_SERVICE 权限
  2. 验证通知内容是否来自可信来源

关键点:前台服务的通知必须包含明确的用户可识别信息,比如「正在录音」「正在定位」。别用「正在运行」这种模糊描述——用户看不懂,也容易成为钓鱼目标。

来看一段安全的前台服务启动代码:

// 安全的前台服务启动示例
public class SecureForegroundService extends Service {
    private static final String TAG = "SecureForeground";
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 1. 检查调用方权限
        if (checkCallingPermission("android.permission.FOREGROUND_SERVICE") 
            != PackageManager.PERMISSION_GRANTED) {
            stopSelf();
            return START_NOT_STICKY;
        }
        
        // 2. 验证通知来源
        String action = intent.getAction();
        if (!"com.example.ACTION_START_FOREGROUND".equals(action)) {
            stopSelf();
            return START_NOT_STICKY;
        }
        
        // 3. 构建安全的通知
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("安全扫描进行中")
            .setContentText("正在检查应用权限...")
            .setSmallIcon(R.drawable.ic_shield)
            .setOngoing(true)
            .build();
        
        startForeground(NOTIFICATION_ID, notification);
        return START_STICKY;
    }
}

我的经验:我曾经接手过一个项目,前台服务通知里直接显示了用户手机号。这不仅是安全问题,还违反了隐私政策。记住:通知内容要最小化,只展示必要信息。

绑定服务:接口暴露的噩梦

绑定服务(Bound Service)通过 Binder 暴露接口,这玩意儿一旦没控制好,就是灾难。你想想看,任何应用都能 bindService 进来调用你的方法,多可怕。

绑定服务的安全要点:

  • 使用 android:exported="false" 限制外部访问
  • 必须暴露时,用自定义权限做访问控制
  • Binder 接口方法要做参数校验

我在项目中遇到过一个问题:某个金融应用的绑定服务暴露了转账接口,结果被恶意应用直接调用。原因就是没做调用方身份校验。嗯,这里要注意——Binder 接口的 onTransact() 方法里,一定要检查调用方的 UID 或 PID。

// 安全的 Binder 接口实现
public class SecureBinder extends IMyService.Stub {
    @Override
    public boolean transferMoney(String account, double amount) {
        // 1. 获取调用方 UID
        int callingUid = Binder.getCallingUid();
        
        // 2. 检查 UID 是否属于我们的应用
        if (callingUid != Process.myUid()) {
            // 记录异常行为
            Log.w(TAG, "Unauthorized call from UID: " + callingUid);
            return false;
        }
        
        // 3. 参数校验
        if (account == null || account.isEmpty()) {
            throw new IllegalArgumentException("Invalid account");
        }
        if (amount <= 0 || amount > MAX_TRANSFER_LIMIT) {
            throw new SecurityException("Amount out of range");
        }
        
        // 执行转账逻辑
        return doTransfer(account, amount);
    }
}

避坑指南:我曾经在 Binder 接口里直接返回了数据库查询结果,没做数据脱敏。结果被其他应用通过反射拿到了敏感数据。记住:Binder 返回的数据也要做权限校验和脱敏处理。

权限检查:别只依赖 manifest

很多人以为在 AndroidManifest 里声明了权限就万事大吉。其实不然——运行时权限检查才是真正的防线。特别是 Service 场景,调用方可能来自不同进程。

Service 权限检查的三种方式:

方式 适用场景 安全等级
Manifest 声明 基础防护
onBind/onStartCommand 检查 绑定服务、前台服务
Binder 方法内检查 精细权限控制

我个人推荐第三种方式——在 Binder 方法内部做权限检查。这样即使 Service 被绑定,也能控制每个方法的访问权限。

// 在 Binder 方法内做权限检查
@Override
public void onBind(Intent intent) {
    // 检查调用方是否有自定义权限
    int check = checkCallingPermission("com.example.permission.ACCESS_SERVICE");
    if (check != PackageManager.PERMISSION_GRANTED) {
        throw new SecurityException("Missing permission");
    }
    
    // 记录调用方信息
    String caller = getPackageManager()
        .getNameForUid(Binder.getCallingUid());
    Log.i(TAG, "Service bound by: " + caller);
    
    return mBinder;
}

核心原则:权限检查要「层层设防」。Manifest 是第一道门,onBind 是第二道,Binder 方法是第三道。三道门都锁上,才能挡住大多数攻击。

知识体系总览

下面这张图总结了 Service 安全的三个核心维度,以及它们之间的关系:

Service 安全知识体系 前台服务 通知安全 权限检查 资源控制 用户可见性 核心:通知不可伪造 绑定服务 Binder 接口 UID 校验 参数校验 数据脱敏 核心:接口不裸奔 权限检查 Manifest 声明 运行时检查 自定义权限 调用方验证 核心:层层设防 三者协同:前台服务保活 + 绑定服务控接口 + 权限检查守大门

这张图把 Service 安全的三个维度串起来了。前台服务负责「保活」和「通知安全」,绑定服务管「接口暴露」,权限检查做「访问控制」。三者缺一不可。

最后说一句:Service 安全没有银弹。我见过太多人只关注 Activity 安全,结果 Service 成了突破口。记住:攻击者永远找最薄弱的环节。把 Service 的安全做好,你的应用才算真正「锁好了后门」。


公众号:蓝海资料掘金营,微信deep3321