Use of JWT(JSON Web Token)
Preface
This post is correct shiro uses JWT Complete complementary extensions to provide full sample code for detailed use in JWT.
If there is a discrepancy between your understanding of RSA and your own reading of this blog, please read the part of this blog explaining RSA. If there are other inconsistencies after reading, you are welcome to leave a message for discussion.
Role and basic formatting of JWT
Just read this question and answer , which explains in detail what JWT is, what JWT can be used for, the format of JWT, how JWT works, why JWT should be used, and so on.
Basic Format
In fact, after reading the above document, you will understand its basic format. To put it simply here, JWT is divided into three sections
xxxxx.yyyyy.zzzzz
Represents the Header, which contains the signature algorithm used, and the type of token
{ "alg": "HS256", "typ": "JWT" }
Payload,Payload mainly contains token-related information such as signing time, expiration time, and some customized information. A more complete example of payload is as follows
{ "sub": "1000", "aud": [ "https://app-one.com", "https://app-two.com" ], "nbf": 1638357246, "iss": "http://localhost:18080", "exp": 1638357846, "iat": 1638357246, "jti": "17fcfe5e-f705-481d-bf55-23368988f8d6", "scope": "read write" }
- iss can be understood as where token s are generated
- exp expiration time
- iat can be interpreted as generating token time
- nbf means token must be no earlier than this time
- aud can be understood as the scope of this token
- jti JWT ID
- subject is stored information, such as userId
- scope is our custom field, if you want to set other fields basic actual scene settings
See full explanation Here
Signature is the signature of the JWT. Encrypt the encryption algorithm used by Header and Payload+ after base64 encoding
The final output is a base64-encoded string, separated by three dots (...), so the JWT is decoded by Base64 to see the corresponding payload content. So sensitive information isn't recommended in payload.
Why do I need to sign?
In fact, the second red part of the above screenshot has already been explained. Signatures are added to prevent tampering with JWT and, if encrypted using RSA private keys, the correct decrypted content can only be obtained by decrypting using RSA public keys on the server side. It is also important to use this to prove that the current JWT signature has not been tampered with or is currently signed on the server side (similarly, for HMAC, HMAC secret s are only on the server side, and the results of the calculation agree that the JWT is indeed from its own server).
JWT-related Libraries
The JWT libraries used more in Java are nimbus-jose-jwt and io.jsonwebtoken. Can be used in Complete list of JWT Libraries More libraries supported by JWT are found in.
stay Using JWT in Shiro In this post, use the io.jsonwebtoken library. It simply pastes some code without making a detailed distinction between the class library choices.
This article describes using the nimbus-jose-jwt Library
Selection of nimbus-jose-jwt and jsonwebtoken
The stackoverflow has already given the answer, and here are two reference links that you can use to make your own decisions
In short, nimbus-jose-jwt supports more features
nimbus-jose-jwt use
nimbus-jose-jwt Official Web There are a number of examples from which you can quickly get started using JWT. In the official website, nimbus is basically divided into two parts: JWS and JWE for the use of JWT. This paper will mainly explain JWS,JWE as a guide, readers can explore the practice themselves.
JWS
JSON Web Signature
Sign the JWT. The role of signing content has been described previously
- Prevent tampering with JWT content and ensure its integrity
- You can verify that the JWT is actually signed from the current server
Note: In fact, signing is not only used in JWT, but also in the practical application of SSL. You can learn more if you are interested
There are HMAC, RSA, EC, etc. on the encryption algorithm of the signature. For the usage scenario of each encryption algorithm, please move on Official Documents , which explains in detail some of the objectives of encrypting data and the corresponding goals and scenarios for various encryption algorithms
HMAC encryption algorithm
Scenarios using HMAC
For example, the second red-colored part of a message's verification code explains its best use when data is sent outside and eventually applied for identification. Its core idea is to ensure that the data is not tampered with and that the data is generated by us.
Here's a simple springboot application for demonstration
Generate jwt using HMAC encryption algorithm
@Configuration @Component public class SignerAndVerifierConfiguration { private static final String sharedSecret = "31611159e7e6ff7843ea4627745e89225fc866621cfcfdbd40871af4413747cc"; @Bean(name = "HmacSigner") @SneakyThrows public JWSSigner generateHmacJwsSigner() { SecureRandom random = new SecureRandom(); random.nextBytes(sharedSecret.getBytes()); return new MACSigner(sharedSecret); } @Bean(name = "HmacVerifier") @SneakyThrows public JWSVerifier getHmacJwsVerifier() { SecureRandom random = new SecureRandom(); random.nextBytes(sharedSecret.getBytes()); return new MACVerifier(sharedSecret); } }
Configure HMAC's signer-JWSSigner and verifier-JWSVerifier first, where HMAC's secret uses a random string
Next, we begin to construct the JWT, starting with our payload, which uses the ==JWTClaimsSet.Builder()==method
@Component public class JWTClaimsSetFactory { public JWTClaimsSet buildJWTClaimsSet(String userId) { Calendar signTime = Calendar.getInstance(); Date signTimeTime = signTime.getTime(); signTime.add(Calendar.MINUTE, 10); Date expireTime = signTime.getTime(); return new JWTClaimsSet.Builder() .issuer("http://localhost:18080") .subject(userId) .audience(Arrays.asList("https://app-one.com", "https://app-two.com")) .expirationTime(expireTime) .notBeforeTime(signTimeTime) .issueTime(signTimeTime) .jwtID(UUID.randomUUID().toString()) .claim("scope", "read write") .build(); } }
Once you get payload, add a Header and use signer to encrypt. This uses SignedJWT, which means the JWS we use
@RestController @RequestMapping("generate") @Log public class GenerateTokenController { @Autowired @Qualifier("HmacSigner") private JWSSigner hmacSigner; @Autowired private JWTClaimsSetFactory jwtClaimsSetFactory; @GetMapping("hmac") @SneakyThrows public String generateHMACToken() { // Incoming header and payload SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build(), jwtClaimsSetFactory.buildJWTClaimsSet("ADMIN")); // Sign signedJWT.sign(hmacSigner); String result = signedJWT.serialize(); log.info("HMAC token is: \n" + result); return result; } }
Resolving jwt of HMAC encryption algorithm
@RestController @RequestMapping("verify") public class VerifyTokenController { @Autowired @Qualifier("HmacVerifier") private JWSVerifier hmacVerifier; @GetMapping("hmac") @SneakyThrows public boolean verifyHMACToken(@RequestHeader("Authorization") String token) { SignedJWT parse = SignedJWT.parse(token); if (!parse.verify(hmacVerifier)) { throw new RuntimeException("invalid token"); } verifyClaimsSet(parse.getJWTClaimsSet()); return true; } /** * All validations are done here. * @param jwtClaimsSet */ private void verifyClaimsSet(final JWTClaimsSet jwtClaimsSet) { boolean result = false; if (Calendar.getInstance().getTime().before(jwtClaimsSet.getExpirationTime())) { result = true; } if (!result) { throw new RuntimeException("token expired"); } } }
RSA Encryption Algorithm
Scenarios using RSA
For example, when OAuth2.0 servers send access token s. When used, the public and private keys need to be generated, then signed by the private key and verified by the public key.
Online generation of RSA public and private keys
Use during presentations Online RAS Generation Web site to generate RSA public and private keys. In practice, openSSL can be used to generate on the server
Put the generated public and private keys into the publish-key.pem and private-key.pem files, respectively. If not, create a new one.
Generate jwt using RSA encryption algorithm
Like HMAC, we also configure signer and verifier
@Configuration @Component public class SignerAndVerifierConfiguration { private static final String sharedSecret = "31611159e7e6ff7843ea4627745e89225fc866621cfcfdbd40871af4413747cc"; @Bean(name = "RsaSigner") @SneakyThrows public JWSSigner generateRsaJwsSigner(){ // Read private key content String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString("rsa/private-key.pem"); RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey); return new RSASSASigner(rsaKey.toRSAPrivateKey()); } @Bean(name = "RsaVerifier") @SneakyThrows public JWSVerifier getRsaJWSVerifier() { // Read Public Key Content String pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString("rsa/publish-key.pem"); RSAKey rsaPublicKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPublicKey); return new RSASSAVerifier(rsaPublicKey); } }
Here we put the two files we just built under the resources file of the project and read them when initializing signer and verifier
@RestController @RequestMapping("generate") @Log public class GenerateTokenController { @Autowired @Qualifier("RsaSigner") private JWSSigner rsaSigner; @Autowired private JWTClaimsSetFactory jwtClaimsSetFactory; @GetMapping("{userId}") @SneakyThrows public String generateRSAToken(@PathVariable String userId) { // header Select RS256 here SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).type(JOSEObjectType.JWT).build(), jwtClaimsSetFactory.buildJWTClaimsSet(userId)); // autograph signedJWT.sign(rsaSigner); String result = signedJWT.serialize(); log.info("token is: \n" + result); return result; } }
Construct the payload section here, as in the example, without giving duplicate code
Resolving jwt of RAS encryption algorithm
@RestController @RequestMapping("verify") public class VerifyTokenController { @Autowired @Qualifier("RsaVerifier") private JWSVerifier rsaVerifier; @GetMapping @SneakyThrows public boolean verifyRSAToken(@RequestHeader("Authorization") String token) { SignedJWT parse = SignedJWT.parse(token); if (!parse.verify(rsaVerifier)) { throw new RuntimeException("invalid token"); } verifyClaimsSet(parse.getJWTClaimsSet()); return true; } }
JWE
JWS uses signatures to ensure that data content is not tampered with, but the pauload content is still visible after the final base64 decoding. In practice, we also have a userId in our JWT. If it comes with OAuth 2.0, there may be scope s and so on, but in fact these contents will have little impact on us after they are decoded.
However, if you want to encrypt the contents of payload so that the data cannot be decoded, then you need a JWE-JSON Web Encryption.
Here's another reminder:
- JWS is to sign content without encrypting it
- JWE encrypts but does not sign content
Encrypt content using RSA
@Component @Configuration public class EncryptAndDecryptConfiguration { @Bean @SneakyThrows public JWEEncrypter generateRsaJweEncrypter() { String pemEncodedRSAPublicKey = PEMKeyUtils.readKeyAsString("rsa/publish-key.pem"); RSAKey rsaPublicKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPublicKey); return new RSAEncrypter(rsaPublicKey); } @Bean @SneakyThrows public JWEDecrypter getRsaJweDecrypter() { String pemEncodedRSAPrivateKey = PEMKeyUtils.readKeyAsString("rsa/private-key.pem"); RSAKey rsaKey = (RSAKey) JWK.parseFromPEMEncodedObjects(pemEncodedRSAPrivateKey); return new RSADecrypter(rsaKey); } }
Configure JWEEncrypter and JWEDecrypter
@RestController @RequestMapping("secret") @Log public class SecretController { @Autowired private JWEEncrypter jweEncrypter; @Autowired private JWTClaimsSetFactory jwtClaimsSetFactory; @GetMapping("{userId}") @SneakyThrows public String secretToken(@PathVariable final String userId) { // Set up JWE header JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM); EncryptedJWT encryptedJWT = new EncryptedJWT(header, jwtClaimsSetFactory.buildJWTClaimsSet(userId)); // Encrypt with publishKey encryptedJWT.encrypt(jweEncrypter); String result = encryptedJWT.serialize(); log.info("encrypt token is: \n" + result); return result; } }
Decrypt the base64 string of the generated JWT and find that the payload content is not visible
Decrypt
@GetMapping("decrypt") @SneakyThrows public void decryptRSASecretToken(@RequestHeader("Authorization") String token) { EncryptedJWT encryptedJWT = EncryptedJWT.parse(token); // Decrypt using private encryptedJWT.decrypt(jweDecrypter); }
JWS + JWE
After the previous introduction, some people may wonder if I can sign and encrypt, of course. Here you can refer to the official nimbus About Example of using signature with encryption . Don't explain much here
The order of official suggestions is to sign first and then encrypt. For why, look at the first line of the official example above to explain
Some explanations of RSA
Those who know RSA in general may be confused with the way I use RSA in JWS. Specific doubts may be as follows:
-
RSA asymmetric encryption is both a pair of RSA public and private keys. Why do only the server side have public and private keys here?
A: The server that sends token down corresponds to the browser. The browser itself does not need to decrypt the token, just put the next request in the request header. So here the browser does not actually need to maintain its own set of RSA public and private keys
-
Why use RSA private key encryption, public key decryption. Aren't both public key encryption and private key decryption?
A: Scenarios for public key encryption and private key decryption are for content encryption. The scenario we use with JWS is signing content. So in the use of RSA practice, we need to look at our needs. Simple explanation is as follows
The first usage is public key encryption and private key decryption. For encryption and decryption
Second usage: private key signature, public key verification. For signatureIt's a bit confusing. Don't try to remember it. Make a summary:
All you have to do is:
Since it is encrypted, you certainly don't want others to know my message, so only I can decrypt it, so it can be concluded that the public key is responsible for encryption and the private key is responsible for decryption.
Since it's a signature, you certainly don't want anyone to pass me off as a message. Only I can publish this signature, so you can get the private key to sign and the public key to verify.In the same way, I'm saying something different:
The private key and the public key are a pair. Anyone can encrypt and decrypt it, but who encrypts and decrypts it depends on the scenario:
The first scenario is signing, encrypting with a private key, and decrypting the public key, which allows all public key owners to verify the identity of the private key owner and prevents content published by the private key owner from being tampered with. However, it is not used to guarantee that content is not acquired by others.
The second scenario is encryption, encryption with a public key, and decryption with a private key, which is used to publish information to the public key owner that may have been tampered with but cannot be obtained by others.For example, encryption scenarios:
If A wants to send B a secure and confidential data, then A and B should each have a private key. A first encrypts the data with B's public key, then encrypts the encrypted data with its own private key. Finally, it is sent to B, which ensures that the content will not be read and tampered with.This is a very simple and clear explanation of how public and private keys work together in different scenarios. This is from This Blogger Thank you very much for your explanation
-
In OAtuh2.0, is the public or private key used when Authorization Server issues token s?
A: In fact, the above examples and the second question have been explained. For token signatures, we need to use a private key signature to ensure that the signature comes from Authorization Server. At the same time, in OAuth2.0, the server is responsible for issuing tokens. Judging whether this token is legal can actually be transferred to other servers, with less pressure on the authentication server. Then you just need to copy the corresponding public key to the server cluster that resolves the token. This can also be seen where the RSA algorithm implements JWS where the truncation is red
-
Recommendations for digital signatures and content encryption?
A: You can read some explanations on reliable websites, such as Ruan Yifeng's website, which should be covered by relevant content, although I haven't looked for it yet. Or use Google more, use less or don't use Baidu