Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvements #255

Merged
merged 17 commits into from
Jan 29, 2019
Merged

Performance improvements #255

merged 17 commits into from
Jan 29, 2019

Conversation

skjolber
Copy link
Contributor

@skjolber skjolber commented May 30, 2018

Changes from #194 plus various minor improvements.

  • crypto helper is more application-specific to JWT in taking in header and payload as different arguments.
  • passing ObjectReader around where it might be used
  • directly generate json in PayloadSerializer
  • default HashMap sizes

Stats:
Current:
Benchmark Mode Cnt Score Error Units
JavaJwtFromAuth0Bench.claim thrpt 5 14174 ± 297,734 ops/s
JavaJwtFromAuth0Bench.parse thrpt 5 63936 ± 1605,433 ops/s
JavaJwtFromAuth0Bench.validate thrpt 5 14085 ± 189,359 ops/s

Optimized:
Benchmark Mode Cnt Score Error Units
JavaJwtFromAuth0Bench.claim thrpt 5 17962 ± 1126,980 ops/s
JavaJwtFromAuth0Bench.parse thrpt 5 311501 ± 12945,965 ops/s
JavaJwtFromAuth0Bench.validate thrpt 5 18152 ± 755,550 ops/s

See also

https://github.com/skjolber/java-jwt-benchmark

@skjolber
Copy link
Contributor Author

skjolber commented Jun 12, 2018

@lbalmaceda - I am looking at increasing code coverage so the checks pass, would it be acceptable to remove the Algorithm.sign(byte[]) method, or must it first be deprectated?

Edit: Went ahead and deleted unused code.

@skjolber skjolber force-pushed the optimize branch 2 times, most recently from f77fe56 to d8b1cd7 Compare June 26, 2018 16:32
@skjolber
Copy link
Contributor Author

@lbalmaceda any hope for merging this? Also the build fail on java 7 due to some connection reset and code coverage seems to be not working either(?).

Copy link
Contributor

@lbalmaceda lbalmaceda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm slowly going through issues and PRs. Thanks for your participation so far. I've left a few comments! cheers


boolean verifySignatureFor(String algorithm, byte[] secretBytes, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
return MessageDigest.isEqual(createSignatureFor(algorithm, secretBytes, contentBytes), signatureBytes);
boolean verifySignatureFor(String algorithm, byte[] secretBytes, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, this method could be safely deleted and you could use the byte[] header and payload variant instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below comment

}

boolean verifySignatureFor(String algorithm, PublicKey publicKey, byte[] contentBytes, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
boolean verifySignatureFor(String algorithm, PublicKey publicKey, String header, String payload, byte[] signatureBytes) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as my comment above, unless this methods are called from many places passing String and as a convenience we want to wrap that here. What are your thoughts?

Copy link
Contributor Author

@skjolber skjolber Jan 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the header/payload from DecodedJWT are strings, so it is good to pass them directly so to the helper. Also there is quite a lot of unit test calls to this convenice method. I guess keeping them around is fair.

* @return the signature in a base64 encoded array of bytes
* @throws SignatureGenerationException if the Key is invalid.
*/
public abstract byte[] sign(byte[] contentBytes) throws SignatureGenerationException;
public abstract byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change (affecting implementations as well)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Basically this narrows the use for Algorithm to just signing tokens, which is in line with its own class description (in the javadoc).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree that this is an improvement. But it will break customers who are implementing their own Algorithms.. I can't see many customers doing that but some feature requests have been opened in the past asking us to extend the algorithms we support. Let me discuss this internally. Maybe with a good readme note and proper changelog notes we can accept it. I can't promise anything.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re above: No breaking change allowed (and no major plans for the time being). We need to find an alternative to this. Normally I'd say call the new method from the old one and deprecate it, but in this case there's no way for you to figure out at what point of the contentBytes array is the half of it in order to separate it into header and payload bytes. What if you add the new sign signature and keep both methods side-to-side? I guess the new one can call the old one by merging both header and payload bytes with a "." in-between, so not to repeat the sign method's body code. If not you will have to rollback the changes for this class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a default implementation of the new method (sign with header and payload) in Algorithm, but overridden that in the known subclasses. This should ensure backwards compatibility for those implementing their own algorithms, at the same time as speeding things up for the rest of us.

CryptoHelper has now both the new and the old methods, but they're all quite small.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lbalmaceda If this solution is acceptable, I'll add a few of the old unit tests for code coverage (see failing build checks).

} catch (IOException e) {
throw exceptionForInvalidJson(json);
}
private static JWTDecodeException decodeException() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be no reason to make this 2 methods static. Does this have any impact on the performance?

Copy link
Contributor Author

@skjolber skjolber Jan 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer static to non-static if there is no access to fields; it is just simpler and in this case is there no kind of subclassing going on either.

}

