WIP
This commit is contained in:
@ -37,9 +37,9 @@
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.7.0</version>
|
||||
<groupId>org.bitbucket.b_c</groupId>
|
||||
<artifactId>jose4j</artifactId>
|
||||
<version>0.7.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -1,20 +1,28 @@
|
||||
package org.owasp.webwolf.jwt;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.*;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import lombok.*;
|
||||
import org.jose4j.jws.AlgorithmIdentifiers;
|
||||
import org.jose4j.jws.JsonWebSignature;
|
||||
import org.jose4j.jwt.consumer.InvalidJwtException;
|
||||
import org.jose4j.jwt.consumer.JwtConsumer;
|
||||
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
|
||||
import org.jose4j.jwx.CompactSerializer;
|
||||
import org.jose4j.keys.HmacKey;
|
||||
import org.jose4j.lang.JoseException;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.springframework.util.Base64Utils.decodeFromUrlSafeString;
|
||||
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
public class JWTToken {
|
||||
|
||||
private static final Pattern jwtPattern = Pattern.compile("(.*)\\.(.*)\\.(.*)");
|
||||
@ -24,63 +32,70 @@ public class JWTToken {
|
||||
private String header;
|
||||
private String payload;
|
||||
private boolean signatureValid = true;
|
||||
private boolean validToken = true;
|
||||
|
||||
public void decode() {
|
||||
validToken = parseToken(encoded);
|
||||
parseToken(encoded.trim().replace(System.getProperty("line.separator"), ""));
|
||||
signatureValid = validateSignature(secretKey, encoded);
|
||||
}
|
||||
|
||||
public void encode() {
|
||||
var mapper = new ObjectMapper();
|
||||
try {
|
||||
if (StringUtils.hasText(secretKey)) {
|
||||
encoded = Jwts.builder()
|
||||
.signWith(SignatureAlgorithm.HS256, Base64Utils.encodeToUrlSafeString(secretKey.getBytes()))
|
||||
.setClaims(mapper.readValue(payload, Map.class))
|
||||
.setHeader(mapper.readValue(header, Map.class))
|
||||
.compact();
|
||||
} else {
|
||||
encoded = Jwts.builder()
|
||||
.setClaims(mapper.readValue(payload, Map.class))
|
||||
.setHeader(mapper.readValue(header, Map.class))
|
||||
.compact();
|
||||
JsonWebSignature jws = new JsonWebSignature();
|
||||
jws.setPayload(payload);
|
||||
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
|
||||
jws.setDoKeyValidation(false);
|
||||
if (StringUtils.hasText(secretKey)) {
|
||||
jws.setKey(new HmacKey(secretKey.getBytes(UTF_8)));
|
||||
try {
|
||||
encoded = jws.getCompactSerialization();
|
||||
signatureValid = true;
|
||||
} catch (JoseException e) {
|
||||
header = "";
|
||||
payload = "";
|
||||
}
|
||||
validToken = true;
|
||||
} catch (JsonProcessingException e) {
|
||||
validToken = false;
|
||||
signatureValid = false;
|
||||
} else {
|
||||
var encodedHeader = jws.getHeaders().getEncodedHeader();
|
||||
var encodedPayload = jws.getEncodedPayload();
|
||||
encoded = CompactSerializer.serialize(new String[]{encodedHeader, encodedPayload});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean parseToken(String jwt) {
|
||||
var matcher = jwtPattern.matcher(jwt);
|
||||
var mapper = new ObjectMapper().writerWithDefaultPrettyPrinter();
|
||||
var mapper = new ObjectMapper();
|
||||
|
||||
if (matcher.matches()) {
|
||||
try {
|
||||
Jwt<Header, Claims> headerClaimsJwt = Jwts.parser().parseClaimsJwt(matcher.group(1) + "." + matcher.group(2) + ".");
|
||||
this.header = mapper.writeValueAsString(headerClaimsJwt.getHeader());
|
||||
this.payload = mapper.writeValueAsString(headerClaimsJwt.getBody());
|
||||
var prettyPrint = mapper.writerWithDefaultPrettyPrinter();
|
||||
this.header = prettyPrint.writeValueAsString(mapper.readValue(decodeFromUrlSafeString(matcher.group(1)), Map.class));
|
||||
this.payload = prettyPrint.writeValueAsString(mapper.readValue(decodeFromUrlSafeString(matcher.group(2)), Map.class));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
this.header = mapper.writeValueAsString(new String(Base64Utils.decodeFromUrlSafeString(matcher.group(1))));
|
||||
this.payload = mapper.writeValueAsString(new String(Base64Utils.decodeFromUrlSafeString(matcher.group(2))));
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
this.header = new String(decodeFromUrlSafeString(matcher.group(1)));
|
||||
this.payload = new String(decodeFromUrlSafeString(matcher.group(2)));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
this.header = "error";
|
||||
this.payload = "error";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean validateSignature(String secretKey, String jwt) {
|
||||
try {
|
||||
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
if (StringUtils.hasText(secretKey)) {
|
||||
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
|
||||
.setSkipAllValidators()
|
||||
.setVerificationKey(new HmacKey(secretKey.getBytes(UTF_8)))
|
||||
.setRelaxVerificationKeyValidation()
|
||||
.build();
|
||||
try {
|
||||
jwtConsumer.processToClaims(jwt);
|
||||
return true;
|
||||
} catch (InvalidJwtException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,10 +8,8 @@ $(document).ready(() => {
|
||||
url: '/WebWolf/jwt/decode',
|
||||
data: JSON.stringify({encoded: token, secretKey: secretKey}),
|
||||
success: function (data) {
|
||||
if (data.validToken) {
|
||||
$('#tokenHeader').val(data.header);
|
||||
$('#tokenPayload').val(data.payload);
|
||||
}
|
||||
$('#tokenHeader').val(data.header);
|
||||
$('#tokenPayload').val(data.payload);
|
||||
updateSignature(data);
|
||||
},
|
||||
contentType: "application/json",
|
||||
@ -69,11 +67,10 @@ function parseJson(text) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function updateSignature(data) {
|
||||
if (data.signatureValid) {
|
||||
$('#signatureValid').val("Signature valid");
|
||||
$('#signatureValid').html("Signature valid");
|
||||
} else {
|
||||
$('#signatureValid').val("Signature invalid");
|
||||
$('#signatureValid').html("Signature invalid");
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
<form th:action="@{/login}" method="post">
|
||||
<fieldset>
|
||||
<h2>Sign in</h2>
|
||||
Use your WebGoat account.
|
||||
<br/>
|
||||
<div th:if="${param.error}">
|
||||
<div class="alert alert-danger">
|
||||
@ -31,11 +30,11 @@
|
||||
|
||||
<div class="form-group">
|
||||
<input type="text" name="username" id="username" class="form-control input-lg"
|
||||
placeholder="UserName" required="true" autofocus="true"/>
|
||||
placeholder="Username WebGoat" required="true" autofocus="true"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" name="password" id="password" class="form-control input-lg"
|
||||
placeholder="Password" required="true"/>
|
||||
placeholder="Password WebGoat" required="true"/>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -0,0 +1,81 @@
|
||||
package org.owasp.webwolf.jwt;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class JWTTokenTest {
|
||||
|
||||
@Test
|
||||
void encodeCorrectTokenWithoutSignature() {
|
||||
var headers = Map.of("alg", "HS256", "typ", "JWT");
|
||||
var payload = Map.of("test", "test");
|
||||
var token = JWTToken.builder().header(toString(headers)).payload(toString(payload)).build();
|
||||
|
||||
token.encode();
|
||||
|
||||
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeCorrectTokenWithSignature() {
|
||||
var headers = Map.of("alg", "HS256", "typ", "JWT");
|
||||
var payload = Map.of("test", "test");
|
||||
var token = JWTToken.builder()
|
||||
.header(toString(headers))
|
||||
.payload(toString(payload))
|
||||
.secretKey("test")
|
||||
.build();
|
||||
|
||||
token.encode();
|
||||
|
||||
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4");
|
||||
}
|
||||
|
||||
@Test
|
||||
void encodeTokenWithNonJsonInput() {
|
||||
var token = JWTToken.builder()
|
||||
.header("aaa")
|
||||
.payload("bbb")
|
||||
.secretKey("test")
|
||||
.build();
|
||||
|
||||
token.encode();
|
||||
|
||||
assertThat(token.getEncoded()).isEqualTo("eyJhbGciOiJIUzI1NiJ9.YmJi.VAcRegquayARuahZZ1ednXpbAyv7KEFnyjNJlxLNX0I");
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeValidSignedToken() {
|
||||
var token = JWTToken.builder()
|
||||
.encoded("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4")
|
||||
.secretKey("test")
|
||||
.build();
|
||||
|
||||
token.decode();
|
||||
|
||||
assertThat(token.getHeader()).contains("\"alg\" : \"HS256\"");
|
||||
assertThat(token.isSignatureValid()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodeInvalidSignedToken() {
|
||||
var token = JWTToken.builder().encoded("eyJhbGciOiJIUzI1NiJ9.eyJ0ZXsdfdfsaasfddfasN0IjoidGVzdCJ9.KOobRHDYyaesV_doOk11XXGKSONwzllraAaqqM4VFE4").build();
|
||||
|
||||
token.decode();
|
||||
|
||||
assertThat(token.getHeader()).contains("\"alg\":\"HS256\"");
|
||||
assertThat(token.getPayload()).contains("{\"te");
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String toString(Map<String, String> map) {
|
||||
var mapper = new ObjectMapper();
|
||||
return mapper.writeValueAsString(map);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user