SpringBoot e-Commerce project mall (35K + STAR) address: github.com/macrozheng/…

Abstract

The JWT library used to be JJWT. Although small enough to use, but some details of JWT encapsulation is not very good. Nimbus-jose-jwt is a new JWT library that supports both symmetric and asymmetric encryption algorithms.

Introduction to the

Nimbus-jose-jwt is the most popular JWT open source library, based on the Apache 2.0 open source protocol, and supports all standard signing (JWS) and encryption (JWE) algorithms.

JWT conceptual relationships

Here we need to understand the relationship between JWT, JWS, and JWE. JWT(JSON Web Token) refers to a specification that allows us to use JWT to deliver secure and reliable information between two organizations. JWS(JSON Web Signature) and JWE(JSON Web Encryption) are two different implementations of the JWT specification, and the most commonly used implementation is JWS.

use

Next we’ll look at the nimbus-Jose-JWT library, which uses both symmetric encryption (HMAC) and asymmetric encryption (RSA) algorithms to generate and parse JWT tokens.

Symmetric encryption (HMAC)

Symmetric encryption refers to the use of the same secret key for encryption and decryption. If your secret key does not want to be exposed to the decryptor, consider using asymmetric encryption.

  • In order to usenimbus-jose-jwtLibrary, first inpom.xmlAdd dependencies;
<! --JWT -->
<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>8.16</version>
</dependency>
Copy the code
  • createJwtTokenServiceImplAs the business class handled by JWT, add the basisHMACThe method by which the algorithm generates and parses JWT tokens can be foundnimbus-jose-jwtThe API for library manipulation of JWT is very easy to understand;
/** * Created by macro on 2020/6/22. */
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
    @Override
    public String generateTokenByHMAC(String payloadStr, String secret) throws JOSEException {
        // Create the JWS header and set the signature algorithm and type
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).
                type(JOSEObjectType.JWT)
                .build();
        // Encapsulates Payload information into Payload
        Payload payload = new Payload(payloadStr);
        // Create a JWS object
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        // Create an HMAC signature
        JWSSigner jwsSigner = new MACSigner(secret);
        / / signature
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }

    @Override
    public PayloadDto verifyTokenByHMAC(String token, String secret) throws ParseException, JOSEException {
        // Parse JWS objects from tokens
        JWSObject jwsObject = JWSObject.parse(token);
        // Create an HMAC validator
        JWSVerifier jwsVerifier = new MACVerifier(secret);
        if(! jwsObject.verify(jwsVerifier)) {throw new JwtInvalidException("Token signature is illegal!");
        }
        String payload = jwsObject.getPayload().toString();
        PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);
        if (payloadDto.getExp() < new Date().getTime()) {
            throw new JwtExpiredException("Token has expired!");
        }
        returnpayloadDto; }}Copy the code
  • createPayloadDtoEntity classes that encapsulate information stored in JWT;
/** * Created by macro on 2020/6/22. */
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
public class PayloadDto {
    @ApiModelProperty("Theme")
    private String sub;
    @ApiModelProperty("Time of issue")
    private Long iat;
    @ApiModelProperty("Expiration time")
    private Long exp;
    @ApiModelProperty("JWT的ID")
    private String jti;
    @ApiModelProperty("User name")
    private String username;
    @ApiModelProperty("User permissions")
    private List<String> authorities;
}
Copy the code
  • inJwtTokenServiceImplClass to get the default PayloadDto method, JWT expiration time set to60s;
/** * Created by macro on 2020/6/22. */
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
    @Override
    public PayloadDto getDefaultPayloadDto(a) {
        Date now = new Date();
        Date exp = DateUtil.offsetSecond(now, 60*60);
        return PayloadDto.builder()
                .sub("macro")
                .iat(now.getTime())
                .exp(exp.getTime())
                .jti(UUID.randomUUID().toString())
                .username("macro")
                .authorities(CollUtil.toList("ADMIN")) .build(); }}Copy the code
  • createJwtTokenControllerClass to add an interface to generate and parse JWT tokens based on the HMAC algorithm, since the HMAC algorithm requires a length of at least32 bytesSo we use MD5 encryption under;
/** * JWT token management Controller * Created by macro on 2020/6/22. */
@Api(tags = "JwtTokenController", description = "JWT Token Management")
@Controller
@RequestMapping("/token")
public class JwtTokenController {

    @Autowired
    private JwtTokenService jwtTokenService;

