본문 바로가기

iOS

iOS 계층구조-1

728x90
반응형
SMALL

이 글은 iOS 초보자가 쓰는 글이며, 정확하지 않습니다! 혼공하고 흔적을 남기기 위한 수단임을 유의해주세요ㅠㅠㅠ

 

  1. Cocoa Touch 계층: iOS 앱을 개발하기 위한 핵심 프레임워크입니다. UIKit, Foundation, Core Animation, Core Data, Core Location 등의 프레임워크가 포함되어 있습니다.
  2. Media 계층: 오디오, 비디오, 이미지 처리와 관련된 프레임워크를 제공합니다. AV Foundation, Core Audio, Core Video 등이 포함됩니다.
  3. Core Services 계층: 앱의 핵심 기능을 제공하는 프레임워크입니다. 예를 들어, iCloud, Core Bluetooth, Core Motion 등이 포함됩니다.
  4. Core OS 계층: iOS의 핵심적인 서비스를 담당하는 하부 레벨의 프레임워크입니다. 예를 들어, 시스템 보안, 메모리 관리, 네트워킹 등이 포함됩니다.

Core OS 계층

Core OS 계층은 iOS의 핵심적인 서비스를 담당하는 하부 레벨의 프레임워크입니다. 이 계층에는 iOS 운영체제의 핵심 기능들이 포함되어 있습니다.

  1. 시스템 보안: iOS는 안전한 운영체제로 알려져 있으며, Core OS 계층에는 암호화, 인증, 적절한 권한 관리 등의 보안 기능이 포함됩니다.
  2. 메모리 관리: Core OS 계층은 iOS 운영체제의 메모리 관리를 담당합니다. 이를 통해 iOS는 안정적이고 빠른 성능을 제공할 수 있습니다.
  3. 네트워킹: Core OS 계층에는 iOS의 네트워킹 기능이 포함되어 있습니다. 이를 통해 iOS 앱은 인터넷에 접속하여 다양한 기능을 제공할 수 있습니다.
  4. 파일 시스템: iOS는 파일 시스템을 다루기 위해 Core OS 계층을 사용합니다. 이를 통해 iOS 앱은 파일을 저장하고 읽어올 수 있습니다.

Core OS 계층은 iOS의 안정성, 보안성, 성능 등에 큰 영향을 미치는 중요한 계층 중 하나입니다.

 

Core OS 계층의 시스템 보안에는 다양한 기능들이 있습니다. 예를 들어, 암호화, 인증, 권한 관리 등이 있습니다. 아래는 이러한 기능들에 대한 예제입니다.

  1. 암호화: Core OS 계층은 iOS 운영체제의 데이터 보안을 담당합니다. 예를 들어, iOS는 파일 암호화를 지원하여 사용자의 개인 정보를 안전하게 보호할 수 있습니다. 또한 iOS에서는 SSL/TLS를 사용하여 인터넷 통신을 암호화할 수 있습니다.
  2. 인증: Core OS 계층은 iOS 운영체제의 인증을 담당합니다. 예를 들어, iOS에서는 Touch ID나 Face ID 등의 바이오메트릭 인증을 제공하여 사용자 인증을 강화합니다. 또한 iOS에서는 OAuth, OpenID Connect 등의 인증 프로토콜을 지원하여 다양한 인증 방식을 사용할 수 있습니다.
  3. 권한 관리: Core OS 계층은 iOS 운영체제의 권한 관리를 담당합니다. 예를 들어, iOS에서는 사용자의 위치 정보나 연락처 등의 개인 정보에 접근하기 위해서는 앱에서 권한을 요청해야 합니다. 이러한 권한 요청은 Core OS 계층에서 처리되며, 사용자가 승인하기 전까지는 앱에서 해당 정보에 접근할 수 없습니다.

이러한 보안 기능들은 iOS 앱에서 매우 중요합니다. Core OS 계층에서 제공하는 보안 기능들을 적극 활용하여 iOS 앱의 보안성을 높이는 것이 필수적입니다.

 

*** SSL/TLS, Secure Sockets Layer/Transport Layer Security *** 

iOS에서 SSL/TLS는 인터넷 통신 보안을 위한 프로토콜입니다. iOS에서는 SSL/TLS를 지원하여, 앱에서 안전한 인터넷 통신을 할 수 있습니다.

