add jwt between game and player service

This commit is contained in:
Ryan Esch 2018-10-10 12:57:05 -05:00
parent 0f0cfaa2ed
commit 18d4e3365e
7 changed files with 123 additions and 10 deletions

View File

@ -1,5 +1,6 @@
dependencies {
compileOnly group: 'javax.websocket', name: 'javax.websocket-api', version: '1.1'
compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.0'
}
ext {
@ -17,6 +18,7 @@ liberty {
}
}
libertyStart.doLast {
println "Application available at: http://localhost:${httpPort}/"
}
}

View File

@ -3,8 +3,13 @@ package org.libertybikes.game.core;
import static org.libertybikes.game.round.service.GameRoundWebsocket.sendToClient;
import static org.libertybikes.game.round.service.GameRoundWebsocket.sendToClients;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.Calendar;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
@ -31,6 +36,10 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.libertybikes.game.core.Player.STATUS;
import org.libertybikes.restclient.PlayerService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@JsonbPropertyOrder({ "id", "gameState", "board", "nextRoundId" })
public class GameRound implements Runnable {
@ -66,8 +75,18 @@ public class GameRound implements Runnable {
private LobbyCountdown lobbyCountdown;
private AtomicBoolean lobbyCountdownStarted = new AtomicBoolean();
private String jwt = null;
private int ticksFromGameEnd = 0;
protected static Key signingKey = null;
protected String keyStore;
String keyStorePW;
String keyStoreAlias;
// Get a string of 4 random uppercase letters (A-Z)
private static String getRandomId() {
char[] chars = new char[4];
@ -266,18 +285,95 @@ public class GameRound implements Runnable {
private void updatePlayerStats() {
if (gameState != State.FINISHED)
throw new IllegalStateException("Canot update player stats while game is still running.");
throw new IllegalStateException("Cannot update player stats while game is still running.");
PlayerService playerSvc = CDI.current().select(PlayerService.class, RestClient.LITERAL).get();
int rank = 1;
for (Player p : playerRanks) {
log("Player " + p.name + " came in place " + rank);
if (p.isRealPlayer())
playerSvc.recordGame(p.id, rank);
if (p.isRealPlayer()) {
String jwt = createJWT();
String authHeader = "Bearer " + jwt;
playerSvc.recordGame(p.id, rank, authHeader);
}
rank++;
}
}
private String createJWT() {
if (jwt != null)
return jwt;
if (signingKey == null) {
try {
getKeyStoreInfo();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
Claims onwardsClaims = Jwts.claims();
onwardsClaims.put("upn", "game-service");
onwardsClaims.put("groups", "admin");
// Set the subject using the "id" field from our claims map.
onwardsClaims.setSubject(id);
onwardsClaims.setId(id);
// We'll use this claim to know this is a user token
onwardsClaims.setAudience("client");
onwardsClaims.setIssuer("https://game-service-libertybikes.mybluemix.net");
// we set creation time to 24hrs ago, to avoid timezone issues in the
// browser verification of the jwt.
Calendar calendar1 = Calendar.getInstance();
calendar1.add(Calendar.HOUR, -24);
onwardsClaims.setIssuedAt(calendar1.getTime());
// client JWT has 24 hrs validity from now.
Calendar calendar2 = Calendar.getInstance();
calendar2.add(Calendar.HOUR, 48);
onwardsClaims.setExpiration(calendar2.getTime());
// finally build the new jwt, using the claims we just built, signing it
// with our signing key, and adding a key hint as kid to the encryption header,
// which is optional, but can be used by the receivers of the jwt to know which
// key they should verify it with.
jwt = Jwts.builder()
.setHeaderParam("kid", "bike")
.setHeaderParam("alg", "RS256")
.setClaims(onwardsClaims)
.signWith(SignatureAlgorithm.RS256, signingKey)
.compact();
return jwt;
} catch (Throwable e) {
e.printStackTrace();
}
return "bad";
}
private synchronized void getKeyStoreInfo() throws IOException {
if (signingKey != null)
return;
try {
// load up the keystore
keyStore = InitialContext.doLookup("jwtKeyStore");
keyStorePW = InitialContext.doLookup("jwtKeyStorePassword");
keyStoreAlias = InitialContext.doLookup("jwtKeyStoreAlias");
FileInputStream is = new FileInputStream(keyStore);
KeyStore signingKeystore = KeyStore.getInstance(KeyStore.getDefaultType());
signingKeystore.load(is, keyStorePW.toCharArray());
signingKey = signingKeystore.getKey(keyStoreAlias, keyStorePW.toCharArray());
} catch (Exception e) {
throw new IOException(e);
}
}
private void gameTick() {
if (gameState != State.RUNNING) {
ticksFromGameEnd++;

View File

@ -2,6 +2,7 @@ package org.libertybikes.restclient;
import javax.enterprise.context.Dependent;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@ -23,6 +24,6 @@ public interface PlayerService {
@POST
@Path("/rank/{playerId}/recordGame")
public void recordGame(@PathParam("playerId") String id, @QueryParam("place") int place);
public void recordGame(@PathParam("playerId") String id, @QueryParam("place") int place, @HeaderParam("Authorization") String token);
}

View File

@ -18,6 +18,10 @@
<jndiEntry jndiName="round/gameSpeed" value="50"/> <!-- Default = 50(ms) -->
<jndiEntry jndiName="round/map" value="-1"/> <!-- Default = -1 (random map) -->
<jndiEntry jndiName="round/autoStartCooldown" value="20"/> <!-- Default = 20(sec) -->
<jndiEntry jndiName="jwtKeyStore" value="${server.config.dir}resources/security/validationKeystore.jks"/>
<jndiEntry jndiName="jwtKeyStorePassword" value="secret2"/>
<jndiEntry jndiName="jwtKeyStoreAlias" value="bike2"/>
<applicationManager autoExpand="true"/>

View File

@ -15,6 +15,9 @@ liberty {
libertyStart.doLast {
println "Application available at: http://localhost:${httpPort}/${project.name}"
}
dependencies {
compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.0'
}
task open {
doLast {

View File

@ -5,9 +5,11 @@ import java.util.Collections;
import java.util.Random;
import javax.annotation.PostConstruct;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@ -68,8 +70,13 @@ public class RankingService {
}
@POST
@RolesAllowed({ "admin" })
@Path("/{playerId}/recordGame")
public void recordGame(@PathParam("playerId") String id, @QueryParam("place") int place) {
public void recordGame(@PathParam("playerId") String id, @QueryParam("place") int place, @HeaderParam("Authorization") String token) {
recordGame(id, place);
}
public void recordGame(String id, int place) {
if (place < 0 || place > 4) {
System.out.println("Invalid place (" + place + "), must be 0-4");
return;

View File

@ -22,10 +22,10 @@
location="${server.config.dir}resources/security/validationKeystore.jks" />
<jndiEntry jndiName="jwtKeyStore" value="${server.config.dir}resources/security/validationKeystore.jks"/>
<mpJwt id="myMpJwt"
keyName="rebike"
issuer="https://auth-service-libertybikes.mybluemix.net"
<mpJwt id="myMpJwt"
keyName="bike2"
issuer="https://game-service-libertybikes.mybluemix.net"
audiences="client"/>
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${httpPort}" httpsPort="${httpsPort}" />