Apns JWT Format



Heder


{

    "alg": "ES256",            // 알고리즘

    "kid": "ABC123DEFG“   // key 8글자

}



계정 인증키는 아래에서 확인 할 수 있다. 주의 할 점은 함부로 노출되선 안되며 발급은 최초 한번 밖에 안되니 따로 저장해둘 것.






PayLoad



{

    "iss": "DEF123GHIJ", //  10-character Team ID

    "iat": 1437179036       // in terms of the number of seconds since Epoch, in UTC

 }





Signature

{

      base64_header + "." + base64_payload,

       secret

) base64_endcoding



-> 서명 형식에서 중요한 것은 secret 값이다 . 위의 계정 인증 키와 함께 .p8 인증서 파일을 다운 받을 수 있는데 파일안에 data를 읽어서 String으로 설정하면 된다.





Apple은 JWT를 사용하는 데 있어서 다음과 같은 사항을 요구하고 권장한다.


- APN은 ES256 알고리즘으로 서명 된 공급자 인증 토큰 만 지원합니다.

- 보안되지 않은 JWT 또는 다른 알고리즘으로 서명 된JWT는 거부되고 공급자는 InvalidProviderToken(403) 응답을 받습니다 .


- 보안을 보장하기 위해 APN은 주기적으로 새 토큰을 생성해야합니다.


- 새 토큰에는 토큰이 생성 된 시간을 나타내는 claim 키에서 업데이트 된 발급 항목이 있습니다. 



- 만일 토큰 문제가 발생하고 토큰 문제에 대한 타임 스탬프가 지난 1 시간 이내에 있지 않으면 APN은 후속 푸시 메시지를 거부하고 ExpiredProviderToken (403) 오류를 반환합니다.


-제공자 토큰 서명 키가 유출 된 것으로 의심되면 개발자 계정에서 해지 할 수 있습니다. 새 키 쌍을 발행 할 수 있으며 새 개인 키를 사용하여 새 토큰을 생성 할 수 있습니다. 보안을 최대화하려면 지금 폐기 된 키로 서명 된 토큰을 사용했던 APN에 대한 모든 연결을 닫고 새 키로 서명 된 토큰을 사용하기 전에 다시 연결하십시오.






=> 사용자로 하여금 지속적으로 토큰을 수정하게 하도록하고, 또한 앞서 단점이라고 생각했던 보안에 있어서 완벽하다고 할 순 없지만 apple에서는 커버하고자 하려 했던 것 같다.


=> JWT는 현재 java, python, c 등등 라이브러리를 지원하고 있다. 라이브러리를 사용하는 것보단 직접 구현을 통해 JWT를 구현해보고자 한다.




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
 
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
 
public class JsonWebToken {
    
    private String token;
    
    
    public String createToken() throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException, SignatureException {
        long nowMillis = System.currentTimeMillis() / 1000;
        
        
        //json
        String jwt_header = "{\"alg\" : \"ES256\" , \"kid\":\"KA1KG44A\"}";
        //json
        String jwt_payload = "{\"iss\" : \"TEAMID1234\" , \"iat\":" + nowMillis + "}";
        
        
        String KEY_PATH = JsonWebToken.class.getResource("").getPath() + "../../lib/AuthKey.p8";
 
 
 
        String base64_header = new String(Base64.encode(jwt_header.getBytes(StandardCharsets.UTF_8)));
        String base64_payload = new String(Base64.encode(jwt_payload.getBytes(StandardCharsets.UTF_8)));
        String part1 = base64_header + "." + base64_payload;
 
        BufferedReader br = null;
        String secret = "";
        try {
            String currentLine;
            br = new BufferedReader(new FileReader(KEY_PATH));
            while ((currentLine = br.readLine()) != null) {
                secret += currentLine;
            }
  br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        
        token = base64_header + "." + base64_payload + "." + ES256(secret, part1);
    
        System.err.println(token);
        
        return token;
        
    }
    
    public String ES256(final String secret, final String data) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, SignatureException {
 
            KeyFactory kf;
    
            kf = KeyFactory.getInstance("EC");
            KeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decode(secret));
//            KeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(secret));
            PrivateKey key;
            key = kf.generatePrivate(keySpec);
 
            final Signature sha256withECDSA = Signature.getInstance("SHA256withECDSA");
            sha256withECDSA.initSign(key);
 
            sha256withECDSA.update(data.getBytes(StandardCharsets.UTF_8));
            final byte[] signed = sha256withECDSA.sign();
            
            return Base64.encode(signed).toString();
    
                    
    }
}
cs







 






