본문 바로가기
개발/Android

[Android] AccountManager 기본 예제

by JhDroid 2021. 9. 19.
728x90

AccountManager

  • 안드로이드 기기의 '설정 > 계정 및 백업 > 계정 관리'에 들어가보면 여러 앱에서 로그인한 계정이 등록된것을 확인할 수 있는데 이 계정 목록에 접근하거나 계정을 추가하는 작업을 도와주는 것이 AccountManager 입니다.
  • AccountManager를 사용해 자신의 앱에서 사용할 '맞춤 계정 유형(Account Type)'을 생성하고 이를 통해 계정 목록에서 '계정 유형'에 해당하는 계정을 불러올 수 있습니다.
  • AccountManager는 암호화 서비스나 키체인이 아니고 개발자가 전달한 사용자 인증 정보를 '일반 텍스트로 저장'합니다.
    • 루트에서만 액세스 가능한 데이터베이스에 저장하기 때문에 대부분의 기기에서는 특별히 우려할 사안은 아님
    • 단, 루팅된 기기에서는 adb 권한이 있는 누구나 사용자 정보를 읽을 수 있음
  • 예제 프로젝트
 

GitHub - JhDroid/accountmanager-sample: android accountmanager sample

android accountmanager sample. Contribute to JhDroid/accountmanager-sample development by creating an account on GitHub.

github.com

 

AccountManager 계정 추가하기

  • 사용자 계정 추가를 위한 권한 추가
<!--맞춤 사용자 유형 추가를 위한 권한-->
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>

 

  • AccountManager 객체 생성 및 사용자 계정 추가 함수 생성
    • 예제에서는 AccountManager 객체를 관리할 object 클래스를 생성해서 사용했습니다.
object AccountHelper {
    private var accountManager: AccountManager? = null
    
    /**
     * AccountManager에 계정 이름을 쿼리할 때 계정 유형별로 필터링 가능
     * 계정 유형은 계정을 발급한 주체를 고유하게 식별하는 문자열
     * Google의 계정 유형은 'com.google', Twitter의 계정 유형은 'com.twitter.android.auht.login'
     * */
    private const val MY_ACCOUNT_TYPE = "com.jhdroid.auth.login"

    fun initAccountManager(context: Context) {
        accountManager = AccountManager.get(context)
    }
    
    fun getAccountManager(): AccountManager? = accountManager
    
    // 사용자 계정 추가 함수
    fun addAccount(id: String, pw: String) {
        Account(id, MY_ACCOUNT_TYPE).also { account ->
            accountManager?.addAccountExplicitly(account, pw, null)
        }
    }
}
  • 맞춤 계정 유형은 자신의 앱에서 추가한 계정을 불러오기 위한 'Key' 역할을 합니다.
  • AccountManager 클래스의 addAccountExplicitly() 함수를 통해 계정 추가가 가능합니다.
  • id, pw는 사용자로 부터 입력받아야 하며 입력을 받기위한 로그인 화면이 필요합니다.

 

  • 로그인 화면 만들기
    • 로그인 화면은 간단하게 EditText 2개와 버튼 하나로만 구성했습니다.
    • 예제 프로젝트이기 때문에 계정 정보를 따로 암호화하지는 않지만 계정 정보는 암호화해서 등록하는 것을 추천드립니다.
class AuthenticatorActivity : AppCompatActivity() {

    private val binding by lazy { ActivityLoginBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.loginSubmitBtn.setOnClickListener {
            val accountId = binding.loginIdEdt.text.toString()
            val accountPw = binding.loginPwEdt.text.toString()

			// 사용자 계정 추가
            AccountHelper.addAccount(accountId, accountPw)
            setResult(RESULT_OK)
            finish()
        }
    }
}

 

  • AbstractAccountAuthenticator 확장 및 addAccount() 추상 메소드 구현
class Authenticator(
    private val context: Context
): AbstractAccountAuthenticator(context) {
    /**
     * '설정 > 계정 및 백업 > 계정 관리 > 계정 추가'에서 자신의 앱을 선택하면 KEY_INTENT로 넘겨주는 Intent를 실행해줌
     * 보통은 인증 정보를 받기위해 로그인 화면으로 이동시킨다. (ex: 네이버, 페이코)
     * */
    override fun addAccount(
        response: AccountAuthenticatorResponse,
        accountType: String,
        accountTokenType: String,
        requiredFeatures: Array<out String>,
        options: Bundle
    ): Bundle {
        Timber.d("addAccount()")

        val intent = Intent(context, AuthenticatorActivity::class.java).apply {
            putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType)
            putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
        }

        return Bundle().also {
            it.putParcelable(AccountManager.KEY_INTENT, intent)
        }
    }
    
    ...
}

 

  • 인증자 서비스 생성
/**
 * 인증자 서비스
 * 인증자는 여러 애플리케이션에서 사용할 수 있어야 하고 백그라운드에서 작동해야 하므로 Service내에서 실행해야 하며 이를 '인증자 서비스'라고 함
 * 인증자 서비스는 AbstractAccountAuthenticator를 확장한 클래스(Authenticator)의 getIBinder()를 리턴해주면 된다.
 * */
class AuthenticatorService : Service() {
    override fun onBind(intent: Intent?): IBinder? {
        val authenticator = Authenticator(this)
        return authenticator.iBinder
    }
}

 

  • 계정 목록에서 자신의 앱을 나타낼 리소스 생성

 

 

 

 

  • 해당 목록에서 자신의 앱을 나타내기 위한 아이콘과 앱 이름을 설정해 줘야 합니다.
  • 리소스 폴더에 'xml' 폴더를 생성하고 'authenticator.xml' 파일을 생성합니다.(이름은 상관 없습니다.)
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="@string/account_type"
    android:icon="@mipmap/ic_launcher"
    android:smallIcon="@mipmap/ic_launcher"
    android:label="@string/account_label" />
  • accountType : 자신의 앱에서 사용하는 맞춤 계정 유형
  • icon, smallIcon : 자신의 앱을 나타내는 아이콘
  • label : 자신의 앱 이름

 

 

 

 

 

 

 

 

  • 계정 추가 및 확인

 

 

계정 정보 불러오기

  • 위에서 추가한 계정 정보를 불러오는 방법을 알아보겠습니다.
  • 계정 목록을 불러오기 위한 권한 추가
<!--기기의 계정 목록을 가져오기 위한 권한-->
<uses-permission android:name="android.permission.GET_ACCOUNTS"
    android:maxSdkVersion="22" />

 

  • 계정 목록 반환 함수 추가
    • 위에서 추가한 object 클래스(AccountHelper)에 계정 목록을 불러오는 함수 추가
  • AccountManager의 getAccountsByType(<AccountType>) 함수와 getAccounts() 함수로 계정 목록을 불러올 수 있습니다.
private const val MY_ACCOUNT_TYPE = "com.jhdroid.auth.login"

/**
 * 사용자 계정 목록을 가져옴
 * 해당 API에서 개인 정보 및 민감한 사용자 데이터를 반환하므로 이를 사용자에게 명확하게 공개해야함
 * @Account Account 객체 자체는 데이터를 보호하지 않으며 사용자 계정 이름 이외의 다른 항목에 액세스할 권한을 부여하지 않음
 * */
fun getMyAccounts(): Array<out Account>? = accountManager?.getAccountsByType(MY_ACCOUNT_TYPE)

/**
 * 접근 가능한 모든 계정 목록을 불러옴
 * */
fun getAccounts(): Array<out Account>? = accountManager?.accounts

 

  • 반환된 계정 정보 확인
    • 계정 정보는 'Account' 객체로 확인 가능합니다.
    • Account 클래스는 name과 type으로만 이루어진 간단한 데이터 클래스입니다.
      • name은 계정을 등록할 때 ID이고 type도 authenticator.xml에서 등록한 accountType 입니다.
  • MainActivity에 버튼을 추가하고 textView에 계정 정보를 출력해서 확인해봅니다.
binding.mainMyAccountBtn.setOnClickListener {
    AccountHelper.getMyAccounts().takeIf { !it.isNullOrEmpty() }?.forEach { account ->
        appendLog("name : ${account.name}\ntype : ${account.type}")
    } ?: appendLog("계정 정보 없음")
}
        
private fun appendLog(msg: String) {
    binding.mainAccountInfoTv.append("$msg\n\n")
}

 

 

기타

  • 계정 비밀번호 확인
    • 비밀번호는 AccountManager의 getPassword() 함수로 확인 가능합니다.
fun getPassword(account: Account?): String? {
    return try {
        accountManager?.getPassword(account)
    } catch (e: Exception) {
        ""
    }
}

 

다음 글은 AccountManager를 사용해 토큰을 관리하는 예제 입니다.

 

* 글에 틀린 부분이 있으면 댓글 부탁드립니다 :D

728x90