SSL/TLS는 서버와 클라이언트 간의 통신을 암호화하여 제3자가 중간에서 정보를 가로채거나 변경하는 것을 방지합니다. iOS에서는 이러한 SSL/TLS 기능을 제공하기 위해 Secure Transport 프레임워크를 사용합니다. Secure Transport 프레임워크는 TLS/SSL 프로토콜을 지원하며, iOS에서 앱에서 이를 사용할 수 있도록 API를 제공합니다.

iOS에서 SSL/TLS를 사용하기 위해서는 보안 인증서를 사용해야 합니다. iOS에서는 SSL/TLS 통신을 위한 보안 인증서를 자동으로 설치하거나, 사용자가 수동으로 설치할 수 있도록 지원합니다. 또한, iOS에서는 HTTPS를 사용하여 앱과 서버 간의 통신을 암호화할 수 있습니다.

앱에서 SSL/TLS를 사용할 때는, 보안 인증서가 유효한지 확인해야 합니다. iOS에서는 이를 위해 URLSessionDelegate를 제공하며, URLSessionDelegate에서 서버의 보안 인증서를 확인하여 유효한지 검사할 수 있습니다.

SSL/TLS는 iOS에서 앱의 보안성을 높이는 중요한 기능 중 하나입니다. iOS 앱에서 SSL/TLS를 적극 활용하여, 인터넷 통신 보안을 강화하는 것이 필수적입니다.

 

URLSessionDelegate에서 서버의 보안 인증서를 확인하여 유효한지 검사하는 코드

import UIKit

class ViewController: UIViewController, URLSessionDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        let url = URL(string: "https://expired.badssl.com/")
        let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
        let task = session.dataTask(with: url!) { (data, response, error) in
        }
        task.resume()
    }

    func printCertificateValidity(_ certificate: SecCertificate, _ trust: SecTrust, _ result: SecTrustResultType) {
        let summary = SecCertificateCopySubjectSummary(certificate) as! String
        print("Certificate for '\(summary)' is ")
        if result == .unspecified || result == .proceed {
            print("valid.")
        } else {
            print("invalid.")
        }
    }

    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
            if let serverTrust = challenge.protectionSpace.serverTrust {
                let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)!
                var trust: SecTrust?
                let policy = SecPolicyCreateSSL(true, challenge.protectionSpace.host as CFString)
                let status = SecTrustCreateWithCertificates(certificate, policy, &trust)
                if status == errSecSuccess {
                    var result: SecTrustResultType = .invalid
                    SecTrustEvaluate(trust!, &result)
                    printCertificateValidity(certificate, trust!, result) // 수정된 부분
                    if result == .unspecified || result == .proceed {
                        let credential = URLCredential(trust: serverTrust)
                        completionHandler(.useCredential, credential)
                        return
                    }
                }
            }
        }
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}

서버의 보안 인증서가 유효하지 않은 예제 사이트 (https://expired.badssl.com/)를 사용해서 테스트하면? 인증서가 유효하지 않다고 invlaid가 뜹니다.

반면 그냥 네이버의 경우는? 정상적으로 valid가 출력됩니다. :)

*** OAuth, OpenID Connect ***

OAuth (Open Authorization)는 서드파티 애플리케이션에서 인증되지 않은 사용자가, 인증된 사용자의 리소스에 대한 액세스 권한을 받을 수 있도록 인증 및 권한 부여 프레임워크를 제공합니다. 즉, OAuth는 리소스 소유자가 자신의 리소스에 대한 액세스 권한을 위임할 수 있도록 하는 인증 방식입니다.

 

OpenID Connect (OIDC)는 OAuth 2.0 프로토콜 위에 구축된 인증 프로토콜입니다. OIDC는 사용자 정보를 안전하게 전달하고 인증된 사용자를 표현하기 위한 JSON 기반 ID 토큰을 제공합니다. OIDC는 OAuth 2.0에서 인증 부분을 보완하여 보다 안전한 사용자 인증 및 정보 교환을 가능하게 합니다.

 

 

OAuth를 Swift로 구현하는 방법은 다음과 같습니다.

1. OAuth 2.0 스펙을 이해하고, 인증 및 토큰 요청을 위한 엔드포인트 URL을 확인합니다.

2. URLSession 객체를 사용하여 인증 및 토큰 요청을 위한 HTTP 요청을 생성합니다. 이때, 요청 헤더에는 Authorization 헤더에 인증 정보가 포함되어야 합니다.

3. HTTP 요청을 서버로 보내고, 서버로부터 응답을 받습니다. 응답 데이터를 JSONSerialization을 사용하여 파싱합니다.

