이번 회사에서 신규 백엔드 프로젝트를 진행하며, 차세대의 전신을 만들고자 Quarkus(java)를 이용한 jwt 보안 적용 기록을 정리 하였다. 프로토타입이고 차후 더 정리해 나갈 예정임.


openssl genrsa -out private.pem 4096
openssl rsa -in private.pem -pubout -outform PEM -out public_key.pem
openssl pkcs8 -topk8 -inform PEM -in private.pem -out private_key.pem -nocryp!
https://quarkus.io/get-started/ 참조
//jwt
compile 'io.quarkus:quarkus-smallrye-jwt'
compile 'io.quarkus:quarkus-smallrye-jwt-build'
//rest client
compile 'io.quarkus:quarkus-rest-client'
compile 'io.quarkus:quarkus-rest-client-jackson'
mp.jwt.verify.publickey.location=/public_key.pem
mp.jwt.verify.issuer=test
quarkus.smallrye-jwt.enabled=true
@Path("/auth")
@RequestScoped
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class AuthController {
@Inject
JsonWebToken jwt;
@Autowired
AuthService authService;
@GET
@Path("permit-all")
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public Response hello() {
return Response.ok().build();
}
@GET
@Path("roles-allowed")
@RolesAllowed({ "User", "Admin" })
@Produces(MediaType.TEXT_PLAIN)
public Response helloRolesAllowed(@Context SecurityContext ctx) {
return Response.ok().build();
}
@GET
@Path("token")
@PermitAll
@Consumes(MediaType.APPLICATION_JSON)
public Response token() {
AuthDto authDto = new AuthDto();
authDto.setAccessToken(authService.genToken());
return Response.ok(authDto).build();
}
}
@Service
public class AuthService {
@Autowired
JwtCmd jwtCmd;
public String genToken() {
return jwtCmd.generateToken();
}
}
@Data
public class AuthDto {
String accessToken;
String refreshToken;
}
public final class Roles {
private Roles() { }
public static final String USER = "User";
public static final String SERVICE = "Service";
public static final String ADMIN = "Admin";
}
@Service
@Log4j2
public class JwtCmd {
private final static String jwtSecretKey = "test!@#";
private final static String jwtSecreKeyLocation = "/private_key.pem";
private Key getHmacKey() {
Key key = null;
try {
key = new HmacKey(jwtSecretKey.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
}
return key;
}
private Key getHmacKey(String keyString) {
Key key = null;
try {
key = new HmacKey(keyString.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
}
return key;
}
public String generateToken(String jwtkey, float expireMin, String id, String jwtIss, String jwtAudience ) {
JwtClaims claims = new JwtClaims();
claims.setExpirationTimeMinutesInTheFuture(expireMin);
claims.setSubject(id);
claims.setIssuer(jwtIss);
claims.setAudience(jwtAudience);
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
jws.setKey(getHmacKey(jwtkey));
String jwt = null;
try {
jwt = jws.getCompactSerialization();
} catch (JoseException e) {
log.warn("do not make JWT TOKEN e=", e);
}
return jwt;
}
public String generateToken(String jwtKey, String... roles) {
JwtClaims claims = new JwtClaims();
claims.setExpirationTimeMinutesInTheFuture(60);
claims.setSubject("test");
claims.setIssuer("test");
claims.setAudience("test");
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256);
jws.setKey(getHmacKey(jwtKey));
jws.setDoKeyValidation(false); // relaxes the key length requirement
String jwt = null;
try {
jwt = jws.getCompactSerialization();
} catch (JoseException e) {
log.warn("do not make JWT TOKEN e", e);
}
return jwt;
}
public String generateToken() {
JwtClaims claims = new JwtClaims();
claims.setExpirationTimeMinutesInTheFuture(5);
claims.setSubject("test");
claims.setIssuer("test");
claims.setAudience("test");
claims.setClaim(Claims.groups.name(), Arrays.asList("User"));
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
jws.setKey(readPrivateKey(jwtSecreKeyLocation));
jws.setHeader("typ", "JWT");
String jwt = null;
try {
jwt = jws.getCompactSerialization();
} catch (JoseException e) {
log.warn("do not make JWT TOKEN e", e);
}
return jwt;
}
public JwtClaims getPayload(String token, String jwtKey) {
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
.setExpectedIssuer("test")
.setExpectedAudience("test")
.setVerificationKey(getHmacKey(jwtKey))
.setRelaxVerificationKeyValidation() // relaxes key length requirement
.build();
JwtClaims processedClaims = null;
try {
processedClaims = jwtConsumer.processToClaims(token);
} catch (InvalidJwtException e) {
log.warn("do not parse JWT TOKEN e", e);
}
return processedClaims;
}
public JwtClaims getPayload(String token) {
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
.setExpectedIssuer("test")
.setExpectedAudience("test")
.setVerificationKey(getHmacKey())
.setRelaxVerificationKeyValidation() // relaxes key length requirement
.build();
JwtClaims processedClaims = null;
try {
processedClaims = jwtConsumer.processToClaims(token);
} catch (InvalidJwtException e) {
log.warn("do not parse JWT TOKEN e", e);
}
return processedClaims;
}
public String getSubject(String token) {
try {
return getPayload(token).getSubject();
} catch (MalformedClaimException e) {
log.warn("do not parse JWT TOKEN e", e);
return null;
}
}
public Boolean isTokenRefresh(String token) {
JwtClaims map = getPayload(token);
if (map == null)
return true;
NumericDate expiration = null;
try {
expiration = map.getExpirationTime();
} catch (MalformedClaimException e) {
}
if (expiration == null)
return true;
return expiration.isBefore(NumericDate.now());
}
public JwtClaims getAllClaimsFromToken(String token) {
return getPayload(token);
}
public NumericDate getExpirationDateFromToken(String token) {
JwtClaims map = getPayload(token);
if (map == null) return null;
try {
return map.getExpirationTime();
} catch (MalformedClaimException e) {
return null;
}
}
public Boolean isTokenExpired(String token) {
NumericDate expiration = getExpirationDateFromToken(token);
if (expiration == null) return true;
return expiration.isBefore(NumericDate.now());
}
public PrivateKey readPrivateKey(final String pemResName) {
InputStream contentIS = JwtCmd.class.getResourceAsStream(pemResName);
byte[] tmp = new byte[4096];
int length;
PrivateKey pk;
try {
length = contentIS.read(tmp);
pk = decodePrivateKey(new String(tmp, 0, length, "UTF-8"));
} catch (IOException e) {
log.warn("do not readPrivateKey JWT TOKEN e", e);
return null;
}
return pk;
}
public static PrivateKey decodePrivateKey(final String pemEncoded) {
byte[] encodedBytes = toEncodedBytes(pemEncoded);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encodedBytes);
KeyFactory kf;
PrivateKey pk;
try {
kf = KeyFactory.getInstance("RSA");
pk = kf.generatePrivate(keySpec);
} catch (InvalidKeySpecException e) {
log.warn("do not generatePrivate JWT TOKEN e", e);
return null;
} catch (NoSuchAlgorithmException e) {
log.warn("do not decodePrivateKey JWT TOKEN e", e);
return null;
}
return pk;
}
private static byte[] toEncodedBytes(final String pemEncoded) {
final String normalizedPem = removeBeginEnd(pemEncoded);
return Base64.getDecoder().decode(normalizedPem);
}
private static String removeBeginEnd(String pem) {
pem = pem.replaceAll("-----BEGIN (.*)-----", "");
pem = pem.replaceAll("-----END (.*)----", "");
pem = pem.replaceAll("\r\n", "");
pem = pem.replaceAll("\n", "");
return pem.trim();
}
}