Skip to content

Commit

Permalink
[kiworkshop#106] ad JwtConverter.
Browse files Browse the repository at this point in the history
  • Loading branch information
myeongjae-kim committed Aug 10, 2020
1 parent c6d9093 commit 011f3c4
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 0 deletions.
2 changes: 2 additions & 0 deletions community-backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ ext {
// springCloudVersion = "Greenwich.RELEASE" // TODO: replace
springCloudVersion = "Greenwich.SR1"
springSecurityOAuth2AutoConfigureVersion = "2.1.9.RELEASE"

jwtVersion = '3.10.3'
}

task dockerComposeUp(type: Exec) {
Expand Down
10 changes: 10 additions & 0 deletions community-backend/core/core-jwt-converter/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
plugins {
id 'java-library'
}

dependencies {
api project(':module:module-object-mapper-async')

implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation "com.auth0:java-jwt:${jwtVersion}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.kiworkshop.community.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.JWTVerifier;
import lombok.Builder;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;

public class JwtConverter<T> {
private static final int JWT_SECRET_MIN_LENGTH = 32;

private final Class<T> clazz;
private final ObjectMapperAsync objectMapperAsync;

private final Algorithm algorithm;
private final JWTVerifier jwtVerifier;

@Builder
private JwtConverter(Class<T> clazz, String jwtSecret, ObjectMapperAsync objectMapperAsync) {
Assert.isTrue(jwtSecret.getBytes().length >= JWT_SECRET_MIN_LENGTH,
"jwtSecret's length must be large or equal than " + JWT_SECRET_MIN_LENGTH);

this.clazz = clazz;
this.objectMapperAsync = objectMapperAsync;
this.algorithm = Algorithm.HMAC256(jwtSecret);
this.jwtVerifier = JWT.require(this.algorithm).build();
}

public Mono<String> encode(T subject) {
return objectMapperAsync.writeValueAsString(subject)
.map(subjectAlter -> JWT.create().withSubject(subjectAlter).sign(algorithm));
}

public Mono<T> decode(String jwt) {
String serializedSubject = jwtVerifier.verify(jwt).getSubject();

return objectMapperAsync.readValue(serializedSubject, clazz);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.kiworkshop.community.util;

import static org.assertj.core.api.BDDAssertions.then;
import static org.assertj.core.api.BDDAssertions.thenThrownBy;

import java.time.ZonedDateTime;
import java.util.Objects;
import lombok.Getter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;

@ExtendWith(MockitoExtension.class)
class JwtConverterTest {
private static final ZonedDateTime now = ZonedDateTime.now();
private static final String jwtSecret = "secretKeyEqualOrLargerThan32byte";

@Getter
static class Sample {
String field = "field";
ZonedDateTime zonedDateTime = now;
}

@Test
void encodeAndDecode_ValidInput_ValidOutput() {
// given
JwtConverter<Sample> jwtService = JwtConverter.<Sample>builder()
.clazz(Sample.class)
.jwtSecret(jwtSecret)
.objectMapperAsync(new ObjectMapperAsync(
new Jackson2JsonEncoder(),
new Jackson2JsonDecoder(),
new DefaultDataBufferFactory()))
.build();

Sample sample = new Sample();

// when
String jwt = jwtService.encode(sample).block();
Sample result = Objects.requireNonNull(jwtService.decode(jwt).block());

// then
then(result.field).isEqualTo("field");
then(result.zonedDateTime).isEqualTo(result.zonedDateTime);
}

@Test
void construct_shortJwtSecret_Exception() {
thenThrownBy(() -> JwtConverter.<Sample>builder()
.clazz(Sample.class)
.jwtSecret("shortJwtSecret")
.objectMapperAsync(new ObjectMapperAsync(
new Jackson2JsonEncoder(),
new Jackson2JsonDecoder(),
new DefaultDataBufferFactory()))
.build()
)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("jwtSecret's length must be large or equal than 32");
}
}
1 change: 1 addition & 0 deletions community-backend/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ include 'in-system-available:common-web'

include 'module:module-validation'
include 'module:module-object-mapper-async'
include 'core:core-jwt-converter'

0 comments on commit 011f3c4

Please sign in to comment.