-
Notifications
You must be signed in to change notification settings - Fork 927
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
Conversation
@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. |
f77fe56
to
d8b1cd7
Compare
@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(?). |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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.
@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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
…e commons Charsets
@lbalmaceda Added a few more unit tests, Javadoc in CryptoHelper, @deprecated to the old sign(..) methods, some cleanups. |
Thanks a lot for sticking with this 👍 👍 |
@lbalmaceda Thank you for your patience 👍 🥇 |
@skjolber I forgot to ask you that the other day. Thanks! 💥 |
Changes from #194 plus various minor improvements.
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