티스토리 뷰
프로젝트 진행 중 카카오 로그인 보안 강화를 위해 nonce를 추가하기로 결정하였다.
Kakao Developers
카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.
developers.kakao.com
카카오 문서에서도 OpenID Connect 사용 시 ID 토큰 재생 공격을 방지하기 위해 nonce 파라미터 사용을 권장한다고 한다.
이렇게 봤을 때는 왜 사용하는지 이해가 가지 않았다.... 그래서 관련 내용을 더 찾아보았다!!
OpenID Connect가 무엇인가?
카카오 로그인 Open ID Connect 지원한다는 공지에서 관련 내용을 확인할 수 있었다.
[공지] 카카오 로그인 OpenID Connect 지원 / [Notice] Support of OpenID Connect
안녕하세요, 카카오입니다. 카카오 로그인에 인증(Authentication)을 위한 OAuth 2.0 확장 프로토콜인 OpenID Connect를 지원합니다. 이에 따라 각 서비스에서 카카오 로그인을 통해 보다 강화된 사용자 인
devtalk.kakao.com
일단 OpenID Connect를 사용하기 위해서는 [내 애플리케이션] > [카카오 로그인] > [OpenID Connect 활성화] 설정을 [ON]으로 변경해야 한다.
아래와 같이 간략하게 설명을 해놓은 걸 확인해보자.
정리하자면, 사용자가 여러 서비스에서 단일 로그인으로 접근할 수 있어 사용자 경험을 향상시키고 보안성을 강화할 수 있다고 한다.
- OpenID Connect(OIDC) 사용 시: 액세스 토큰과 리프레시 토큰 외에도 ID 토큰을 추가로 발급받을 수 있다.
- ID 토큰 사용: 사용자 인증 기능을 강화할 수 있다.
- 단일 로그인 기능: OIDC를 사용하면 사용자가 여러 서비스에서 단일 로그인으로 접근할 수 있다.
이렇게 해도 Open ID가 무엇인지 왜 단일 로그인을 제공해 주는지 이해가 가지 않는다,,, 그래서 OpenID를 이해하기 위해 찾아보다 SSO 인증 방식을 알게 되었다.
SSO란 무엇인가?
사용자의 인증 기능을 애플리케이션에서 독자적으로 제공하기에는 구현하기 까다롭고, 보안에 매우 민감하다.
그래서 나온 게 애플리케이션이 해야 하는 사용자 인증 책임을 소셜(네이버, 카카오, 구글)과 같은 믿을 수 있는 공급체에 인증 책임을 위임하는 방법이다.
이게 바로 SSO 인증 방법이고, SSO는 한 번의 로그인으로 여러 애플리케이션이나 시스템에 접근할 수 있게 한다.
즉, 사용자가 하나의 시스템에서 인증하면 다른 시스템에서도 인증 정보를 확인하고, 이미 인증된 경우 추가 로그인 없이 접근할 수 있다.
인증 정보가 없을 경우 통합 인증을 통해 로그인 절차가 진행된다.
SSO 방식을 사용하면, 신뢰할 수 있는 인증 서비스 공급자가 제공하는 인증 서비스를 이용함으로써 보안성이 향상되고, 애플리케이션마다 개별로 계정을 만들 필요 없이 하나의 로그인으로 모든 서비스에 접근할 수 있어 편리하다.
🟡 SSO 구현을 위한 프로토콜
SSO 구현을 위한 프로토콜은 세 가지로 아래와 같다.
OIDC가 바로 SSO 구현을 위한 프로토콜이었다,,,, 그래서 단일 로그인을 제공해 주는 거였구나 ㅎㅎ
1. OAuth 2.0:
- 목적: 자원 소유자가 제3자 애플리케이션에 자신의 자원에 대한 접근을 허가하기 위한 인증 프로토콜.
- 주요 기능: 접근 토큰을 사용해 클라이언트가 리소스 서버에 안전하게 접근.
2. SAML (Security Assertion Markup Language):
- 목적: XML 기반의 인증 및 권한 부여 데이터를 교환하는 표준으로, 여러 도메인 간의 SSO를 가능하게 함.
- 주요 기능: 인증 정보를 제공해 사용자가 한 번의 로그인으로 여러 서비스에 접근 가능.
3. OIDC (OpenID Connect):
- 목적: OAuth 2.0을 기반으로 사용자 인증을 처리하는 프로토콜.
- 주요 기능: ID 토큰을 사용해 사용자 신원을 확인하고 프로필 정보를 제공, 웹 및 모바일 애플리케이션에서 통합 로그인 경험 제공.
그럼 OIDC와 OAuth의 차이는 무엇일까..?
OAuth와 OIDC의 차이
가장 중요한 차이는 둘의 목적에 있다.
- OAuth: 권한허가(인가)
- OIDC: 사용자 인증 및 사용자 정보 제공(인증)
OAuth는 애플리케이션을 승인한 사용자에 대한 정보를 제공하는 것이 아니라 액세스 토큰을 사용하여 권한을 제공한다.
예를 들어 액세스 토큰은 호텔 키 카드와 같다. 호텔 카드키만으로 키 카드 소유자가 어떤 객실에 출입할 권한이 있는지 알 수 있지만, 카드키 소유자에 대한 신원 정보는 알 수 없다.
즉, 핵심 목적은 인가이다.
OIDC을 사용하면 ID 토큰을 받을 수 있는데 ID 토큰에는 소유자에 대한 신원 정보가 담겨있다.
액세스 토큰이 호텔 키 카드라면, ID 토큰은 주민등록증이라고 할 수 있다. 주민등록증으로 신원 확인은 할 수 있지만 호텔 객실에는 출입할 수 없다.
즉, 핵심 목적은 인증이다.
🤔 OAuth만을 사용해서 인증해도 되지 않나?
OAuth 2.0은 사용자의 리소스를 리소스 서버로부터 가져오기 위해 사용한다. 이 과정에서 인증 서버를 통해 사용자를 인증하는 과정을 거친다. 사용자의 신원 정보가 필요하면, OAuth 2.0 인가 과정을 거쳐 발급된 액세스 토큰을 가지고 사용자 정보 리소스에 접근하면 된다.
OIDC 없이 사용자 프로필 정보를 리소스 서버에서 가져올 수 있다. 하지만, OIDC 없이 OAuth 2.0 단독으로 사용자 리소스를 가지고 오기 위해서는 OIDC를 사용할 때의 2배의 통신을 해야 한다.
1. OAuth 2.0 단독 사용 => 액세스 토큰을 발급받은 다음 이 토큰을 사용해 사용자 리소스를 다시 요청하여 받아와야 한다.
2. OIDC 사용 -> 액세스 토큰과 함께 전달받은 ID 토큰을 복호화하여 사용자 정보 가져온다.
즉, OAuth 2.0 단독으로 사용하여 사용자 정보 조회할 때 발생한 통신이 1억 회라면, OIDC를 사용하면 5천만 회로 줄어든다.
✏️ OIDC 사용하는 이유
SSO 인증 방식 덕분에 사용자가 여러 서비스에서 단일 로그인을 할 수 있다. 또한, OAuth를 단독으로 사용할 때보다 사용자 인증 기능을 강화하고, 통신을 줄일 수 있다. OIDC는 OAuth 2.0을 확장하여 ID 토큰을 통해 사용자 신원을 확인하고 프로필 정보를 제공하여 보다 통합적이고 효율적인 인증을 가능하게 한다.
드디어 OIDC를 왜 사용하고 어떤 이점이 있는지 이해할 수 있게 되었다 ㅎㅎ
그럼 ID 토큰에 nonce 파라미터는 왜 넣어야할까??
ID 토큰에 nonce는 왜 필요한가
ID 토큰에 nonce 파라미터를 포함하여 ID 토큰 재생 공격을 방지할 수 있다고 했다.
🤔 왜 그럴까?
ID 토큰에 nonce 파라미터를 넣어 보내 통신하는 과정을 알아보자.
1. 클라이언트가 인증 요청을 보낼 때 nonce 값을 포함시켜 서버에 요청한다.
2. 서버는 인증 후 ID 토큰에 nonce 포함하여 반환한다.
3. 클라이언트는 ID 토큰을 수신하고, 해당 토큰 내의 nonce 값이 원래 요청한 값과 일치하는지 확인한다.
이 과정에서 동일한 ID 토큰을 재사용하지 못하게 해서 각 요청이 유일함을 보장할 수 있도록 한다.
아 그래서 ID 토큰 재생 공격을 방지할 수 있다고 한 거 구나,,
드디어 ID 토큰에 왜 nonce 파라미터를 추가해야 하는지 이해할 수 있었다 ~~
이제 nonce를 추가해보자!!
kakao ID 토큰에 nonce 추가
🟡 nonce 만들기
아래와 같이 nonce 문자열을 만들고 입력된 문자열의 SHA-256 해시를 계산하는 CrytoHelper를 열거형으로 선언해 준다.
SHA-256 해시를 계산하는 이유는 데이터 무결성과 보안을 강화할 수 있어서 많이 사용한다고 한다.
enum CryptoHelper {
//임의의 nonce 문자열을 생성
static func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
for random in randoms {
if remainingLength == 0 {
continue
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
//입력된 문자열의 SHA-256 해시를 계산
static func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
String(format: "%02x", $0)
}.joined()
return hashString
}
}
ramdomNonceString()으로 nonce를 생성한 후 그 randomNonce값을 sha256() 함수에 넣어 해시를 계산해 준다.
let randomNonce = CryptoHelper.randomNonceString()
let nonce = CryptoHelper.sha256(randomNonce)
🟡 nonce 파라미터 추가
kakao 문서에 나와있는 카카오 로그인 코드는 아래와 같다.
근데 nonce를 대체 어디에 추가해야 하는지 찾을 수 없었다...
UserApi.shared.loginWithKakaoAccount {(oauthToken, error) in
if let error = error {
print(error)
}
else {
print("loginWithKakaoAccount() success.")
//do something
_ = oauthToken
}
}
위의 코드를 Xcode에 넣고 loginWithKakaoAccount를 [cmd + click]을 하면 어떻게 nonce 파라미터를 넣어야 하는지 친절하게 알려준다 ㅎㅎ
이걸 몰라서 얼마나 헤매었는지,,,~~~
아래와 같이 nonce 파라미터에 위에서 계산한 nonce 값을 넣어주면 끝난다.
UserApi.shared.loginWithKakaoAccount(nonce: nonce) { oauthToken, error in
if let error = error {
print(error)
} else {
print("loginWithKakaoAccount() success.")
}
}
참고
https://velog.io/@choidongkuen/OIDC%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
'스위프트 > SwiftUI' 카테고리의 다른 글
[SwiftUI] - 디바이스 모델명, OS, 앱 버전 출력 (0) | 2024.06.02 |
---|---|
[SwiftUI] - Unit Testing (0) | 2024.05.13 |
[SwiftUI] - ViewModel에서 @State 사용시 문제점 (0) | 2024.04.18 |
[SwiftUI] - NavigationLink Action 추가 (0) | 2024.04.07 |
[SwiftUI] - Custom Font 적용 (0) | 2024.03.19 |