[iOS] 네이버 SMS API 사용하기 (1/2)

Jehyeon Lee·2023년 11월 22일
0

네이버 SMS API

목록 보기
1/3
post-thumbnail

네이버 SMS API 사용하기!

문자인증을 통한 로그인 회원가입 기능을 구현하기위해 네이버 SMS API를 사용해서 구현하였습니다.

Class 만들기

final class SMSAuthService {
enum SMSRequestType: String {
        case sms
        case lms
        case mms
        
        var name: String {
            return self.rawValue.uppercased()
        }
    }
    
    struct SMSRequest: Codable {
        let type: String // (SMS | LMS | MMS)
        /// 발신번호
        let from: String
        /// 기본 메시지 제목
        let subject: String?
        /// 기본 메시지 내용
        let content: String
        /// 메시지 정보
        let messages: [Message]
        let files: [File]
    }
    
    struct Message: Codable {
        /// 수신번호
        let to: String
    }
    
    struct File: Codable {
        let fileId: String?
    }
    
}

코드 설명

  • 내부 사정이 변경되어 인증 방식이 달라질 것을 대비하여 열거형 타입으로 두어 SMSRequestType을 SMS, LMS, MMS 등 여러개의 케이스로 두었습니다.
  • SMSRequest은 문자인증을 요청 할 때 네이버 SMS API가 요구하는 JSON BODY 부분에 대한 구조체입니다.

개인정보 보호하면서 작업하기

필요한 정보들을 Key 값을 Plist 파일로 따로 뺴서 Git에 팀원의 전화번호가 올라가는 불상사를 막았습니다. 그 후 번들파일을 extension 하여 메서드를 만들어서 사용하였습니다.

extension Bundle {
var senderPhoneNumber: String {
        guard let file = self.path(forResource: "APIKeys", ofType: "plist") else { return "" }
        
        guard let resource = NSDictionary(contentsOfFile: file) else { return "" }
        
        guard let key = resource["SENDERPHONENUMBER"] as? String else {
            fatalError("KEY를 찾을수없음")
        }
        return key
    }
    var accessKey: String {
        guard let file = self.path(forResource: "APIKeys", ofType: "plist") else { return "" }
        
        guard let resource = NSDictionary(contentsOfFile: file) else { return "" }
        
        guard let key = resource["ACCESS_KEY"] as? String else {
            fatalError("KEY를 찾을수없음")
        }
        return key
    }
    
    var secretKey: String {
        guard let file = self.path(forResource: "APIKeys", ofType: "plist") else { return "" }
        
        guard let resource = NSDictionary(contentsOfFile: file) else { return "" }
        
        guard let key = resource["SECRET_KEY"] as? String else {
            fatalError("KEY를 찾을수없음")
        }
        return key
    }
    
    var serviceId: String {
        guard let file = self.path(forResource: "APIKeys", ofType: "plist") else { return "" }
        
        guard let resource = NSDictionary(contentsOfFile: file) else { return "" }
        
        guard let key = resource["SERVICE_ID"] as? String else {
            fatalError("KEY를 찾을수없음")
        }
        return key
    }
 }

코드 설명

  • guard let file = self.path(forResource: "APIKeys", ofType: "plist") else { return "" }
    - APIKeys.plist 파일의 경로를 가져오는 코드입니다. 없으면 공백을 반환하도록 했습니다.
  • guard let resource = NSDictionary(contentsOfFile: file) else { return "" }
    - 파일의 데이터를 읽어오는 부분입니다.
  • guard let key = resource["넣을 키"] as? String else {
    fatalError("KEY를 찾을수없음")
    }

    - Root 중 하나를 "넣을 키" 부분에 삽입하면 됩니다.
  • return 타입은 String으로 반환하였습니다.

우선 밑의 사진처럼 API Header에서 요구하는것들을 전부 만들겁니다.

Header 1

  • API Header중 x-ncp-apigw-timestamp는 협정세계시부터의 경과시간을 요청합니다.
  • Solution
    - UTC부터 현재 시간까지의 경과시간을 String으로 변환하여 해결했습니다.
private let timestamp = String(Int(Date().timeIntervalSince1970 * 1000)) 

코드설명

  • 현재 날짜와 시간에서 1970년 1월 1일까지 의 경과시간을 나타내어 Double로 반환했습니다.
  • 그 반환된값을 * 1000을 곱함으로써 초를 밀리초로 변환했습니다.
  • 이제 변환한 값을 String으로 변환하여 밀리초로 표현하는 Int값을 String 값으로 받았습니다.

