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







 







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/




+ Recent posts