    @ApiOperation("Generate tokens using symmetric Encryption (HMAC) algorithm")
    @RequestMapping(value = "/hmac/generate", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult generateTokenByHMAC(a) {
        try {
            PayloadDto payloadDto = jwtTokenService.getDefaultPayloadDto();
            String token = jwtTokenService.generateTokenByHMAC(JSONUtil.toJsonStr(payloadDto), SecureUtil.md5("test"));
            return CommonResult.success(token);
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return CommonResult.failed();
    }

    @ApiOperation("Verify tokens using symmetric Encryption (HMAC) algorithms")
    @RequestMapping(value = "/hmac/verify", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult verifyTokenByHMAC(String token) {
        try {
            PayloadDto payloadDto  = jwtTokenService.verifyTokenByHMAC(token, SecureUtil.md5("test"));
            return CommonResult.success(payloadDto);
        } catch (ParseException | JOSEException e) {
            e.printStackTrace();
        }
        returnCommonResult.failed(); }}Copy the code
  • Test by calling the interface that generates JWT tokens using the HMAC algorithm;

  • Test by calling the interface that parses JWT tokens using the HMAC algorithm.

Asymmetric encryption (RSA)

Asymmetric encryption refers to the use of public and private keys for encryption and decryption operations. For encryption, the public key is responsible for encryption, the private key is responsible for decryption, and for signing, the private key is responsible for signing, and the public key is responsible for authentication. The use of asymmetric encryption in JWT is clearly a signing operation.

  • If we need to use fixed public and private keys for signature and authentication, we need to generate a certificate file, which we will use in JavakeytoolTools to generatejksCertificate file, the tool in the JDKbinDirectory;

  • Run the following command to generate a certificate file and set the alias tojwt, file namejwt.jks;
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
Copy the code
  • Enter the password as123456, and then you can generate the certificate after entering various informationjwt.jksThe file;

  • Certificate filejwt.jksCopy to the projectresourceDirectory, and then need to read from the certificate fileRSAKeyHere we need to be inpom.xmlAdd a Spring Security RSA dependency to the
<! Spring Security RSA Tool class -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-rsa</artifactId>
    <version>1.0.7. RELEASE</version>
</dependency>
Copy the code
  • Then, inJwtTokenServiceImplClass to read the certificate file from the classpath and convert toRSAKeyObject;
/** * Created by macro on 2020/6/22. */
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
    @Override
    public RSAKey getDefaultRSAKey(a) {
        // Get the RSA key pair from classpath
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair("jwt"."123456".toCharArray());
        // Obtain the RSA public key
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // Obtain the RSA private key
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return newRSAKey.Builder(publicKey).privateKey(privateKey).build(); }}Copy the code
  • We can do it atJwtTokenControllerAdd an interface to get the public key in the certificate.
/** * JWT token management Controller * Created by macro on 2020/6/22. */
@Api(tags = "JwtTokenController", description = "JWT Token Management")
@Controller
@RequestMapping("/token")
public class JwtTokenController {

    @Autowired
    private JwtTokenService jwtTokenService;
    
    @ApiOperation("Obtain the public key of the Asymmetric Encryption (RSA) algorithm")
    @RequestMapping(value = "/rsa/publicKey", method = RequestMethod.GET)
    @ResponseBody
    public Object getRSAPublicKey(a) {
        RSAKey key = jwtTokenService.getDefaultRSAKey();
        return newJWKSet(key).toJSONObject(); }}Copy the code
  • This interface is used to view public key information, which can be accessed publicly.

  • inJwtTokenServiceImplAdd according toRSAThe algorithm generates and parses the JWT token method that can be found and the aboveHMACThe algorithm operation is basically the same;
/** * Created by macro on 2020/6/22. */
@Service
public class JwtTokenServiceImpl implements JwtTokenService {
    @Override
    public String generateTokenByRSA(String payloadStr, RSAKey rsaKey) throws JOSEException {
        // Create the JWS header and set the signature algorithm and type
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256)
                .type(JOSEObjectType.JWT)
                .build();
        // Encapsulates Payload information into Payload
        Payload payload = new Payload(payloadStr);
        // Create a JWS object
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        // Create an RSA signature
        JWSSigner jwsSigner = new RSASSASigner(rsaKey, true);
        / / signature
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }

    @Override
    public PayloadDto verifyTokenByRSA(String token, RSAKey rsaKey) throws ParseException, JOSEException {
        // Parse JWS objects from tokens
        JWSObject jwsObject = JWSObject.parse(token);
        RSAKey publicRsaKey = rsaKey.toPublicJWK();
        // Create an RSA validator using the RSA public key
        JWSVerifier jwsVerifier = new RSASSAVerifier(publicRsaKey);
        if(! jwsObject.verify(jwsVerifier)) {throw new JwtInvalidException("Token signature is illegal!");
        }
        String payload = jwsObject.getPayload().toString();
        PayloadDto payloadDto = JSONUtil.toBean(payload, PayloadDto.class);
        if (payloadDto.getExp() < new Date().getTime()) {
            throw new JwtExpiredException("Token has expired!");
        }
        returnpayloadDto; }}Copy the code
  • inJwtTokenControllerClass to add an interface to generate and parse JWT tokens based on the RSA algorithm, using the default RSA key pair;
/** * JWT token management Controller * Created by macro on 2020/6/22. */
@Api(tags = "JwtTokenController", description = "JWT Token Management")
@Controller
@RequestMapping("/token")
public class JwtTokenController {

    @Autowired
    private JwtTokenService jwtTokenService;

    @ApiOperation("Generate tokens using an asymmetric Encryption (RSA) algorithm")
    @RequestMapping(value = "/rsa/generate", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult generateTokenByRSA(a) {
        try {
            PayloadDto payloadDto = jwtTokenService.getDefaultPayloadDto();
            String token = jwtTokenService.generateTokenByRSA(JSONUtil.toJsonStr(payloadDto),jwtTokenService.getDefaultRSAKey());
            return CommonResult.success(token);
        } catch (JOSEException e) {
            e.printStackTrace();
        }
        return CommonResult.failed();
    }

    @ApiOperation("Verify tokens using an asymmetric encryption (RSA) algorithm")
    @RequestMapping(value = "/rsa/verify", method = RequestMethod.GET)
    @ResponseBody
    public CommonResult verifyTokenByRSA(String token) {
        try {
            PayloadDto payloadDto  = jwtTokenService.verifyTokenByRSA(token, jwtTokenService.getDefaultRSAKey());
            return CommonResult.success(payloadDto);
        } catch (ParseException | JOSEException e) {
            e.printStackTrace();
        }
        returnCommonResult.failed(); }}Copy the code
  • Call the interface that generates JWT tokens using THE RSA algorithm for testing;

  • Test by calling the interface that parses JWT tokens using the RSA algorithm.

The resources

The official document: connect2id.com/products/ni…

Project source code address

Github.com/macrozheng/…

The public,

Mall project full set of learning tutorials serialized, attention to the public number the first time access.