๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ์˜ OAuth 2.0 ๊ตฌํ˜„ ๊ฐ€์ด๋“œ: Deep Linking๊ณผ Secure Storage ํ™œ์šฉ

mrmount 2024. 10. 20.

 

 

 

1. ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ OAuth 2.0์˜ ์ค‘์š”์„ฑ

๋ชจ๋ฐ”์ผ ์•ฑ์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์†Œ์…œ ๋กœ๊ทธ์ธ๊ณผ API ์ธ์ฆ ์„ ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ OAuth 2.0 ์„ ํ†ตํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์—์„œ๋Š” ํ† ํฐ ์ €์žฅ ๋ฐฉ์‹๊ณผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ ์— ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 


 

2. Deep Linking๊ณผ Redirect ์ฒ˜๋ฆฌ

 

Deep Linking์ด๋ž€?

Deep Linking ์€ ์•ฑ ๋‚ด๋ถ€์˜ ํŠน์ • ํŽ˜์ด์ง€๋กœ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ”๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. OAuth ๋กœ๊ทธ์ธ ํ›„ ์‚ฌ์šฉ์ž๋ฅผ ๋‹ค์‹œ ์•ฑ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๋•Œ Deep Linking ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 

Android์—์„œ Deep Link ์„ค์ • ์˜ˆ์ œ

<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myapp" android:host="oauth" />
    </intent-filter>
</activity>

 

 

1. ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ OAuth 2.0์˜ ์ค‘์š”์„ฑ

๋ชจ๋ฐ”์ผ ์•ฑ์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ์†Œ์…œ ๋กœ๊ทธ์ธ๊ณผ API ์ธ์ฆ ์„ ์ž์ฃผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ OAuth 2.0 ์„ ํ†ตํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ชจ๋ฐ”์ผ ํ™˜๊ฒฝ์—์„œ๋Š” ํ† ํฐ ์ €์žฅ ๋ฐฉ์‹๊ณผ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ ์— ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 


 

2. Deep Linking๊ณผ Redirect ์ฒ˜๋ฆฌ

 

Deep Linking์ด๋ž€?

Deep Linking ์€ ์•ฑ ๋‚ด๋ถ€์˜ ํŠน์ • ํŽ˜์ด์ง€๋กœ ์‚ฌ์šฉ์ž๋ฅผ ๋ฐ”๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. OAuth ๋กœ๊ทธ์ธ ํ›„ ์‚ฌ์šฉ์ž๋ฅผ ๋‹ค์‹œ ์•ฑ์œผ๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•  ๋•Œ Deep Linking ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

 

Android์—์„œ Deep Link ์„ค์ • ์˜ˆ์ œ

<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="myapp" android:host="oauth" />
    </intent-filter>
</activity>

 

iOS์—์„œ Deep Link ์„ค์ • ์˜ˆ์ œ

// SceneDelegate.swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    if let url = URLContexts.first?.url {
        print("Deep Link URL: \(url)")
        // OAuth ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง
    }
}

์„ค๋ช…:
- Android์™€ iOS ๋ชจ๋‘ OAuth ์ธ์ฆ ํ›„ ์•ฑ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ•˜๋Š” Deep Link๋ฅผ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
- ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ์„ ์™„๋ฃŒํ•˜๋ฉด myapp://oauth ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

 


 

3. Secure Storage์— ํ† ํฐ ์ €์žฅํ•˜๊ธฐ

 

์™œ Secure Storage๊ฐ€ ํ•„์š”ํ•œ๊ฐ€?

๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ๋Š” Access Token๊ณผ Refresh Token ์ด ํƒˆ์ทจ๋˜์ง€ ์•Š๋„๋ก ๋ณด์•ˆ ์ €์žฅ์†Œ ์— ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. Android์™€ iOS์—์„œ Secure Storage ๋ฅผ ํ™œ์šฉํ•ด ๋ฏผ๊ฐํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณดํ˜ธํ•ฉ๋‹ˆ๋‹ค.

 

Android์—์„œ Secure Storage ์‚ฌ์šฉ ์˜ˆ์ œ

val sharedPreferences = getSharedPreferences("auth_prefs", Context.MODE_PRIVATE)
val editor = sharedPreferences.edit()

// ํ† ํฐ ์ €์žฅ
editor.putString("access_token", "your_access_token")
editor.apply()

// ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ
val accessToken = sharedPreferences.getString("access_token", null)

 

iOS์—์„œ Secure Storage ์‚ฌ์šฉ ์˜ˆ์ œ

import KeychainAccess

let keychain = Keychain(service: "com.example.myapp")

// ํ† ํฐ ์ €์žฅ
keychain["access_token"] = "your_access_token"

// ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ
if let accessToken = keychain["access_token"] {
    print("Access Token: \(accessToken)")
}

์„ค๋ช…:
- Android์—์„œ๋Š” SharedPreferences ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, ์•”ํ˜ธํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
- iOS์—์„œ๋Š” Keychain ์„ ์‚ฌ์šฉํ•ด ์•ˆ์ „ํ•˜๊ฒŒ ํ† ํฐ์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

 


 

4. SSO(Single Sign-On) ๊ตฌํ˜„ํ•˜๊ธฐ

 

