ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [BroadcastReceiver] Broadcast permission 주기
    개발/Android 2018. 12. 11. 00:46
    반응형

     Android 개발을 하다 보면 BroadcastReceiver를 이용한 이벤트 전달 방식을 사용하게 됩니다. 현재 앱이 실행중일때 푸시메시지 수신시 이벤트만 날려주거나, 알람설정을 해놨다가 시간이 되었다는 이벤트만 날려주거나 하는 식으로 쓰는 경우가 많이 있었습니다. 문제는 이렇게 사용을 했을때 해당 Action 정보만 알고 있다면 다른 앱에서 동일한 Action을 등록한 BroadcastReceiver가 있는 경우는 해당 이벤트를 받을수 있다는 문제가 있습니다. 

     sendBroadcast() 메소드 실행시 특정 권한을 가진 앱만 이벤트를 받을수 있도록 바꿔보도록 하겠습니다.


     간단하게 Broadcast를 하는 앱을 작성해보도록 하겠습니다. 먼저 UI부터 작성하도록 합니다. 전송버튼 3개, 삭제버튼 1개, 로그가 표시될 텍스트뷰 2개를 추가해줍니다.



     전송에 관련된 3개의 버튼과 로그 삭제에 필요한 1개의 버튼을 추가 했습니다. 간단한 테스트를 위한 앱이므로 그냥 뚝딱뚝딱 붙였습니다. 상단의 2개의 버튼은 동일한 액션에 대해서 Permission의 포함 여부를 구분하고 있습니다. 두번째 버튼은 기존에 Permission을 등록한 Receiver에 다른 permission을 포함한 이벤트를 전송했을때의 동작을 확인하고자 합니다. 그외에는 로그삭제 및 로그 표현용 UI 입니다.

     이제 구현해 봅시다.


    class BroadCastFragment : Fragment() {
        companion object {
            const val ACTION_PERMISSION = "com.leeds.example.broadcast.ACTION_PERMISSION"
            const val ACTION_ANOTHER = "com.leeds.example.broadcast.ACTION_ANOTHER"
            const val PERMISSION = "com.leeds.example.broadcast.permission.ACTION_PERMISSION"
            const val PERMISSION_ANOTHER = "com.leeds.example.broadcast.permission.ACTION_ANOTHER"
            const val EXTRA_TEXT = "text"
    
            @JvmStatic
            fun getInstance(): BroadCastFragment {
                return BroadCastFragment()
            }
    
            @JvmStatic
            fun getInstance(bundle: Bundle): BroadCastFragment {
                return BroadCastFragment().apply { arguments = bundle }
            }
        }
    
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            return inflater.inflate(R.layout.fragment_broadcast, container, false)
        }
    
        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
    
            //수신된 메시지 전체 삭제
            clear.setOnClickListener { clickClear() }
            //permission을 가진 메시지 발행
            send_with_permission.setOnClickListener { clickSendWithPermission() }
            //permission이 없는 메시지 발행
            send_without_permission.setOnClickListener { clickSendWithoutPermission() }
            //permission이 다른 메시지 발행
            send_another_action.setOnClickListener { clickSendAnotherAction() }
        }
    
        private fun clickSendAnotherAction() {
            val activity = activity ?: return
            activity.sendBroadcast(Intent().apply {
                action = ACTION_ANOTHER
                putExtra(EXTRA_TEXT, "C")
            }, PERMISSION_ANOTHER)
        }
    
        private fun clickSendWithPermission() {
            val activity = activity ?: return
            activity.sendBroadcast(Intent().apply {
                action = ACTION_PERMISSION
                putExtra(EXTRA_TEXT, "A")
            }, PERMISSION)
        }
    
        private fun clickSendWithoutPermission() {
            val activity = activity ?: return
            activity.sendBroadcast(Intent().apply {
                action = ACTION_PERMISSION
                putExtra(EXTRA_TEXT, "B")
            })
        }
    
        private fun clickClear() {
            receive_with_permission.text = ""
            receive_without_permission.text = ""
        }
    	
    	...
    }
    

     취향에 따라 선택하면 되지만 일단 Fragment에 구현을 하였습니다. Activity에 구현을 하는 경우는 초기화 과정이 달라질 수 있습니다. 테스트할 Action과 Permission은 상수로 선언을 하고 각 버튼에 OnClickListener를 설정해 줍니다. 각각의 버튼 동작은 sendBroadcast() 메소드가 호출이 되어야 하기 때문에 Activity를 체크하여 동작합니다. 쿨하게 없으면 아무것도 하지 않습니다.

     이렇게 기본적인 UI 동작은 구현이 끝났습니다. 다음으로는 BroadcastReceiver를 설정해 보도록 합시다.


     Manifest에 등록하는 정적등록도 가능하지만 여기서는 동적으로 생성하여 등록하는 방식으로 구현하였습니다. 

    class BroadCastFragment : Fragment() {
        
    	...
    	
        private val intentFilter = IntentFilter().apply {
            addAction(ACTION_PERMISSION)
            addAction(ACTION_ANOTHER)
        }
    
        override fun onResume() {
            super.onResume()
            registerReceiver()
        }
    
        override fun onPause() {
            unregisterReceiver()
            super.onPause()
        }
    
        private fun registerReceiver() {
            val activity = activity ?: return
            activity.registerReceiver(withPermission, intentFilter, PERMISSION, null)
            activity.registerReceiver(withoutPermission, intentFilter)
        }
    
        private fun unregisterReceiver() {
            val activity = activity ?: return
            activity.unregisterReceiver(withPermission)
            activity.unregisterReceiver(withoutPermission)
        }
    
        private fun setReceiveData(text: String) {
            receive_with_permission.run {
                append(text)
                append("\n")
            }
            receive_without_permission.run {
                append(text)
                append("\n")
            }
        }
    
        private val withPermission = object : BroadcastReceiver() {
            override fun onReceive(p0: Context?, p1: Intent?) {
                val text = p1?.getStringExtra(EXTRA_TEXT) ?: return
                setReceiveData("$text withPermission")
            }
        }
        private val withoutPermission = object : BroadcastReceiver() {
            override fun onReceive(p0: Context?, p1: Intent?) {
                val text = p1?.getStringExtra(EXTRA_TEXT) ?: return
                setReceiveData("$text withoutPermission")
            }
        }
    }
    

     앞서 구현된 화면에서 BroadcastReceiver 부분이 추가되었습니다. Action을 수신해야 하기때문에 IntentFilter도 동적으로 선언해 주도록 합시다. onResume() / onPause()에서 regist / unregist를 잊지 않도록 합니다. 잊어버리면 leak이 발생할 수 있기때문에 주의해야 합니다. 나중에 고생합니다. unregisterReceiver()에서는 기존에 등록된 BroadcastReceiver를 제거해 주는것이기 때문에 별다른 내용이 없습니다. registerReceiver()는 Permission을 구별하여 넣어주도록 합니다.

     발행된 메시지를 수신했을경우는 각각 어느 Receiver에서 수신했는지 추가하여 양쪽로그에 모두 적어줍니다. 여기서 기대되는것은 Permission에 따라 수신이 안되는것이 있을거라는 예상입니다.

     위에서 언급은 안했지만 Permission이 포함되어있으면 접두어 'A' 없으면 'B', 다른 Permission 이면 'C'가 붙습니다. 여기서 끝난게 아닙니다. Manifest에 Permission을 선언해 주어야 정상적으로 동작을 합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="leeds.com.example">
    
        <permission android:name="com.leeds.example.broadcast.permission.ACTION_PERMISSION" android:protectionLevel="signature"/>
        <uses-permission android:name="com.leeds.example.broadcast.permission.ACTION_PERMISSION" />
        <permission android:name="com.leeds.example.broadcast.permission.ACTION_ANOTHER" android:protectionLevel="signature"/>
        <uses-permission android:name="com.leeds.example.broadcast.permission.ACTION_ANOTHER" />
    	
    	...
    	
    </manifest>
    

     Manifest에 Permission까지 선언을 했으니 테스트를 해보도록 하겠습니다.


     Permission 포함 / Permission 미포함 / 다른 Permission 이렇게 3개의 버튼을 한번씩 눌렀습니다.



     결과는 모두 수신되었습니다. 현재까지는 Permission의 의미가 없어보입니다. 동일한 앱에서 전송하는것은 의미가 없나 싶어서 별도의 앱을 하나 더 만들어서 테스트를 진행했습니다. 이번에는 UI는 없고 Manifest에 BroadcastReceiver만 설정하여 테스트 하겠습니다.

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.leeds.parser.app2">
        <application>
    		
    		...
    		
            <receiver
                android:name=".ExternalReceiver"
                android:enabled="true"           
                android:exported="true">
                <intent-filter>
                    <action android:name="com.leeds.example.broadcast.ACTION_PERMISSION" />
                </intent-filter>
            </receiver>
               
    	   ...
    	   
        </application>
    </manifest>
    

     추가 앱을 생성하고 정적 BroadcastReceiver를 추가하였습니다. 해당 Receiver는 별다른 동작없이 수신된 이벤트에서 text만 꺼내어 로그만 찍도록 되어있습니다.


    W/BroadcastQueue: Permission Denial: broadcasting Intent .....

    I/TEST: ExternalReceiver receive!!! B


     중요하지 않은 부분은 생략하고 결과만 두었습니다. 보면 Permission을 담아서 보낸 접두어 'A'는 표출되지 않고 Permission Denial이라고 로그가 찍혔습니다. 두번째 Permission을 생략하고 보낸 접두어 'B'는 정상적으로 로그가 찍혔습니다. 추가로 <uses-permission>을 이용하여 해당 Permission을 추가하는 경우는 두개의 메시지를 정상적으로 수신하였습니다. 

     앞선 앱에서 생성한 Permission은 android:protectionLevel="signature"로 선언하였기 때문에 같은 인증서로 서명된 앱이 아니면 Permission을 사용할 수 없습니다. protectionLevel에 대한 상세한 내용은 개발자 사이트를 참고하시면 됩니다. 

     참고 : https://developer.android.com/guide/topics/manifest/permission-element#plevel


    뭔가 애매하지만 지금까지 테스트 한 결과로 정리를 해보면 

    • sendBroadcast() 메소드에서 Permission 정보를 넣어서 권한을 설정해 줄수 있고, 해당 Permission은 Manifest 파일에 선언하여야 정상동작 한다.
    • 같은 앱에서 resigerReceiver() 메소드에서 Permission 정보를 넣어서 등록을 하여도 별다른 차이는 없다. 해당 Permission으로 발행된 이벤트만 들어올것이라 생각했는데 아니다.
    • Permission 정보를 넣어서 발행한 이벤트는 다른 앱에서 BroadcastReceiver를 이용하여 수신하려면 <uses-permission>을 이용하여 Permission을 사용하도록 선언후 수신이 가능하다.
     정도로 정리가 됩니다. 개인적으로 느낀점은 Permission의 ProtectionLevel을 이용하면 외부에서 sendBroadcast() 된 이벤트를 접근하지 못하게 되고 아주 최소한의 정보만 습득이 가능하게 된다고 보입니다. 위에선 생략이 되었지만 Action 명칭은 로그상에서 표출이 되기 때문에 좀 더 보안측면으로 설정하고자 한다면 Action은 단순하게 하여 활용하고 함께 전달되는 Intent에 중요 정보들을 넣어서 전달하는 방식으로 구현하면 될것 같습니다.


    반응형
Designed by Tistory.