장점


(1)사용이 쉽다.

(2)서버 개발 부담을 덜어 준다.

(3)보안

- 애플리케이션의 보안을 높일 수 있지만 무조건 위험을 벗어나는 것은 아니다.


(4) 모바일 환경에 적합하다.

- (웹에서 쿠키와같은 데이터로 인증안해도됨)


(5)Stateless 서버 (무상태)

- 즉 연결 유지를 하지 않아도 데이터를 가지고 올 수 있다.

- 토큰 값만 알고 있다면, 서버로부터 토큰값과 함께 데이터를 요청할 수 있다.







단점


(1)길이

- Claim에 넣는 데이터가 많아질 수록, JWT 토큰의 길이가 길어진다.

- API 호출 시 매 호출마다 헤더에 붙어서 가야하기 때문에, 길이가 길다는 것은 그만큼 네트워크 대역폭 낭비가 심하다는 의미이다.


(2) 한번 발급된 토큰은 값을 수정하거나 폐기가 불가

- JWT는 토큰 내에 모든 정보를 다 가지고 있기 때문에, 한번 발급된 토큰에 대한 변경은 서버에서는 더 이상 불가능하다. 예를 들어 토큰을 잘못 발행해서 삭제하고 싶더라도, Signature만 맞으면 맞는 토큰으로 인식을 하기 때문에, 서버에서는 한번 발급된 토큰의 정보를 바꾸는 일등이 불가능하다.

그래서 만약에 JWT를 쓴다면, Expire time을 꼭 명시적으로 두도록 하고, refresh token등을 이용해서, 중간중간 토큰을 재발행하도록 해야 한다.



(3)보안

JWT는 기본적으로 Claim에 대한 정보를 암호화 하지 않는다. 단순히 BASE64인코딩만 하기 때문에, 중간에 패킷을 가로채거나, 기타 방법으로 토큰을 취득했으면 토큰 내부 정보를 통해서 사용자 정보가 누출 될 수 있는 가능성이 있다


특히 자바스크립트 기반의 웹 클라이언트의 경우 브라우져상의 디버거등을 통해서 토큰이 노출될 가능성이 높다.


그래서, 이를 보완하는 방법으로는 토큰 자체를 암호화 하는 방법이 있다. JSON을 암호화 하기 위한 스펙으로는 JWE(JSON Web Encryption)





=> 위와 같은 단점에도 불구하고 폐쇄성이 높은 APPLE에서 JWT를 사용한다는 것은 위의 단점을 모두 커버했을 뿐 아니라 적절하게 이용되고 있을 것이라고 생각하다. 











APNS 공급자 인증


HTTP/2 프로토콜을 이용하여 APNS Provider API를 사용할 경우, JWT(Json Web Token) 을 지원하며 이를 통해 공급자 인증을 수행할 수 있다.
또한 APNS 개발자 계정의 고유한 key 값을 통해 JWT가 만들어지며 모든 앱에 Notification을 수행할 수 있는 강력한 방법이다.

HTTP/2 프로토콜을 이용하여 JWT를 사용하지 않고, 인증서를 통해 공급자 인증을 수행한다면 당연하게도 하나의 앱에서 Notificaiton을 수행한다.

공급자 인증을 수행하기 전에 JWT에 대해 자세히 알아봐야 한다.






JWT


- JWT JSON 객체를 전달할 수 있고 여기에 서명하거나 암호화 할 수 있다.

- JWT 는 보통 2가지 경우에서 사용한다.


(1)인증

(2)정보 교류


- 구조 


aaaaa.bbbb.cccccc

                                              header       payload      signature    



(1) JWT Header


- Typ : 토큰의 타입 

- Alg : 알고리즘



{

  "typ": "JWT",

  "alg": "HS256“

 }







(2) JWT Payload