@Override
public Map<String, Claim> getClaims() {
Map<String, Claim> claims = new HashMap<>();
Map<String, Claim> claims = new HashMap<>(tree.size() * 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the rationale to pick this default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crude estimate of necessary capacity to avoid rehashing and collision.

https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap

break;
default:
gen.writeFieldName(e.getKey());
if (e.getValue() instanceof Date) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this default case could be merged with the 3 known claims above (which are dates as well), or rephrasing, this could be deleted:

case PublicClaims.EXPIRES_AT:
case PublicClaims.ISSUED_AT:
case PublicClaims.NOT_BEFORE:
   gen.writeFieldName(e.getKey());
   gen.writeNumber(dateToSeconds((Date) e.getValue()));
   break;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted according to your comment.

@skjolber
Copy link
Contributor Author

@lbalmaceda see updates / comments, sorry for slow reaction on QA.

@@ -51,7 +54,7 @@ static Verification init(Algorithm algorithm) throws IllegalArgumentException {
}

this.algorithm = algorithm;
this.claims = new HashMap<>();
this.claims = new HashMap<>(48);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No magic numbers please. Can we move it into a constant and give it a proper name/description?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, 16 (the default) is probably okey here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I've never seen a token with that many claims 😆

* @return the signature in a base64 encoded array of bytes
* @throws SignatureGenerationException if the Key is invalid.
*/
public abstract byte[] sign(byte[] contentBytes) throws SignatureGenerationException;
public abstract byte[] sign(byte[] headerBytes, byte[] payloadBytes) throws SignatureGenerationException;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re above: No breaking change allowed (and no major plans for the time being). We need to find an alternative to this. Normally I'd say call the new method from the old one and deprecate it, but in this case there's no way for you to figure out at what point of the contentBytes array is the half of it in order to separate it into header and payload bytes. What if you add the new sign signature and keep both methods side-to-side? I guess the new one can call the old one by merging both header and payload bytes with a "." in-between, so not to repeat the sign method's body code. If not you will have to rollback the changes for this class.

Copy link
Contributor

@lbalmaceda lbalmaceda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code is fine so yes, go ahead and add any test you consider appropriate to keep the coverage. The only comment is around the old vs new methods and the deprecated annotation. Some of them are missing the annotation and in particular for the CryptoHelper class, even if not public, please add the reason why it's deprecated (e.g. "please use this other alternative method ...") or at least add a comment like you did in the new sign implementation standing that the "duplicated code" in the new methods is until we decide to remove the other signatures. Just to keep track of this on the future. The rest is fine. Once checks pass I'll merge it. Thanks!

@skjolber
Copy link
Contributor Author

@lbalmaceda Added a few more unit tests, Javadoc in CryptoHelper, @deprecated to the old sign(..) methods, some cleanups.

@lbalmaceda
Copy link
Contributor

Thanks a lot for sticking with this 👍 👍

@lbalmaceda lbalmaceda merged commit 8ca949e into auth0:master Jan 29, 2019
@lbalmaceda lbalmaceda modified the milestones: v3-Next, 3.7.0 Jan 29, 2019
@skjolber
Copy link
Contributor Author

@lbalmaceda Thank you for your patience 👍 🥇

@skjolber
Copy link
Contributor Author

skjolber commented Feb 1, 2019

@lbalmaceda
Copy link
Contributor

@skjolber I forgot to ask you that the other day. Thanks! 💥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants