
토큰을 검색하면 동전모양으로, 무언가(서비스 등)와 교환할 수 있는 화폐같은게 나온다.
카지노에서는 칩에 해당하는 용어…이지만 지금 알고싶은 정보는 이게 아니다.
프로그래밍 언어에서 토큰은 컴파일러가 소스코드를 분석할 때 사용되는 가장 작은 의미 단위라고 한다.
int x = 10;
예를 들어보면 int, x, =, 10, ; 모두 하나의 토큰이라고 하는것이다.
그렇지만, 내가 지금 궁금한 것은 네트워킹에서 인증 및 보안과 관련된 토큰의 의미이다.
보통 IT에서 토큰이라고 하면 웹 등에서 개인을 확인하기 위한 수단의 하나로 시스템 간 통신에서 사용자 인증 정보를 포함한 작은 데이터 조각을 말한다고 한다.
토큰의 의미가 무언가를 식별하기 위한 용도로 쓰인다는 것은 분야가 무엇이 되든 같은 의미인거 같다.
가볍게 봐도 되며 JWT와 OAuth토큰은 조금 자세히 보자.
토큰은 프로그래밍 언어의 문법 요소로 분류된다.
아래와 같이 분류되지만 지금 궁금하진 않다.
int, if, return 등 언어에서 예약된 단어+, -, *, /, =, == 등 연산을 수행하는 기호;, {, }, () 등 코드를 구분하는 기호(1) 인증 토큰 종류
(2) CSRF 토큰
(3) 비밀 키 또는 API 토큰
여기서의 종류는 배척개념이 아니다.
즉, JWT 토큰이라고 해서 OAuth토큰이 아닌것은 아니고 Access랑 Refresh를 사용했다고 해서 OAuth 토큰인 것은 아닌 것이다.
Access와 Refresh 토큰은 권한 부여를 구현하는 일반적인 패턴으로도 많이 사용된다고 한다.
암호화폐 토큰 종류
(a) 기능에 따른 분류
(b) 기술적 분류
(1) 게임 및 서비스
(2) 물리적 토큰
JWT(JSON Web Token)은 JSON 포맷을 기반으로 한 압축된 토큰으로, 주로 권한 부여 및 정보 교환 목적으로 사용된다고 한다.
JWT는 .(점)으로 구분된 세 가지 파트로 구성된다.
{
"alg": "HS256",
"typ": "JWT"
}{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
}iss (issuer): 토큰 발급자sub (subject): 토큰의 주체aud (audience): 토큰의 대상exp (expiration): 토큰 만료 시간iat (issued at): 토큰 발급 시간HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)JWT는 다음과 같은 형태를 띄어서 Header, Payload, Signature로 구분된다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQSflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cexp)을 통해 토큰의 유효성을 검증할 수 있다.Access Token: 주로 짧은 기간 동안 리소스 서버에 접근하기 위해 사용Refresh Token: Access Token이 만료된 후 새 Access Token을 발급받기 위해 사용Authorization: Bearer <Access Token>if 검증이 성공:else Access Token 만료:if 검증이 성공:(reissue) ```
POST /auth/refresh-token
Authorization: Bearer <Refresh Token>
```
- `else` **Refresh Token 만료:
새로 로그인**하여 Access토큰과 Refresh토큰을 발급받는다.POST /auth/login
Content-Type: application/json
{
"username": "john",
"password": "mypassword"
}
응답:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"refresh_token": "d2hhdCBhIG5pY2UgZGF5IQ=="
}
GET /api/user-profile
Authorization: Bearer <Access Token>
서버 동작:
401 Unauthorized 응답을 반환HTTP/1.1 401 Unauthorized
Content-Type: application/json
{
"error": "Access token expired"
}
https://developers.google.com/identity/protocols/oauth2?hl=ko
OAuth(Open Authorization)는 인터넷 사용자 권한 위임 프로토콜로
하나의 애플리케이션이 다른 애플리케이션의 리소스에 사용자의 비밀번호 없이 안전하게 접근할 수 있도록 권한을 부여하는 방식을 제공한다고 한다.
사용자의 비밀번호같은 자격증명을 노출하지 않고
제3자가 특정 리소스에 제한적인 접근 권한을 얻도록 허용하는 것이다.
Google 계정을 사용해 다른 서비스에 로그인하는 소셜로그인, Kakao 계정을 사용해 친구 목록 가져오기 등 이러한 예시를 들 수 있다.
OAuth는 클라이언트, 리소스 소유자, 리소스 서버, 인증 서버 이렇게 4개의 상호작용으로 동작한다.
리소스 소유자:
리소스를 소유한 사용자 (예: Kakao 계정의 사용자)
클라이언트:
리소스에 접근하려는 애플리케이션 (예: 우리가 만드는 팝콘 앱)
리소스 서버:
보호된 리소스를 호스팅하는 서버 (예: Kakao API)
인증 서버:
리소스 소유자의 인증을 처리하고 권한 부여 토큰을 발급하는 서버 (예: Kakao의 OAuth 서버)
email, profile, read:useriOS에서 데이터 저장소에는 UserDefaults와 Keychain이 있다.
각각 일반 데이터와 민감한 데이터를 저장하는 용도로 사용되며 목적과 보안을 고려해서 사용하면 된다.
멋도모르고 토큰을 UserDefaults에 저장하는 코드를 작성했는데
토큰은 민감한 정보이므로 Keychain에 저장하는 코드로 바꾸어야 한다.
앱 내에서 사용자 설정이나 상태 정보 등의 간단한 데이터를 영구적으로 저장할 때 사용한다.
Library/Preferences)에 저장된다고 한다.// 저장
UserDefaults.standard.set("John Doe", forKey: "username")
// 읽기
let username = UserDefaults.standard.string(forKey: "username")
iOS와 macOS에 내장된 보안 프레임워크를 통해 구현되며
민감한 데이터(비밀번호, 인증 토큰 등)를 안전하게 저장하기 위한 보안 저장소이다.
import Security
func saveToKeychain(service: String, account: String, data: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: data
]
SecItemAdd(query as CFDictionary, nil)
}
func getFromKeychain(service: String, account: String) -> Data? {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var dataTypeRef: AnyObject?
if SecItemCopyMatching(query as CFDictionary, &dataTypeRef) == errSecSuccess {
return dataTypeRef as? Data
}
return nil
}




