diadia

興味があることをやってみる。自分のメモを残しておきます。

Android 認証の実装について調べる

androidの認証機能の実装のキーワードはAccountManager

AccountManagerは、アクセストークンを管理することができるようだ。

用語

  • Account Type : Authenticator を特定するための文字列。account-authenticatorタグのandroid:accountType属性値を指す。

androidのユーザー認証の方法

  • ユーザーログイン画面を表示し、ユーザー名とパスワードを入力させてログインさせる。
  • ユーザーログイン画面を表示し、ユーザーにどのアカウントを使うか選ばせログインさせる。(パスワード等不要)

使い勝手を良くするためには、後者の仕組みを実装すると良いと思われる。

後者の仕組みはユーザー認証画面にリダイレクトさせるか、ユーザー認証ページにアクセスすると、まずアプリケーションに該当するアカウントの一覧を表示させる。 そのアカウントの中からユーザーを選択させる。 選択したユーザーのアクセストークンを使ってwebサービスにログインを試みる。 ログインが成功した場合アクセストークンの更新を実施する。失敗した場合にはユーザーログイン画面を表示させユーザーにログインを促す。 username,passwordによってログインしたユーザーがアプリケーションに該当するアカウントの一覧に含まれない場合にアカウントを追加し、アクセストークンを追加する。 アカウントの一覧に追加される場合にはアクセストークンを更新する。

こんな感じで実装できれば良いんじゃないか??

実装方法

概要

作成するファイル

実際編集(作成)するファイルは5つある。

  • AndroidManifst.xml
  • authenticator.xml
  • 認証用のActivity.kt
  • AbstractAccountAuthenticatorを継承したAuthenticatorファイル
  • それらを動作させる役割をもつAuthenticatorService.ktファイル

AndroidManifst.xml

AndroidManifest.xmlに記述する事項がある。

https://developer.android.com/training/sync-adapters/creating-authenticator#DeclareAuthenticator

<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<service
     android:name=".AuthenticatorService"
     android:exported="false">
     <intent-filter>
          <action android:name="android.accounts.AccountAuthenticator" />
     </intent-filter>
     <meta-data
          android:name="android.accounts.AccountAuthenticator"
          android:resource="@xml/authenticator" />
</service>

serviceタグのname属性について補足:android:name属性にはサブクラスを記述する。自分の間違いはファイル名を記述しエラーを出してしまった。 つまりAuthenticationService.ktファイル内にAuthenticatorServiceクラスを記述している場合には、AuthenticatorServiceをandroid:nameに記述する。

authenticator.xml

res/xml/authenticator.xmlを作成する。account-authenticatorタグを使い記述する。 通常このファイルはauthenticator.xmファイルと名付けるようだ。

認証システム メタデータ ファイルを追加する

<account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="com.example.test"
    android:icon="@drawable/ic_launcher"
    android:smallIcon="@drawable/ic_launcher"
    android:label="@string/account_label" />

認証用のHogeActivity.kt

認証用Activiti.ktファイルには、ログインの認証だけを記述することに加えて、AccountManagerインスタンスを作成し、addAccountExplicityメソッドを使ってカスタムアカウントを作成するロジックを書く。

class MyLoginActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_login)


    //Viewオブジェクトを取得
    val et_emailAddress = findViewById<EditText>(R.id.et_emailAddress)
    val et_password = findViewById<EditText>(R.id.et_password)
    val btn_login = findViewById<Button>(R.id.btn_login)

     btn_login.setOnClickListener {

        //入力内容を取得
        val inputEmailAddress = et_emailAddress.text.toString()
        val inputPassword = et_password.text.toString()
        fun login(inputEmailAddress, inputPassword)
      }
   }
    fun login(inputEmailAddress:String, inputPassword:String){

            //okhttpで通信してログインを実施する
            //postの内容(body)を生成
            val url: String = "http://10.0.2.2:8000/rest-auth/login/"
            val body = FormBody.Builder()
                .add("email", inputEmailAddress)
                .add("password", inputPassword)
                .build()

            //クライアントオブジェクトを生成
            val client: OkHttpClient = OkHttpClient.Builder().build()

            //Requestオブジェクトを生成
            val request = Request.Builder().url(url).post(body).build()

            client.newCall(request).enqueue(object : Callback {

                override fun onFailure(call: Call?, e: IOException?) {
                    println("fail : $e")
                }

                override fun onResponse(call: Call, response: Response?) {

                       loginSuccess(email, password)
                  
                    }
                }
            })
      }
    fun loginsuccess(email, password){

              /*
              Account(username, your_account_type).also { account ->
              accountManager.addAccountExplicitly(account, password, null)
              }
              */
          //以下でも同じことが実現できる
          val account:Account = Account(email, "com.example.test")
          val am:AccountManager = AccountManager.get(this)         
          am.addAccountExplicitly(account, password, null)

          //am.setAuthToken(account, authTokenType, authToken)


        // 認証画面終了
        setResult(RESULT_OK);
        finish();
        }

}          

AbstractAccountAuthenticatorを継承したAuthenticatorクラスのファイル

