Quarkus JWT token

csm·2021년 10월 15일

quarkus

목록 보기
2/4

개요

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

테스트 내용

postman 이용


작업 내용

키생성

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!

Quarkus 프로젝트 생성

https://quarkus.io/get-started/ 참조

gradle

 	//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'

properties

mp.jwt.verify.publickey.location=/public_key.pem
mp.jwt.verify.issuer=test
quarkus.smallrye-jwt.enabled=true

JAVA

AuthContRoller

@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(); 
    }
    
}

AuthService

@Service
public class AuthService {

	@Autowired
	JwtCmd jwtCmd;

	public String genToken() {
		return jwtCmd.generateToken();
	}

}

AuthDto 생성

@Data
public class AuthDto {
	String accessToken;
	String refreshToken;
}

Role 생성

public final class Roles {
    private Roles() { }
    public static final String USER = "User";
    public static final String SERVICE = "Service";
    public static final String ADMIN = "Admin";
}

JwtCmd 생성

@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();
    }
    
}
profile
개발자, 아키텍트

0개의 댓글