Apa Itu JSON Web Token (JWT)
Selama ini bikin coding restfull ga pernah pake security, mungkin karena ga pernah bikin web untuk public kali ya, jadi yah bikin standar aja. Tapi karena perkembangan IT yang cukup cepat and mulai coding go public, mau ga mau harus belajar secuity-nya.Catatan untuk saat ini adalah mengimplementasikan JWT (JSON Web Token). Dan Apa itu JWT ???
Dari hasil baca-baca sekilas JWT adalah sebuah URL yang dikemas dengan aman untuk mewakili klaim transaksi 2 buah party. Klaim-nya sendiri di encode menjadi JSON object sebagai tandatangan digital mengunakan JWS (JSON Web Signature).
Kurang lebih seperti ini gambaran format-nya:
<base64-encoded header>.<base64-encoded claims>.<base64-encoded signature>JWT Struktur
Terbagi menjadi 3 bagian, yaitu:
- Header, terdiri dari:
Atribut Tipe Keterangan typ
(mandatory)string tipe token, default “JWT” alg
(mandatory)string algoritma yang digunakan oleh signature
{ "typ":"JWT", "alg":"HS256" }
- Claim, berupa pesan dari informasi keamanan
Atribut Tipe Keterangan iss
(mandatory)String Issuer Claim. untuk mengidentifikasi aplikasi client iat
(mandatory)Long issue time, tanggal (UTC Unix time) token di buat (dalam second) exp
(mandatory)Long Expire time. Masa berlaku token (dalam second) qsh
(mandatory)String Query String Hash sub
(optional)String Subjek token aud
(optional)String audien token, berupa informasi produk Note:
Contoh:
Sehubungan library yang digunakan berasal dari atlassian, maka atribut sub dan aud akan kita abaikan
{ "iss": "jira:1314039", "iat": 1300819370, "exp": 1300819380, "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9" }
- Signature
Baiklah kita sudahi saja teori-nya dan kita mulai untuk implementasi, ada 2 sisi yang akan kita buat, yaitu
- Client
untuk generate token - Server
validasi token
- Jdk 1.8.x
- Maven 3.x
CLIENT
pom.xml
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
| <project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" <modelVersion> 4.0 . 0 </modelVersion> <groupId>com.chahyadis.jwtJwtClient</name> <properties> <jwt.version> 1.0 -m 7 </jwt.version> </properties> <dependencies> <dependency> <groupId>com.atlassian.jwt</groupId> <artifactId>jwt-api</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>com.atlassian.jwt</groupId> <artifactId>jwt-core</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version> 2.6 </version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version> 3.8 . 1 </version> <scope>test</scope> </dependency> </dependencies> <repositories> <repository> <id>opencast-public</id> </repository> </repositories> </project> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| public class UrlContextPathModel { private Long expiresAt; private String key; private String sharedSecret; private String method; private String baseUrl; private String contextPath; private String apiPath; public UrlContextPathModel(Long expiresAt, String key, String sharedSecret, String method, String baseUrl, String contextPath, String apiPath) { this.expiresAt = expiresAt; this.key = key; this.sharedSecret = sharedSecret; this.method = method; this.baseUrl = baseUrl; this.contextPath = contextPath; this.apiPath = apiPath; } // getter - setter ... } |
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
| public class ClientToken { public String createClientToken(UrlContextPathModel urlContextPathModel, Map<String, String[]> parameterMap) throws UnsupportedEncodingException, NoSuchAlgorithmException { long issuedAt = TimeUtil.currentTimeSeconds(); long expiresAt = issuedAt + urlContextPathModel.getExpiresAt(); String key = urlContextPathModel.getKey(); String sharedSecret = JwtUtil.computeSha 256 Hash(urlContextPathModel.getSharedSecret()); String method = urlContextPathModel.getMethod(); String baseUrl = urlContextPathModel.getBaseUrl(); String contextPath = urlContextPathModel.getContextPath(); String apiPath = urlContextPathModel.getApiPath(); // create claim JwtJsonBuilder jwtBuilder = new JsonSmartJwtJsonBuilder() .issuedAt(issuedAt).expirationTime(expiresAt).issuer(key); CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest(method, apiPath, contextPath, parameterMap); JwtClaimsBuilder.appendHttpRequestClaims(jwtBuilder, canonical); // claim content String jwtbuilt = jwtBuilder.build(); // create jwt token JwtWriterFactory jwtWriterFactory = new NimbusJwtWriterFactory(); String jwtToken = jwtWriterFactory.macSigningWriter( SigningAlgorithm.HS 256 , sharedSecret).jsonToJwt(jwtbuilt); String apiUrl = baseUrl + apiPath + "?jwt=" + jwtToken; return apiUrl; } } |
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
| public class ClientTokenTest extends TestCase { public void testCreateClientToken() { String sharedSecret = "blablabla" ; UrlContextPathModel ucpm = new UrlContextPathModel( 180 L, "coba-prj" , sharedSecret, "/rest/serverToken" ); Map<String, String[]> parameterMap = new HashMap<String, String[]>(); parameterMap.put( "name" , new String[] { "chahyadis" }); parameterMap.put( "gender" , new String[] { "cowok" }); ClientToken ct = new ClientToken(); String token = "" ; try { token = ct.createClientToken(urlContextPathModel, parameterMap); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } System.out.println( ">> token: " + token); } } |
SERVER
pom.xml
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
| <project xmlns= "http://maven.apache.org/POM/4.0.0" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" <modelVersion> 4.0 . 0 </modelVersion> <groupId>com.chahyadis.jwt</groupId> <artifactId>jwt-server</artifactId> <packaging>jar</packaging> <version> 1.0 </version> <name>JwtServer</name> <properties> <jwt.version> 1.0 -m 7 </jwt.version> </properties> <dependencies> <dependency> <groupId>com.atlassian.jwt</groupId> <artifactId>jwt-api</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>com.atlassian.jwt</groupId> <artifactId>jwt-core</artifactId> <version>${jwt.version}</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version> 2.6 </version> </dependency> <dependency> <groupId>net.minidev</groupId> <artifactId>json-smart</artifactId> <version> 1.0 . 9 </version> </dependency> <dependency> <groupId>com.chahyadis.jwt</groupId> <artifactId>JwtObject</artifactId> <version> 1.0 -SNAPSHOT</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version> 3.1 . 0 </version> </dependency> <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version> 0.7 . 4 </version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version> 3.8 . 1 </version> <scope>test</scope> </dependency> </dependencies> <repositories> <repository> <id>opencast-public</id> </repository> </repositories> </project> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| public class JsonOutModel { private int code ; private String message; private int errCode; public JsonOutModel() { } public JsonOutModel(int code , String message, int errCode) { super (); this. code = code ; this.message = message; this.errCode = errCode; } // getter - setter ... } |
Create class SignatureVerification
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
| public class SignatureVerification implements JWSVerifier { private String sharedSecret; public SignatureVerification(String sharedSecret) { try { this.sharedSecret = JwtUtil.computeSha 256 Hash(sharedSecret); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } ... public boolean verify(ReadOnlyJWSHeader header, byte[] signingInput, Base 64 URL signature) throws JOSEException { String[] token = StringUtils.newStringUtf 8 (signingInput).split( "\\." ); // signingInput format validation if (token.length != 2 ) { return false; } try { // create signature String newSignature = signHmac 256 ( StringUtils.newStringUtf 8 (signingInput), null); // validate signature if (newSignature.equals(signature.toString())) { return true; } } catch (InvalidKeyException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return false; } private String signHmac 256 (String signingInput, String encType) throws NoSuchAlgorithmException, InvalidKeyException { String typeEnc = (null != encType ? encType : "HmacSHA256" ); SecretKey key = new SecretKeySpec(sharedSecret.getBytes(), typeEnc); Mac mac = Mac.getInstance(typeEnc); mac.init(key); return EnDecryptionUtil.encodeBase 64 URLSafeString(mac.doFinal(signingInput.getBytes())); } } |
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
| public class Verification { public static JWTClaimsSet signClaimValidate(String jwt, JWSVerifier verifier, UrlContextPathModel urlContextPathModel, Map<String, String[]> parameterMap, boolean isIssVal, IssuerKey issuerKey) throws ParseException, JOSEException, JwtInvalidClaimException, UnsupportedEncodingException, NoSuchAlgorithmException { JWSObject jwsObject = JWSObject.parse(jwt); String msg = "|Fraudulent token!" ; if (!jwsObject.verify(verifier)) { throw new IllegalArgumentException( "403" + msg); } // payload token JSONObject jsonPayload = jwsObject.getPayload().toJSONObject(); // parse to JWTClaimsSet Object. JWTClaimsSet jcs = JWTClaimsSet.parse(jsonPayload); if (null == jcs.getAllClaims()) { throw new JwtInvalidClaimException( "401" + "|Claims no exist!" ); } else { // mapper to JwtClaimModel object ModelMapper mm = new ModelMapper(); JwtClaimModel jcm = new JwtClaimModel(); mm.map(jcs.getAllClaims(), jcm); // claim content validation if (null == jcm.getQsh() || null == jcm.getIat() || null == jcm.getExp() || jcm.getExp().before(new Date())) { throw new IllegalArgumentException( "403" + msg); } // qsh validation if (!jcm.getQsh().equals(qshHash(urlContextPathModel, parameterMap))) { throw new IllegalArgumentException( "403" + msg); } // iss validation if (isIssVal) { if (null == jcm.getIss() || !Arrays.asList(issuerKey.appsName()).contains(jcm.getIss())) { throw new IllegalArgumentException( "401" + "|Illegal Client!" ); } } } return jcs; } private static String qshHash(UrlContextPathModel urlContextPathModel, Map<String, String[]> parameterMap) throws UnsupportedEncodingException, NoSuchAlgorithmException { CanonicalHttpUriRequest canonical = new CanonicalHttpUriRequest( urlContextPathModel.getMethod(), urlContextPathModel.getApiPath(), urlContextPathModel.getContextPath(), parameterMap); return HttpRequestCanonicalizer.computeCanonicalRequestHash(canonical); } } |
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
| public static void main(String[] args) { try { String token = // ambil nilainya dari output testing client diatas String request = token.split( "jwt=" )[ 1 ]; JwtTokenModel tokenModel = EnDecryptionUtil.decodeJWT(request); // setup header jwt JwtHeaderModel hm = tokenModel.getJwtHeaderModel(); JwtClaimModel cm = tokenModel.getJwtClaimModel(); // cek dulu hasilnya System.out.println( "<><>>>>>> " + hm.getTyp() + "; " + hm.getAlg()); System.out.println( "<> " + cm.getQsh() + ";" + cm.getExp() + ";" + cm.getIat()); // verified token // nilai variable ucmp & parameterMap ambil dari testing client diatas JWTClaimsSet verified = Verification.signClaimValidate(request, new SignatureVerification(sharedSecret), ucpm, parameterMap); // jwt claim model setup Map<String, Object> claim = verified.getAllClaims(); ModelMapper mm = new ModelMapper(); JwtClaimModel cm 1 = new JwtClaimModel(); mm.map(claim, cm 1 ); for (Map.Entry<String, Object> entry : claim.entrySet()) { System.out.println( ">> claim: " + entry.getKey() + ": " + entry.getValue()); } } catch (IllegalArgumentException il) { il.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } |
Sebagai bahan referensi bisa baca disini
sumber : https://javdev.wordpress.com/2015/04/06/json-web-token-jwt/