payload 부분에는 토큰에 담을 정보가 들어있다
- payload에 담는 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부르고, 이는 name / value 의 한 쌍으로 이뤄져있다. 
- 토큰에는 여러개의 클레임 들을 넣을 수 있습니다.
- 클레임 의 종류는 다음과 같이 크게 세 분류로 나뉘어져있다.

 등록된 (registered) 클레임, 공개 (public) 클레임, 비공개 (private) 클레임



{

  "iss": "velopert.com",  //등록된 클레임

   "exp": "1485270000000",  //등록된 클레임

   "https://velopert.com/jwt_claims/is_admin": true,  //공개된 클레임

   "username": "velopert    //비공개 클레임

}




- 클레임의 종류 


iss: 토큰 발급자 (issuer)

sub: 토큰 제목 (subject)

aud: 토큰 대상자 (audience)

exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (: 1480849147370) 언제나 현재 시간보다 이후로 설정


nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다.


iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 가능하다.

jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용하다.








(3) JWT Signature


- 서명 부분을 만들려면
인코딩 된 헤더, 인코딩 된 페이로드, 암호, 헤더에 지정된 알고리즘을 서명해야한다.

- 예를 들어 HMAC SHA256 알고리즘을 사용하려면 다음과 같은 방법으로 서명을 만든다.


{

  base64_header + "." + base64_payload,

  secret

) base64_endcoding















참고: https://jwt.io/





Payload 는 다음과 같은 형태로 보낸다.


Json Data


{

"aps":{

"alert":{

"title":"title",

"body":"Hi"

},

"badge":3,

"sound":"default"

}

}





Frame 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
public class Frame {
    private final int COMMAND_NOTIFICATION = 2;
 
    private final short ITEM_ID_DEVICE_TOKEN = 1;
    private final short ITEM_ID_PAYLOAD = 2;
    private final short ITEM_ID_NOTIFICATION_ID = 3;
    private final short ITEM_ID_EXPIRATION_DATE = 4;
    private final short ITEM_ID_PRIORITY = 5;
 
    private final int MAX_PAYLOAD_BYTES = 2048/// 2048 크기 이상 pay 로드에 싣을수 없음.
 
    private String device_token;
    private int frame_length;
 
    private byte[] frame_data;
 
    public Frame(String device_token) {
        this.device_token = device_token;
    }
 
    public byte[] getFrame_data() {
 
        return frame_data;
    }
 
    public int getFrame_length() {
 
        return frame_length;
    }
 
    public int getCommandNotification() {
        return COMMAND_NOTIFICATION;
    }
 