SceneDelegate:Access, Refresh토큰을 저장하며 각각의 만료일도 저장하게끔 구조체로 정의하였다.handleTokenExpiration 메서드를 정의하였다.loginButtonTapped(): 로그인 매니저 객체를 통해 로그인을하며 실패하면 에러, 성공하면 토큰을 저장한다.dateFormatter를 정의했다.reissue메서드는 Access토큰은 만료이고 Refresh토큰은 만료가 아닐 때 Access토큰을 재발급 받는 메서드이다.
reissue메서드가 TokenExpireResolver 의 메서드에 위치해야할지, TokenRepository 의 메서드에 위치해야할지 고민이 있었다.
결론적으로는 아래의 이유에 따라 TokenRepository 의 메서드에 위치하게 되었다.
TokenRepository는 Keychain 과의 상호작용을 담당하고 있으며 토큰과 관련된 저장, 조회, 삭제도 포함하고 있다.
장점
책임 분리:
TokenRepository는 토큰 데이터를 관리하는 역할을 맡고 있으며 토큰 갱신도 데이터 관리에 속한다고 간주할 수 있다.
재사용성:
TokenRepository를 통해 다양한 곳에서 토큰 갱신 로직을 호출할 수 있다.
코드 구조:
토큰의 저장, 삭제, 갱신 로직이 한 곳에 집중되어 있어 코드 탐색이 쉬워진다.
TokenExpireResolver는 주로 토큰 만료 상태를 확인하고 처리하는 데 초점이 맞춰져있다.
만약 reissueAccessToken 로직이 여기 있다면 만료된 토큰의 처리 로직과 갱신 로직이 한 곳에 모일 수 있다는 장점이 있다.
장점
토큰 만료 처리 집중:
만료된 토큰과 관련된 모든 작업(갱신 포함)이 TokenExpireResolver에서 처리된다.
로직 분리:
TokenRepository는 데이터 관리에 집중하고 TokenExpireResolver는 만료 및 갱신 로직을 관리하도록 역할을 분리할 수 있다.
내가 생각한 가장 깔끔한 설계는 다음과 같다
TokenRepository:
토큰의 저장, 조회, 삭제 및 갱신 요청(서버 통신 포함) 역할을 담당
TokenExpireResolver:
토큰 만료 상태를 확인하고 필요한 경우 TokenRepository.reissueAccessToken을 호출