カスタムアカウントを使う場合には、AccountManegerはAuthenticatorを使ってトークンを取得したりトークンを保存したりする。

で、Authenticatorを呼び出すためにAuthenticationService.ktというサービスを作成する。

デフォルトではAuthenticatorは存在しない。だから作成する必要がある。 Authenticatorを作成する一番簡単な方法にAbstractAccountAuthenticatorを継承する方法がある。

  • addAccount()
  • getAuthToken()

上記メソッドをオーバーライドすれば機能するかと思ったが、その他のメソッドもオーバーライドしないとエラーが出てしまうことに注意したい。

オーバーライドする必要があるメソッドは以下の7種類存在する。

  • addAccount() : Adds an account of the specified accountType.
  • getAuthToken() : Gets an authtoken for an account.
  • getAuthTokenLabel() : Ask the authenticator for a localized label for the given authTokenType.
  • updateCredentials() : Update the locally stored credentials for an account.
  • confirmCredentials() : Checks that the user knows the credentials of an account.
  • hasFeatures() : Checks if the account supports all the specified authenticator specific features.
  • editProperties() : Returns a Bundle that contains the Intent of the activity that can be used to edit the properties.

https://developer.android.com/training/id-auth/custom_auth#ExtendThatThing

スタブ認証システム コンポーネントを追加する

https://stackoverflow.com/questions/31915642/android-login-account-authenticator-vs-manual-authentication

https://developer.android.com/training/sync-adapters/creating-authenticator#CreateAuthenticatorService

class Authenticator(context:Context): AbstractAccountAuthenticator(context) {

    
    override fun addAccount(
        response: AccountAuthenticatorResponse,
        accountType: String,
        authTokenType: String,
        requiredFeatures: Array<String>,
        options: Bundle
    ): Bundle? = null //{print("test")}


   
    override fun getAuthToken(
        response: AccountAuthenticatorResponse,
        account: Account,
        authTokenType: String,
        options: Bundle
    ): Bundle? = null 


    override fun getAuthTokenLabel(s: String): String {
        throw UnsupportedOperationException()
    }

    override fun updateCredentials(
        r: AccountAuthenticatorResponse,
        account: Account,
        s: String,
        bundle: Bundle
    ): Bundle {
        throw UnsupportedOperationException()
    }

    override fun confirmCredentials(
        r: AccountAuthenticatorResponse,
        account: Account,
        bundle: Bundle
    ): Bundle?  = null


    override fun hasFeatures(
        r: AccountAuthenticatorResponse,
        account: Account,
        strings: Array<String>
    ): Bundle {
        throw UnsupportedOperationException()
    }

    override fun editProperties(r: AccountAuthenticatorResponse, s: String): Bundle {
        throw UnsupportedOperationException()
    }

}

AuthenticatorService.kt

認証システムクラス(Authenticatorクラス)を動作させるには、認証システムサービスを作成する必要がある。 認証システムサービスはonCreate() で認証システムクラスのインスタンスを作成し、onBind() で getIBinder() を呼び出すロジックで書く。 他の言い方をすれば、サービスのonBindメソッド中にgetIBinder()メソッドを返す記述を書くのが縛りとなっている。

https://developer.android.com/training/id-auth/custom_auth#TaskFour

https://developer.android.com/reference/kotlin/android/accounts/AbstractAccountAuthenticator.html?hl=en

(実装例:AuthenticatorService.kt)
package com.example.loggingtrial

import android.app.Service
import android.content.Intent
import android.os.IBinder


class AuthenticatorService: Service() {

    private lateinit var myAuthenticator:Authenticator ;

    override fun onCreate() {
        super.onCreate()
        // AuthenticatorはAuthenticator.ktで作成したクラスである
        myAuthenticator = Authenticator(applicationContext)
    }
    
    override fun onBind(intent: Intent) : IBinder {
        return myAuthenticator.getIBinder()
    }
}

メモaddAccount()メソッドに書く内容

AbstractAccountAuthenticator  |  Android デベロッパー  |  Android Developers addAccount(response: AccountAuthenticatorResponse!, accountType: String!, authTokenType: String!, requiredFeatures: Array<String!>!, options: Bundle!)

具体的な実装

https://developer.android.com/training/id-auth/custom_auth?hl=ja#ExtendThatThing

https://developer.android.com/reference/android/accounts/AbstractAccountAuthenticator.html?hl=ja

MyAuthenticator,kt


class MyAuthenticator: AbstractAccountAuthenticator {

    public static final String ACCOUNT_TYPE = "com.example.test";

    val conrext = SingletonContext.getAppicationContext()

    public MyAuthenticator(Context context) {
        super(context);
        mContext = context;
    }

関連ページ

アプリ起動時に前回ログインしたユーザーを選ばせる

まずSharedPreferenceを使って最後にログインしたアカウントを特定する。そしてそのアカウントがログインしたままかログアウトしたままかチェックする。 ログインしたままであればAccountManagerで当該アカウントのアクセストークンを取得してwebアプリケーションに接続できるか確認するフローをとする。 最後にログインしたアカウントがログアウトであれば、AccountManagerを使ってアカウント一覧をダイアログで表示させログインしたいアカウントをユーザーに選ばせる。