웨어러블 기기에서 인증

Wear OS 앱은 호환 앱 없이도 독립형으로 실행할 수 있습니다. 따라서 Wear OS 앱은 인터넷에서 데이터에 액세스할 때 자체적으로 인증을 관리해야 합니다. 시계의 작은 화면 크기와 줄어든 입력 기능으로 Wear OS 앱에서 사용할 수 있는 인증 옵션이 제한됩니다.

이 가이드에서는 Wear OS 앱에 권장되는 인증 방식과 이러한 방식이 앱의 사용 사례에 맞지 않는 경우의 대체 방식을 설명합니다.

좋은 로그인 환경을 설계하는 방법을 자세히 알아보려면 로그인 UX 가이드를 참고하세요.

게스트 모드

모든 기능에 대해 인증을 요구해서는 안 됩니다. 대신 로그인하지 않아도 사용할 수 있는 기능을 가능한 한 많이 사용자에게 제공하세요.

사용자는 모바일 앱을 사용하지 않고 Wear 앱을 검색하고 설치할 수 있으므로 계정이 없거나 어떤 기능이 제공되는지 모를 수도 있습니다. 게스트 모드 기능이 앱의 기능을 정확하게 보여줘야 합니다.

권장되는 인증 방식

다음 인증 방식을 사용하여 독립형 Wear OS 앱에서 사용자 인증 정보를 가져올 수 있습니다.

데이터 영역을 사용하여 토큰 전달

휴대전화의 호환 앱은 웨어러블 데이터 영역을 통해 인증 데이터를 Wear OS 앱에 안전하게 전송할 수 있습니다. 사용자 인증 정보를 메시지 또는 데이터 항목으로 전송합니다.

이러한 유형의 인증은 일반적으로 사용자의 작업 없이도 가능합니다. 그러나 사용자에게 로그인 중임을 알리지 않고 인증을 실행해서는 안 됩니다. 닫을 수 있는 간단한 화면을 통해 계정이 모바일에서 전송 중임을 사용자에게 알릴 수 있습니다.

중요: 이 옵션은 모바일 앱이 설치된 경우에만 Android와 페어링된 시계에서 작동하므로 Wear 앱은 다른 인증 방법을 하나 이상 제공해야 합니다. 모바일 앱이 없거나 Wear OS 기기가 iOS 기기와 페어링된 사용자를 위해 대체 인증 방법을 제공하세요.

다음 예와 같이 모바일 앱의 데이터 영역을 사용하여 토큰을 전달합니다.

val token = "..." // Auth token to transmit to the wearable device.
val dataClient: DataClient = Wearable.getDataClient(context)
val putDataReq: PutDataRequest = PutDataMapRequest.create("/auth").run {
    dataMap.putString("token", token)
    asPutDataRequest()
}
val putDataTask: Task<DataItem> = dataClient.putDataItem(putDataReq)

다음 예와 같이 시계 앱에서 데이터 변경 이벤트를 수신 대기합니다.

val dataClient: DataClient = Wearable.getDataClient(context)
dataClient.addListener{ dataEvents ->
    dataEvents.forEach { event ->
        if (event.type == DataEvent.TYPE_CHANGED) {
            val dataItemPath = event.dataItem.uri.path ?: ""
            if (dataItemPath.startsWith("/auth")) {
                val token = DataMapItem.fromDataItem(event.dataItem).dataMap.getString("token")
                // Display interstitial screen to notify the user they are being signed in.
                // Then, store the token and use it in network requests.
            }
        }
    }
}

웨어러블 데이터 영역 사용에 관한 자세한 내용은 Wear OS에서 데이터 보내기 및 동기화를 참고하세요.

OAuth 2.0 사용

Wear OS는 다음 섹션에 설명된 두 가지 OAuth 2.0 기반 흐름을 지원합니다.

  • RFC 7636에 정의된 PKCE(Proof Key for Code Exchange)로 승인 코드 부여
  • RFC 8628에 정의된 기기 승인 부여

참고: 시계가 대기 모드로 전환될 때 앱이 종료되지 않도록 하려면 인증을 실행하는 활동에서 AmbientModeSupport.attach를 사용하여 상시 사용 설정을 켭니다. 대기 모드에서의 자세한 권장사항 내용은 Wear에서 앱 표시 유지를 참고하세요.

PKCE(Proof Key for Code Exchange)

PKCE를 효과적으로 사용하려면 RemoteAuthClient를 사용하세요.

Wear OS 앱에서 OAuth 제공자에 인증 요청을 실행하려면 OAuthRequest 객체를 만듭니다. 이 객체는 CodeChallenge 객체와 토큰을 가져오기 위한 OAuth 엔드포인트 URL로 구성됩니다. 다음 코드는 인증 요청을 만드는 예를 보여줍니다.

val request = OAuthRequest.Builder(this.applicationContext)
    .setAuthProviderUrl(Uri.parse("https://...."))
    .setClientId(clientId)
    .setCodeChallenge(codeChallenge)
    .build()

인증 요청을 빌드한 후 sendAuthorizationRequest() 메서드를 사용하여 호환 앱으로 보냅니다.