4. 파싱된 응답 데이터에서 엑세스 토큰을 추출하고, 이를 안전하게 저장합니다.

5. 앱에서 엑세스 토큰을 사용하여 OAuth 보호 리소스에 대한 HTTP 요청을 보냅니다. 이때, 요청 헤더에는 Authorization 헤더에 엑세스 토큰이 포함되어야 합니다.

 

OAuth 2.0 인증 및 토큰 요청 예제 코드입니다. 무슨 말인지 전혀 모르겠어서. 한 줄씩 보는 시간을 가져봅시다.

import UIKit
import Foundation


class ViewController: UIViewController {
    
    let oauthClient = OAuthClient()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        

        oauthClient.authenticate()
    }
    
    func handleOAuthRedirectUrl(_ url: URL) {
        oauthClient.handleRedirectUrl(url)
    }
}

// OAuthClient 클래스 선언
class OAuthClient {
    
    // 클라이언트 ID 저장하는 상수
    let clientId = "your-client-id"
    
    // 클라이언트 secret 저장하는 상수
    let clientSecret = "your-client-secret"
    
    // 인증 엔드포인트 URL을 저장하는 상수
    let authorizationEndpoint = "https://example.com/oauth2/authorize"
    
    // 토큰 엔드포인트 URL을 저장하는 상수
    let tokenEndpoint = "https://example.com/oauth2/token"
    
    // 리다이렉트 URI를 저장하는 상수
    let redirectUri = "https://example.com/redirect"
    
    // 상태 코드를 저장하는 변수
    var state = ""
    
    // 코드 검증자를 저장하는 변수
    var codeVerifier = ""

    
    func generateState(withLength length: Int) -> String {
        // 사용할 문자열을 정의합니다.
        let allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        // 문자열에서 사용할 수 있는 문자의 수를 세어 저장합니다.
        let allowedCharactersCount = UInt32(allowedCharacters.count)
        // 무작위 문자열을 담을 변수를 미리 초기화합니다.
        var randomString = ""
        
        // 주어진 길이만큼 무작위 문자열을 생성합니다.
        for _ in 0 ..< length {
            // 무작위로 문자를 선택하기 위해 난수를 생성합니다.
            let randomNumber = Int(arc4random_uniform(allowedCharactersCount))
            // 선택된 문자를 가져와 새 문자열에 추가합니다.
            let randomIndex = allowedCharacters.index(allowedCharacters.startIndex, offsetBy: randomNumber)
            let newCharacter = allowedCharacters[randomIndex]
            randomString += String(newCharacter)
        }
        
        return randomString
    }

    
    func generateCodeVerifier(withLength length: Int) -> String {
        // 허용 가능한 문자열 목록
        let allowedCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
        // 허용 가능한 문자열 수
        let allowedCharactersCount = UInt32(allowedCharacters.count)
        // 무작위 문자열을 저장할 변수 초기화
        var randomString = ""
        
        // 원하는 길이만큼 무작위 문자열 생성
        for _ in 0 ..< length {
            // 허용 가능한 문자열에서 무작위 숫자 생성
            let randomNumber = Int(arc4random_uniform(allowedCharactersCount))
            // 무작위 숫자를 이용하여 새로운 문자열 인덱스 생성
            let randomIndex = allowedCharacters.index(allowedCharacters.startIndex, offsetBy: randomNumber)
            // 새로운 문자열 생성
            let newCharacter = allowedCharacters[randomIndex]
            // 무작위 문자열에 새로운 문자열 추가
            randomString += String(newCharacter)
        }
        
        // 생성된 무작위 문자열 반환
        return randomString
    }