    public void pack() {
        
 
        // payload Data를 담는다.

        String payload = get_jsonData();

        try {
            
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            
            
            baos.write(ITEM_ID_DEVICE_TOKEN);
            baos.write(intTo2ByteArray(32));
            baos.write(DatatypeConverter.parseHexBinary(device_token));
            
        
            
 
            baos.write(ITEM_ID_PAYLOAD);
            baos.write(intTo2ByteArray(payload.getBytes().length));
            baos.write(payload.getBytes("UTF-8"));
    
            
            baos.write(ITEM_ID_NOTIFICATION_ID);
            baos.write(intTo2ByteArray(4));
            baos.write(intTo4ByteArray(5));
 
            
            baos.write(ITEM_ID_EXPIRATION_DATE);
            baos.write(intTo2ByteArray(4));
            baos.write(intTo4ByteArray(5));
            
            baos.write(ITEM_ID_PRIORITY);
            baos.write(intTo2ByteArray(1));
            baos.write((byte)10);
            
            
            
            frame_length = baos.size();
            frame_data = new byte[baos.size()];
            frame_data = baos.toByteArray();
 
baos.close();
    
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
 
    // json 데이터를 가져오는 과정
    public String get_jsonData() {
 
        JSONParser parser = new JSONParser();
        JSONObject jObj = new JSONObject();
        Object obj = null;
 
        try {
            String path =  Main.class.getResource("").getPath();
            obj = parser.parse(new FileReader(path));
            jObj = (JSONObject) obj;
 
            System.out.println("payLoad:" + jObj.toJSONString());
 
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        return jObj.toString();
    }
 
    private static final byte[] intTo4ByteArray(int value) {
        return ByteBuffer.allocate(4).putInt(value).array();
    }
 
    private static final byte[] intTo2ByteArray(int value) {
        int s1 = (value & 0xFF00>> 8;
        int s2 = value & 0xFF;
        return new byte[] { (byte) s1, (byte) s2 };
    }
 
}
cs



-> 전체 Frame Data에 5개의 ITEM 추가 하였다.

 






Binary Provider API


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
 
import javax.net.ssl.SSLSocket;
 
 
public class Binary_Provider_API {
 
    private SSLSocket sSock;
    private String device_token;
 
    public Binary_Provider_API(SSLSocket sSock, String device_token) {
        this.sSock = sSock;
        this.device_token = device_token;
 
    }
 
    
    public void send() {
 
        try {
            Frame frame = new Frame(device_token);
            frame.pack();
            OutputStream outputStream = sSock.getOutputStream();
            outputStream.write(frame.getCommandNotification());
            System.out.println("api_send_command:" + frame.getCommandNotification());
 
            outputStream.write(intTo4ByteArray(frame.getFrame_length()));
            System.out.println("api_send_length:" + frame.getFrame_length());
 
            outputStream.write(frame.getFrame_data());
            System.out.println("Item Packet");
            System.out.println(Arrays.toString(frame.getFrame_data()));
 
            outputStream.flush();
outputStream.close();
 
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
    }
    private static final byte[] intTo4ByteArray(int value) {
        return ByteBuffer.allocate(4).putInt(value).array();
    }
cs



frame data를 가지고 온 이후, Binary Provider API Format에 맞게 보내면 끝.




command / frame_length / frameData






Apns Provider API



1. HTTP/2 프로토콜을 통해 APNS와 연결 가능하며, 인증서와 JWT Token을 이용해 푸쉬 알림을 보낼 수 있다.


2. HTTP/2 프로토콜을 이용한 APNS는 다음과 같은 특징을 갖고 있다.


http/2 ping Frame을 통해 연결 유지 가능하다.

http/2 goaway Frame을 통해 연결 종료 가능하다.








Request Format 





<HTTP/2 Header> 



Method

POST

PATH

/3/device/<장치토큰>




위의 두가지는 필수적으로 추가해야한다.

추가적으로 포함시켜야 하는 키와 데이터는 다음과 같은 항목이 있다,.



- authorization : 인증서 방식이 아닌 토큰 방식을 사용할 때 사용되는 키값이다.

 (토큰은 base64url인코딩된(Signature) jwt format 이어야 한다구체적으로 Bearer + <jwt token> 형태이다.)


- apns-topic : 인증서 방식이 아닌 토큰 방식을 사용되는 키값이다. 토큰 방식은 모든 앱에 전송할 수 있다.

 그렇기 때문에 apns-topic 키 값의 앱의 bundle_id에 해당하는 데이터를 입력한다.


- apns-idUUID , 형태는 32글자의 소문자를 포함한 16진수 Ex) 123e4567-e89b-12d3-a456-42665544000)


apns-expirationutc 시간, 0이면 저장하지 않고 바로 보낸다.


apns-priority : 10이면 즉시보내고, 5이면 클라이언트의 전원장치를 고려하여 전송한다. 기본값은 10이다.


apns-collapse-id64바이트를 초과하지 않아야 하고, 동일한 식별자 값의 알림을 단일 알림으로 표시한다.






<HTTP/2 Body>


- 보내고자 하는 Payload 데이터를 담아 보낸다.












Response Format




<HTTP/2 Header> 


Apns-id

Request apns-id의 값. 요청에서 포함하지 않았다면 apns 서버 자체적으로 새uuid를 만들고 헤더에 반환한다.

Status


응답 코드





Status 









<HTTP/2 body> 


- 성공적으로 request 요청이 수행되면 body 데이터는 비어있다.


- 실패하면 다음과 같은 항목이 포함된다.




Reason

실패에 대한 이유

timestamp


410 에러 상태일 때만 나오는 키 값 , 디바이스 토큰이 마지막으로 활성화 되었을 때의 시간






Status 및 Reason 









Netty를 통해 샘플코드를 작성해볼 수있다.





참고

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/BinaryProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH13-SW1










FeedBackService



:메시지전달 실패에 대한 정보를 제공하는 서비스이다.


- 특정 장비에서 이미 삭제된 애플리케이션으로 Push 메시지를 전달하려고 시도할 경우 해당 장비는 Push 메시지 수신을 거부한다. 

- 원격알림을 전송할 수 없는 디바이스 토큰을 피드백리스트 목록에 올려놓는다. 

- 전송실패 혹은 오류에 대한 알림은 피드백서비스에 영향을 미치지 않는다.

 

- feedback.push.apple.com의 도메인과 2196포트로의 연결을 통해 피드백서비스를 사용할 수 있다.

   (개발서버는 feeback.sandbox.push.apple.com/2196으로 연결한다.)


- 피드백서비스를 사용하여 전달할 수없는 원격 알림 전송을 중지하면 불필요한 메시지 오버 헤드가 줄어들고 전체 시스템 성능을 향상 할 수 있다.

- end-to-end의 TLS/SSL 연결만이 허용된다.


 

-개발서버에서 어플에 해당하는 인증서를 갖고 피드백 서비스에 연결하면 즉시 피드백 서비스에서 리스트업 된 디바이스 토큰 정보가 포함된 데이터를 전송한다.(연결 후 서버에서 따로 해 주어야 할 내용은 없다) 


- 피드백 서비스에서 저장된 디바이스 토큰 리스트의 데이터를 다 받게 되면 서비스 내의 토큰들은 모두 삭제된다.


-개발자 문서에서는 적어도 하루에 한번 피드백 서비스를 확인하라고 명시되어 있으며 상황에 따라 유동적으로 확인이 필요하다.


- 어떠한 요청도 보낼 필요 없이, 연결 즉시 응답이 오며 Connection은 종료된다.





Format






 Timestmap : 데이터에 포함된 타임스탬프 정보를 갖고 서버에 저장된 디바이스 토큰의 갱신여부를 판단할 수 있다. 

 Token Lenth : 디바이스 토큰의 길이

 device Token : 디바이스 토큰 값







예제



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Arrays;
 
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
 
public class Main {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        SSLSocket feedSock = null;
        try {
 
            KeyStore ks = null;
            ks = KeyStore.getInstance("PKCS12");
            ks.load(new FileInputStream("path.."), "password".toCharArray());
 
            System.out.println("key size:" + ks.size() + "/key type:" + ks.getType());
 
            /// SUNX 509 or PKIX
            // 인증서 인코딩 알고리즘
            KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
            kmf.init(ks, "password..".toCharArray());
 
            TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
            tmf.init((KeyStore) null);
 
            // TLS or SSL protocol get
            SSLContext sc = SSLContext.getInstance("TLS");
            // SSLContext sc = SSLContext.getInstance("SSL");
 
            sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
            SSLSocketFactory ssf = sc.getSocketFactory();
 
            feedSock = (SSLSocket) ssf.createSocket("feedback.sandbox.push.apple.com"2196);
            String[] cipherSuites = feedSock.getSupportedCipherSuites();
            feedSock.setEnabledCipherSuites(cipherSuites);
 
            // TLS HandShake
            feedSock.startHandshake();
 
            ;
            System.out.println("피드백 서비스 연결:" + feedSock.isConnected());
 
            int a = 0;
            bis = new BufferedInputStream(feedSock.getInputStream());
            while (true) {
                byte[] tuple = new byte[38];
                a = bis.read(tuple, 0, tuple.length);
                System.out.println(a);
 
                byte[] time_t = Arrays.copyOfRange(tuple, 04);
                byte[] token_length = Arrays.copyOfRange(tuple, 46);
                byte[] token = Arrays.copyOfRange(tuple, 6, tuple.length);
                if (a > 0) {
                    // System.out.println("time_t:" + byteArrayToInt(time_t));
                    // System.out.println("token_length:" + byteArrayToInt(token_length));
                    // System.out.println("token:" + byteArrayToHex(token));
                } else {
                    break;
                }
            }
 
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (KeyStoreException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (KeyManagementException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (CertificateException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 
        finally {
            try {
                bis.close();
                feedSock.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
cs








'Cloud & NoSQL & Middleware > Apns' 카테고리의 다른 글

Binary Provider API 구현  (0) 2018.06.11
HTTP/2 - based Apns Provider API  (0) 2018.05.31
Binary Provider API - Request, Response Format  (0) 2018.05.25
APNS Provider Protocol  (0) 2018.05.25
APNS Notification Payload  (0) 2018.05.15


Request Format









여러 알림에 대한 일괄 처리를 최적의 성능과 함께 수행하는 인터페이스이다.


Command : 1 byte

Frame length : 4 byte

Frame data : 일련의 아이템으로 구성된 가변 데이터









     


프레임은 여러 아이템이 들어있는 뼈대이다.


Item ID : 1byte

Item length : 2 byte

Item data : 가변 데이터



Item ID

 Item length

Item data

 1  ( Device Token)

100 byte 까지

등록된 디바이스의 binary 형태의 값
(
하나이상의 token보내져야함)

 2  (payLoad)

2 kilobyte (2048 byte)

 json 형태의 payload

(하나 이상의 payload가 보내져야 하며

Null로 끝나서는 안됨)

 3  (Notificatin Identifier)

4 byte

Push notification 의 식별 값

 4  (expiry)

4 byte

UTC 로 표현된 날짜 값

( 0 이상이면 적어도 한번 보내고 0이면 즉시 만료되어 알림이 저장되지 않음)

 5  priority

1 byte

 notification의 우선순위

10 아니면 5 중 하나 선택.

10: 즉시 전송

5: 전원의 배터리를 고려하여 전송









Response Format





Apns가  Device에게 성공적으로 알림을 보냈다면, Response Packet을 Provider에게 보내지 않는다.



- command : 1byte이며 Command Number 8

- status code : 발생한 에러에 따라 응답코드를 반환한다.

- Identifier : Provider에서 알림을 보낼 때 포함한 식별자 4byte 값을 반환한다.








참고

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/BinaryProviderAPI.html#//apple_ref/doc/uid/TP40008194-CH13-SW1






'Cloud & NoSQL & Middleware > Apns' 카테고리의 다른 글

HTTP/2 - based Apns Provider API  (0) 2018.05.31
FeedbackService - Format, Packet  (0) 2018.05.29
APNS Provider Protocol  (0) 2018.05.25
APNS Notification Payload  (0) 2018.05.15
APNS Architecture - Connection & Push Flow  (0) 2018.05.11


Protocol


애플을 통해 푸쉬 알림을 보내기 위한 방식은 TCP 방식과 HTTP 방식이 있다.



애플에서 TCP 기반으로 보내는 API를 Binary Provider API , HTTPS 기반으로 보내는 방식을 APNS Provider API라고 명칭하고 있다.






Compare


 

 Binary Provider API

APNS Provider/Notification API 

 protocol

 TCP

HTTP/2 

 host / port

 

 gateway.sandbox.push.apple.com  // 2195      gateway.push.apple.com  // 2195



feedback.sandbox.push.apple.com  // 2196

: feedback.push.apple.com  // 2196 

api.development.push.apple.com:443

api.push.apple.com:443

Port is 443 or 2197

 data

 2KB

4KB 

 figure

High Capacity

 High Security, High Speed

 certificate

 .p12

 .p8, .p12

 feedback service

 O




(1) protocol : Binary Provider API에서는 TCP, APNS Provider API에서는 HTTP/2 Protocol 기반에서 수행된다.


(2) host / port : sandbox가 붙으면 개발서버, 아니면 운영서버이고 Binary Provider API는 피드백서비스를 활성화 해야한다.


(3) data : Payload의 용량 제한은 위와 같다. (VolP Notification 을 보낼 시 최대 5KB까지 가능.)


(4) figure : Binary Provider API 에서는 비동기 처리가 가능하고 생산성이 좋은 반면, HTTP/2 방식은 보안과 속도 측면에서 뛰어나다는 장점이 있다.


(5) certificate: TCP 방식은 .p12 인증서 파일만 사용가능하며, HTTP/2 방식으로 했을 경우에는 Token 방식을 사용할 수 있기 때문에 .p8 파일을 이용하여 여러 앱에 푸쉬 알림을 보낼 수 있다.


(6) feedback service:  feedback 서비스는 메시지전달 실패에 대한 정보를 제공하는 서비스이다. 자세한 정보는 추가적으로 다룰 예정






+ Recent posts