val client = RemoteAuthClient.create(this)
client.sendAuthorizationRequest(request,
    { command -> command?.run() },
    object : RemoteAuthClient.Callback() {
        override fun onAuthorizationResponse(
            request: OAuthRequest,
            response: OAuthResponse
        ) {
            // Extract the token from the response, store it and use it in network requests.
        }

        override fun onAuthorizationError(errorCode: Int) {
            // Handle error
        }
    }
)

이 요청은 호환 앱에 호출을 트리거하고, 이렇게 하면 사용자 휴대전화의 웹브라우저에 승인 UI가 표시됩니다. OAuth 2.0 제공자가 사용자를 인증하고 요청된 권한에 관한 사용자 동의를 얻습니다. 응답은 자동으로 생성된 리디렉션 URL로 전송됩니다.

승인이 성공 또는 실패한 후 OAuth 2.0 서버가 요청에 지정된 URL로 리디렉션됩니다. 사용자가 액세스 요청을 승인하면 응답에 승인 코드가 포함됩니다. 사용자가 요청을 승인하지 않으면 응답에 오류 메시지가 포함됩니다.

응답은 쿼리 문자열 형식이며 다음 예 중 하나와 유사합니다.

  https://wear.googleapis.com/3p_auth/com.your.package.name?code=xyz
  https://wear.googleapis-cn.com/3p_auth/com.your.package.name?code=xyz

이렇게 하면 사용자를 호환 앱으로 연결하는 페이지가 로드됩니다. 호환 앱은 응답 URL을 확인하고 onAuthorizationResponse API를 사용하여 서드 파티 시계 앱에 응답을 전달합니다.

그러면 시계 앱은 승인 코드를 액세스 토큰으로 교환할 수 있습니다.

참고: OAuthRequest가 빌드되면 redirectUrl에 액세스하여 리디렉션 URL을 찾을 수 있습니다.

기기 승인 부여

기기 승인 부여를 사용하면 사용자는 다른 기기에서 인증 URI를 열게 됩니다. 그러면 승인 서버는 사용자에게 요청을 승인하거나 거부하도록 요청합니다.

이 과정을 더 쉽게 하려면 다음 예에서와 같이 RemoteActivityHelper를 사용하여 페어링된 사용자의 휴대기기에서 웹페이지를 엽니다.

// Request access from the authorization server and receive Device Authorization Response.
val verificationUri = "..." // Extracted from the Device Authorization Response.
RemoteActivityHelper.startRemoteActivity(
    this,
    Intent(Intent.ACTION_VIEW)
        .addCategory(Intent.CATEGORY_BROWSABLE)
        .setData(Uri.parse(verificationUri)),
    null
)
// Poll the authorization server to find out if the user completed the user authorization
// step on their mobile device.

iOS 앱을 사용하는 경우 브라우저에서 토큰을 승인하는 방법 대신 범용 링크를 사용하여 앱에서 이 인텐트를 가로챕니다.

기타 인증 방식

Wear OS는 추가 로그인 방식(다음 섹션에서 설명)을 지원합니다.

Google 로그인

Google 로그인을 사용하면 사용자가 기존 Google 계정으로 로그인할 수 있습니다. Google 로그인은 최상의 사용자 환경을 제공하며 지원하기 쉽습니다. 특히, 휴대기기 앱에서 이미 구현한 경우 더욱 그렇습니다.

앞서 설명한 권장 인증 방식 다음에 Google 로그인이 가장 선호되는 솔루션입니다. iOS에서도 잘 작동하기 때문입니다. 다음 섹션에서는 기본적인 Google 로그인 통합을 완료하는 방법을 설명합니다.

기본 요건

Wear OS 앱에서 Google 로그인 통합을 시작하기 전에 Google API 콘솔 프로젝트를 구성하고 Android 스튜디오 프로젝트를 설정해야 합니다. 자세한 내용은 Android 앱에 Google 로그인 통합 시작하기를 참고하세요.

백엔드 서버와 통신하는 앱 또는 사이트에서 Google 로그인을 사용한다면 다음과 같은 두 가지 기본 요건이 추가됩니다.
  • 백엔드 서버의 OAuth 2.0 웹 애플리케이션 클라이언트 ID를 만듭니다. 이 클라이언트 ID는 앱의 클라이언트 ID와 다릅니다. 자세한 내용은 서버 측 액세스 사용 설정을 참고하세요.
  • HTTPS를 통해 사용자의 ID 토큰을 전송하여 현재 로그인한 사용자를 서버에서 안전하게 식별합니다. 백엔드 서버에서 사용자를 인증하는 방식을 알아보려면 백엔드 서버에서 인증을 참고하세요.

앱에 Google 로그인 통합

Google 로그인을 Wear OS 앱에 통합하려면 다음 섹션에서 자세히 설명하는 다음 단계를 검토하고 구현하세요.

  1. Google 로그인을 구성합니다.
  2. Google 로그인 버튼을 추가합니다.
  3. 로그인 버튼을 탭하면 로그인 흐름을 시작합니다.

Google 로그인 구성 및 GoogleApiClient 객체 빌드

