Cheat Sheet 


- 컨닝페이퍼 정도의 의미



새로운 언어에 적응하고 능숙해지는데 시간이 필요하다. 모든 문법을 암기하는 것보단 자주 사용하면서 익숙해지는게 아무래도 자연스럽지 않을까 싶다.

이럴 때 구글링하면서 일일히 찾아보기보단 잘 정리된 문서를 펼쳐놓고 사용하는 것이 좀 더 효율적으로 프로그래밍 할 수 있을 것이다.



프로그래밍을 하면서 문법을 잘 알고 코딩을 잘하는 것도 중요하지만, 내가 알고 있는 지식을 어떻게 활용해서 설계 할지에 대한 고민 그리고 목적에 대한 확실성의 고민이 중요한 것이 아닌가 요새 점차 생각이 든다.   

 


아래 사이트에서 언어, 프레임워크, 형상관리 등 개발자로서 필요한 정보를 얻을 수 있다.


http://www.cheat-sheets.org

'기타' 카테고리의 다른 글

Thread 분석  (0) 2018.11.19
Oracle JDK 유료화의 논쟁, OpenJDK와의 차이  (0) 2018.11.03
DDD(Domain Driven Design)  (0) 2018.10.14
정규표현식(Regular Expression)  (0) 2018.07.08
단축URL 서비스  (0) 2018.07.08


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





Closures


Closures는 자바스크립트가 제공하는 막강한 추상 개념이다. 하지만 동시에 혼란스럽기도 하다. 직역하면, 폐포(닫힌 주머니)를 의미한다.



1
2
3
4
5
function makeAdder(a) {
    return function(b) {
        return a+b;    
    }
}
cs


x = makeAdder(5);

x(6)


-> 11



makeAdder 함수는 한 매개변수를 통해 호출되고, 주어진 매개변수를 더하는 새 익명함수를 생성한다.


또 다른 외부 함수 혹은 호출자가 외부 함수의 변수에 액세스한 다는 점에서 내장함수가 수행하는 일과 비슷하다고 볼 수 있다.


한가지 다른점은 외부 함수가 리턴된다는 점인데, 일반적으로 생각할 때 변수는 사라진다고 볼 수 있으나 여전히 존재하고 있다.


또한, 자바스크립트의 함수가 실행될 때는 언제나, 스코프 객체가 생성되어 해당 함수내에 생성된 지역변수를 저장하고 있다.


함수 매개변수로서 넘어온 어떤 값이라도 초기값으로 저장하고 있다. 이것은 전역변수 및 전역 객체와 비슷하지만, 2가지 차이점이 있다.


1) 함수가 실행될 때마다 새로운 스코프 객체가 생성된다.

2) 전역 객체와 달리 스코프 객체는 자바스크립트 코드에서 직접 접근 불가능하다.


따라서 makeAdder 함수가 호출되면, 스코프 객체는 makeAdder 함수에 매개변수로 넘겨진 하나의 속성 값 a를 갖는 상태로 생성된다.


일반적으로 자바스크립트의 가비지 컬렉터가 makeAdder에 의해 생성된 범위 객체를 제거해야하지만, 리턴된 함수가 여전히 스코프 객체를 참조한다.


결과적으로 스코프 객체는 makeAdder에 의해 리턴된 함수가 더는 참조되지 않을 때까지 가비지컬렉터는 삭제하지 않는다.


또한 Closures는 상태를 저장하도록 허용한다. 그렇기 때문에 객체 내부에서 자주 사용될 수 있는 것이다.



즉 Closures는 함수와 함수에 의해 생성되는 범위 객체를 함께 지칭하는 용어이다.








메모리 누출


자바스크립트는 객체가 생성됨에 따라 메모리를 할당하고, 더 참조하는 다른 객체가 없으면 메모리에서 제거하는 등 가비지컬렉트를 하는 언어이다.  


이에 따라, Closures의 부작용은 IE에서 심각하지는 않지만 메모리 누출이 쉽게 된다는 것이다. 


브라우저 호스트는 HTML 페이지의 DOM 객체로 표현된 많은 수의 객체를 다루어야 한다. 


객체들을 어떻게 할당하고 거둬들이는 지는 브라우저의 책임이다. IE는 자신만의 고유한, 자바스크립트와는 다른 가비지 컬렉션 방식을 사용한다. 


IE에서 메모리 누출은 자바스크립트 객체와 고유 객체간의 참조하는 중 자기 자신을 참조(circular reference : 순환 참조)하게 되는 일이 발생할 경우라면 언제든지 발생하게 된다.



1
2
3
4
5
6
7
function leakMemory(){
 
    var el = document.getElementById('el');
    var o = {'el' : el };
    el.o = o;
 
}
cs




위의 코드는 순한 참조로서 메모리 누출을 일으킨다. IE는 완전히 다시 시작하기 전까지 el과 O에 의해 사용되는 메모리를 반환하지 못한다.


일반적으로 메모리 누출이 이렇게 명확한 경우는 드물다. 누출을 일으키는 데이터 구조는 수차례에 거친 참조 구조를 가지고 있어 순환참조를 하고 있는지 명확하지 않기 때문이다.


Closures는 위와 같은 경우가 아니더라도 간단하게 메모리 누출이 일어날 수 있다.



1
2
3
4
5
6
7
function addHandler(){
 
    var el = document.getElementById('el');
    el.onclick = function(){
        this.style.backgroundColor = 'red';
    }
}
cs



위의 코드는 클릭했을 때 배경색이 바뀌는 엘리먼트를 설정한다. 그리고 메모리 누출 또한 일어난다.


el을 참조하면 의도와 달리 익명함수 때문에, 생성된 Closures 내에 객체가 붙잡혀 있기 때문이다.


이는 자바스크립트 객체(내부 함수)와 원시 객체(el) 간의 순환 참조를 만든다.



다음과 같은 방법을 통해 이 문제를 피할 수 있다.



1
2
3
4
5
6
7
function addHandler(){
 
    var el = document.getElementById('el');
    el.onclick = function(){
        this.style.backgroundColor = 'red';
    }
el = null;
}

cs





혹은 다음과 같은 방법으로 메모리 누출을 피할 수 있다.



1
2
3
4
5
6
7
8
9
10
function addHandler(){
    var clickHandler = function(){
        this.style.backgroundColor = 'red';
    }
 
    (function(){
    var el = document.getElementById('el');
    el.onclick = clickHandler;
    })();
}
cs


클로져에 의해 발생된 순환 참조를 끈기 위해 또 다른 클로져를 추가 했다.

내부함수는 실행되고 바로 사라지므로 clickHandler와 함께 생성된 Closure로 부터 내용을 숨긴다.





'programming > JavaScript' 카테고리의 다른 글

사용자 정의 객체, 프로토타입, call, apply  (0) 2018.05.27
배열, 함수  (0) 2018.05.26
객체 (Object)  (0) 2018.05.26
데이터 타입, 변수, 연산자  (0) 2018.05.26


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

+ Recent posts