    func generateCodeChallenge(from verifier: String) -> String {
        // 문자열 verifier를 utf8 인코딩으로 변환하여 data 상수에 할당한다.
        let data = verifier.data(using: .utf8)!
        // base64 인코딩된 문자열 encodedVerifier를 생성한다.
        let encodedVerifier = data.base64EncodedString().trimmingCharacters(in: CharacterSet(charactersIn: "="))
        // 인코딩된 문자열에서 + 기호를 -로, / 기호를 _로 대체하여 encodedVerifierChallenge 상수에 할당한다.
        let encodedVerifierChallenge = encodedVerifier.replacingOccurrences(of: "+", with: "-").replacingOccurrences(of: "/", with: "_")
        // 생성된 인코딩된 문자열을 반환한다.
        return encodedVerifierChallenge
    }

    
    func authenticate() {
        state = generateState(withLength: 32) // 랜덤한 32자리의 state 생성
        codeVerifier = generateCodeVerifier(withLength: 128) // 랜덤한 128자리의 code verifier 생성
        let codeChallenge = generateCodeChallenge(from: codeVerifier) // code verifier를 사용하여 code challenge 생성
        
        // Authorization URL 문자열 생성
        let authUrlString = "\(authorizationEndpoint)?response_type=code&client_id=\(clientId)&redirect_uri=\(redirectUri)&state=\(state)&code_challenge=\(codeChallenge)&code_challenge_method=S256"
        
        // Authorization URL 생성
        guard let authUrl = URL(string: authUrlString) else {
            print("Failed to create URL")
            return
        }
        
        // Authorization URL을 브라우저에서 열기
        UIApplication.shared.open(authUrl, options: [:], completionHandler: nil)
    }

    
    func handleRedirectUrl(_ url: URL) {
        // URL의 컴포넌트를 가져와서 queryItems를 가져옴
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
              let queryItems = components.queryItems else {
            print("Invalid redirect URL")
            return
        }
        
        // queryItems를 dictionary 형태로 파싱하여 parameters에 저장
        var parameters = [String: String]()
        for queryItem in queryItems {
            parameters[queryItem.name] = queryItem.value
        }
        
        // state parameter 검증
        guard let stateParameter = parameters["state"], stateParameter == state else {
            print("Invalid state parameter")
            return
        }
        
        // code parameter 검증
        guard let codeParameter = parameters["code"] else {
            print("Missing code parameter")
            return
        }
        
        // code parameter를 이용하여 access token 요청
        exchangeCodeForToken(codeParameter)
    }

    
    func exchangeCodeForToken(_ code: String) {
        let grantType = "authorization_code" // grant 타입 지정
        let codeVerifierParam = "code_verifier=\(codeVerifier)" // code_verifier 값을 포함한 문자열을 변수에 저장
        
        // tokenEndpoint에서 URL 생성
        guard let tokenUrl = URL(string: tokenEndpoint) else {
            print("Failed to create URL")
            return
        }
        
        // URLRequest 생성 후 HTTP 메서드를 POST로 설정
        var request = URLRequest(url: tokenUrl)
        request.httpMethod = "POST"
        
        // 파라미터를 Dictionary 타입으로 저장
        let params = [
            "grant_type": grantType,
            "code": code,
            "redirect_uri": redirectUri,
            "client_id": clientId,
            "client_secret": clientSecret,
            "code_verifier": codeVerifier
        ]
        
        // 파라미터를 "&"로 구분하여 문자열로 변환하고 Data 타입으로 저장
        let bodyString = params.map { "\($0)=\($1)" }.joined(separator: "&")
        let bodyData = bodyString.data(using: .utf8)
        
        // HTTP body에 저장
        request.httpBody = bodyData
        request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        
        // URLSession을 사용하여 데이터를 가져오기 위한 task 생성
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                print("Error: \(error.localizedDescription)")
                return
            }
            
            // 응답 코드가 200~299 사이인지 확인
            guard let data = data,
                  let response = response as? HTTPURLResponse,
                  (200..<300).contains(response.statusCode) else {
                print("Invalid response or response status code")
                return
            }
            
            // TokenResponse 타입으로 파싱
            guard let tokenResponse = try? JSONDecoder().decode(TokenResponse.self, from: data) else {
                print("Failed to parse token response")
                return
            }
            
            // TokenResponse에서 Access Token을 추출하여 출력
            let accessToken = tokenResponse.accessToken
            
            print("Access token: \(accessToken)")
        }
        
        // task 실행
        task.resume()
    }

}

//Codable 프로토콜을 준수하는 TokenResponse 구조체 정의
struct TokenResponse: Codable {
    let accessToken: String
    let tokenType: String
    let expiresIn: Int
    
    private enum CodingKeys: String, CodingKey {
        case accessToken = "access_token"
        case tokenType = "token_type"
        case expiresIn = "expires_in"
    }
}

 

728x90
반응형
LIST

'iOS' 카테고리의 다른 글

iOS 계층구조-3  (0) 2023.04.15
iOS 계층구조-2  (0) 2023.04.15
2023 WWDC Student Challenge 도전.  (0) 2023.04.14
시리얼(Serial) 큐, 컨커런트(Concurrent) 큐  (0) 2023.04.13
싱글톤 패턴, 옵저버 패턴, 코디네이터 패턴  (0) 2023.04.13