로그인 활동의 onCreate() 메서드에서 앱에 필요한 사용자 데이터를 요청하도록 Google 로그인을 구성합니다. 그런 다음, Google 로그인 API 액세스 권한과 지정한 옵션을 사용하여 GoogleApiClient 객체를 만듭니다. 이러한 단계는 다음 예에 나와 있습니다.

  public class MyNewActivity extends AppCompatActivity {

    private static final int RC_SIGN_IN = 9001;

    private GoogleSignInClient mSignInClient;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        GoogleSignInOptions options =
               new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                        .build();

        mSignInClient = GoogleSignIn.getClient(this, options);
    }
  }

앱에 Google 로그인 버튼 추가

Google 로그인 버튼을 추가하려면 다음 단계를 따르세요.
  1. 앱 레이아웃에 SignInButton를 추가합니다.
  2.  <com.google.android.gms.common.SignInButton
     android:id="@+id/sign_in_button"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content" />
    
  3. 앱의 onCreate() 메서드에서 버튼을 탭하면 사용자가 로그인되도록 버튼의 OnClickListener를 등록합니다.
  4. Kotlin

    findViewById<View>(R.id.sign_in_button).setOnClickListener(this)
    

    Java

    findViewById(R.id.sign_in_button).setOnClickListener(this);
    

로그인 인텐트 생성 및 로그인 흐름 시작

getSignInIntent() 메서드로 로그인 인텐트를 만들어 onCLick() 메서드에서 로그인 버튼 탭을 처리합니다. 그런 다음 startActivityForResult() 메서드로 인텐트를 시작합니다.

  Intent intent = mSignInClient.getSignInIntent();
  startActivityForResult(intent, RC_SIGN_IN);

The user is prompted to select a Google account to sign in with. If you requested scopes beyond profile, email, and open ID, the user is also prompted to grant access to those resources.

Finally, in the activity's onActivityResult method, retrieve the sign-in result with getSignInResultFromIntent. After you retrieve the sign-in result, you can check whether the sign-in succeeded using the isSuccess method. If sign-in succeeds, you can call the getSignInAccount method to get a GoogleSignInAccount object that contains information about the signed-in user, such as the user's name. These steps are shown in the following example:

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
    super.onActivityResult(requestCode, resultCode, data)

    // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...).
    if (requestCode == RC_SIGN_IN) {
        Auth.GoogleSignInApi.getSignInResultFromIntent(data)?.apply {
            if (isSuccess) {
                // Get account information.
                fullName = signInAccount?.displayName
                mGivenName = signInAccount?.givenName
                mFamilyName = signInAccount?.familyName
                mEmail = signInAccount?.email
            }
        }
    }
}

Java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...).
    if (requestCode == RC_SIGN_IN) {
        GoogleSignInResult signInResult = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        if (signInResult.isSuccess()) {
            GoogleSignInAccount acct = signInResult.getSignInAccount();

            // Get account information.
            fullName = acct.getDisplayName();
            givenName = acct.getGivenName();
            familyName = acct.getFamilyName();
            email = acct.getEmail();
        }
    }
}

Google 로그인을 구현하는 샘플 앱을 보려면 GitHub의 Horologist Google 로그인 샘플을 참고하세요.

맞춤 코드 인증

앞에서 설명한 인증 방법의 대안으로, 사용자에게 스마트폰 또는 태블릿과 같은 다른 기기에서 인증하고 단기간 사용할 수 있는 숫자 코드를 얻도록 요구할 수 있습니다. 그러면 사용자는 Wear OS 기기에 코드를 입력하여 ID를 확인하고 인증 토큰을 받습니다.

이 인증 흐름은 앱의 로그인 모듈을 사용하거나 서드 파티 인증 제공자 로그인 방식을 앱 코드에 수동으로 통합합니다. 이 인증 방법은 수동 작업과 추가 보안 조치가 필요하지만, 독립형 Wear OS 앱에서 초기 인증이 필요한 경우 사용할 수 있습니다.

이 설정의 인증 흐름은 다음과 같습니다.

  1. 사용자가 승인이 필요한 Wear OS 앱으로 작업을 실행합니다.
  2. Wear OS 앱이 사용자에게 인증 화면을 표시하고, 사용자에게 지정된 URL의 코드를 입력하도록 지시합니다.
  3. 사용자가 휴대기기, 태블릿 또는 PC로 전환하고 브라우저를 시작한 다음, Wear OS 앱에 지정된 URL로 이동하여 로그인합니다.
  4. 사용자가 단기간 사용할 수 있는 숫자 코드를 받은 다음, Wear OS에 내장된 키보드를 사용하여 Wear OS 앱 인증 화면에 이 코드를 입력합니다.

  5. 이 시점부터 입력한 코드를 사용자 신원 증명으로 사용할 수 있으며, 이후의 인증된 호출을 위해 Wear OS 기기에 저장 및 보호되는 인증 토큰으로 교환할 수 있습니다.

참고: 사용자가 생성하는 코드는 숫자로만 구성되어야 하며 영문자를 포함할 수 없습니다.

이 인증 흐름은 다음 차트에 나와 있습니다.