SSO๋ž€?

SSO(Single Sign-On) ์€ ์‚ฌ์šฉ์ž๊ฐ€ ํ•œ ๋ฒˆ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋„ ๋™์ผํ•œ ์ธ์ฆ ์ •๋ณด ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. OAuth 2.0๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„  ํ•ฉ๋‹ˆ๋‹ค.

 

์˜ˆ์‹œ: Google SSO ์—ฐ๋™ ์ฝ”๋“œ (Android)

val googleSignInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_SIGN_IN)

fun loginWithGoogle() {
    val signInIntent = googleSignInClient.signInIntent
    startActivityForResult(signInIntent, 100)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == 100) {
        val task = GoogleSignIn.getSignedInAccountFromIntent(data)
        if (task.isSuccessful) {
            val account = task.result
            val idToken = account.idToken
            println("Google ID Token: $idToken")
        }
    }
}

์„ค๋ช…:
- Google SSO๋ฅผ ํ†ตํ•ด ํ•œ ๋ฒˆ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋‹ค๋ฅธ ์„œ๋น„์Šค๋„ ์ž๋™ ๋กœ๊ทธ์ธ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 


 

5. ๋ชจ๋ฐ”์ผ OAuth ๊ตฌํ˜„ ์‹œ ๊ณ ๋ คํ•  ๋ณด์•ˆ ์š”์†Œ

  1. PKCE ์‚ฌ์šฉ
    - ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ๋Š” PKCE ๋ฅผ ์‚ฌ์šฉํ•ด Authorization Code ํƒˆ์ทจ๋ฅผ ๋ฐฉ์ง€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. Refresh Token Rotation
    - Refresh Token์ด ์žฌ์‚ฌ์šฉ๋˜์ง€ ์•Š๋„๋ก ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ์šด Refresh Token์„ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.
  3. HTTPS ์‚ฌ์šฉ
    - ๋ชจ๋“  ํ†ต์‹ ์€ HTTPS ๋ฅผ ํ†ตํ•ด ์•”ํ˜ธํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  4. ํ† ํฐ ๋งŒ๋ฃŒ์™€ ์ž๋™ ๊ฐฑ์‹ 
    - Access Token์ด ๋งŒ๋ฃŒ๋˜๋ฉด Refresh Token์œผ๋กœ ์ž๋™ ๊ฐฑ์‹  ํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

 


 

6. ๋ชจ๋ฐ”์ผ OAuth ๊ตฌํ˜„ ํ๋ฆ„ ์ •๋ฆฌ

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์†Œ์…œ ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ์„ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค.
  2. OAuth ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ Authorization Code ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  3. PKCE ๋ฅผ ์‚ฌ์šฉํ•ด Authorization Code๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.
  4. Access Token์„ ๋ฐœ๊ธ‰๋ฐ›์•„ Secure Storage์— ์ €์žฅ ํ•ฉ๋‹ˆ๋‹ค.
  5. ํ•„์š” ์‹œ Refresh Token์„ ์‚ฌ์šฉํ•ด ํ† ํฐ์„ ๊ฐฑ์‹  ํ•ฉ๋‹ˆ๋‹ค.

 


 

FAQ

Q1. ๋ชจ๋ฐ”์ผ ์•ฑ์—์„œ ๋ฐ˜๋“œ์‹œ PKCE๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋‚˜์š”?
A1. ๋„ค, PKCE๋Š” ํ•„์ˆ˜์  ์ž…๋‹ˆ๋‹ค. ๋ชจ๋ฐ”์ผ ์•ฑ์˜ ๊ณต๊ฐœ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ฝ”๋“œ ํƒˆ์ทจ๋ฅผ ๋ฐฉ์ง€ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

Q2. Access Token๊ณผ Refresh Token์€ ์–ด๋””์— ์ €์žฅํ•ด์•ผ ํ•˜๋‚˜์š”?
A2. Android์—์„œ๋Š” KeyStore ๋˜๋Š” SharedPreferences , iOS์—์„œ๋Š” Keychain ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Q3. SSO ๊ตฌํ˜„ ์‹œ ์–ด๋–ค ์žฅ์ ์ด ์žˆ๋‚˜์š”?
A3. ์‚ฌ์šฉ์ž๊ฐ€ ํ•œ ๋ฒˆ ๋กœ๊ทธ์ธํ•˜๋ฉด ์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋™์ผํ•œ ์ธ์ฆ ์ •๋ณด ๋กœ ์ž๋™ ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Q4. Deep Link ์„ค์ • ์‹œ ์ฃผ์˜ํ•  ์ ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?
A4. ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI๊ฐ€ ์ •ํ™•ํžˆ ์ผ์น˜ ํ•ด์•ผ ํ•˜๋ฉฐ, ์•…์„ฑ ๋งํฌ์— ์ฃผ์˜ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Q5. ๋ชจ๋ฐ”์ผ OAuth์˜ ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•˜๋‚˜์š”?
A5. HTTPS ์‚ฌ์šฉ, PKCE ์ ์šฉ, Refresh Token Rotation ๋“ฑ์„ ํ†ตํ•ด ๋ณด์•ˆ์„ ๊ฐ•ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 


๋Œ“๊ธ€