Header 2

  • SHA256 알고리즘을 통해서 시그니처를 생성하고 API Header 중 x-ncp-apigw-signature-v2 에 넣어서 리퀘스트 요청을 해야한다.
  • Issue
    - 시그니처를 생성하는 부분에서 암호화가 잘못되어서 응답코드가 401: Unauthorized를 반환했습니다.
  • Problem
    - Sha256에 Hmac을 포함시킨 HMAC-SHA-256 알고리즘으로 구현해야한다.
  • Solution
    - CommonCrypto을 사용하여 HMAC-SHA-256 알고리즘을 구현했습니다.
import CommonCrypto



private func makeSignature() -> String {
        let url = "/sms/v2/services/\(serviceId)/messages"
        let message = method + " " + url + "\n" + timestamp + "\n" + accessKey
        let keyData = secretKey.data(using: .utf8)!
        var macOut = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        keyData.withUnsafeBytes { keyBytes in
            CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), keyBytes.baseAddress, keyData.count, message, message.utf8.count, &macOut)
        }

        let hmacData = Data(bytes: macOut, count: Int(CC_SHA256_DIGEST_LENGTH))
        let base64Encoded = hmacData.base64EncodedString()
        
        return base64Encoded
    }

코드 설명

  • HMAC 및 SHA 알고리즘과 같은 암호화 함수들을 제공하는 CommonCrypto을 사용합니다.
  • url은 네이버 SMS API url에 자신의 서비스 아이디를 넣어야합니다.
  • method에는 Swagger에서 제공하는는것 중 하나를 선택해야합니다.
    - https://sens.apigw.ntruss.com/apigw/swagger-ui?productId=plv61henn8&apiId=j5tgfxp2ba&stageId=a0y11xe7vi®ion=KR
  • HMAC-SHA256을 계산하기 위해 CCHmac 함수를 사용하여 HMAC-SHA256 해시를 계산하고 macOut 배열에 저장시킵니다.
  • macOut 배열을 Data 객체로 변환한 다음 base64로 인코딩하여 최종 시그니처를 얻어냅니다.

Header 3

  • x-ncp-iam-access-key 부분에는 SMS API 진행하면서 발급받은 Access Key ID를 넣으면 됩니다.

Body

이제 API에 requset를 하기 위한 JSON의 BODY 부분들의 데이터들을 정의합니다.

	private let accessKey = Bundle.main.accessKey
    private let secretKey = Bundle.main.secretKey
    private let serviceId = Bundle.main.serviceId
    private let senderPhoneNumber = Bundle.main.senderPhoneNumber
    private let receiverPhoneNumber = " "
    private var randomNumber = ""
    private let method = "POST"
    private let timestamp = String(Int(Date().timeIntervalSince1970 * 1000))

코드 설명

  • accesskey, secretkey, serviceId, senderPhoneNumber는 이미 plist안에 넣어둔 정보를 저장했습니다.
  • receiverPhoneNumber: 받는사람 번호입니다.
  • timestamp는 Header부분에서 설명
  • randomNumber: 저는 이 문자인증을 인증 버튼을 누르면 이 메서드가 실행되어 검사를 받는 유저에게 랜덤인 문자 메시지를 전달하게 끔 구현했습니다.
 func configRandomCode() -> String {
        let digits = "0123456789"
        var randomCode = ""
        for _ in 0..<6 {
            let randomIndex = Int.random(in: 0..<digits.count)
            let digit = digits[digits.index(digits.startIndex, offsetBy: randomIndex)]
            randomCode.append(digit)
        }
        print("인증번호 생성: \\(randomCode)")
        randomNumber = randomCode
        return randomCode
    }

func checkRightCode(code: String) -> Bool {
        if randomNumber == code {
            return true
        } else {
            return false
        }
    }

코드설명 안에 코드설명

  • 저희의 앱은 랜덤인 6자리 숫자를 사용자에게 전달하여 보낸 문자와 사용자가 입력하여 검사하는 방식입니다.
  • 0부터9의 랜덤숫자를 리턴하는 메서드입니다.
  • 만약 configRandomCode() 에서 생성된 번호를 randomNumber에 저장시키고 나중에 사용자가 입력을 비교할때 checkRightCode 메서드에서 문자열을 받아 비교하는 방식으로 구현했습니다.

참고
네이버
https://losskatsu.github.io/blockchain/sha256/#4-sha-256-%EA%B3%BC%EC%A0%95

profile
공부한거 느낌대로 써내려갑니당

0개의 댓글

관련 채용 정보