Public number: fly code

HTTP communication itself is stateless, and the server does not know which user is requesting each request. Therefore, cookie and session are invented. After the user logs in, the server saves the session and returns the session ID to the user as part of the cookie. The next time a user requests a cookie, the server obtains the user login information according to the session ID in the cookie, so that the server can know which user requests the cookie

This is an early implementation scheme. Nowadays, most systems are distributed, and it is troublesome to synchronize sessions between systems, so someone invented token. After user logs in, the server generates a token string and returns the token to the user. Basic user information, such as the user name, can be written into the token. The server parses the token to obtain the user name. AES or RSA encryption can be used to generate the token

After the token is generated, it is returned to the user directly. Therefore, the server cannot restrict the token. If the user leaks the token, others can access the user’s data with the token. Another option is to write the token to Redis, where a token can be deleted to allow the user to log in again. Ultimately, it is more reliable to adopt OAuth2.0 scheme.

This article introduces the general token schemes JWT and JWK and RSA256 signatures

JWT

The full Json Web Token is the Token mentioned above and consists of the following three parts:

  1. Header declares the signature algorithm of JWT
  2. Payload Specifies the plaintext data carried by the token
  3. Signture: a JWT is valid based on whether the signature is valid, preventing forged JWT

The first and second base64 parts are in plain text after decoding. Therefore, do not write important data in the payload. In general, do not write the user ID in the payload to prevent others from guessing the number of users based on the USER ID

JWK

Full Json Web Key This is JWT Key information. If RSA encryption is used for signature, you need to provide a Public Key to the verifier. The verifier uses the Public Key to verify whether the signature in the JWT is valid

HMAC256 signature mode

This signature mode is not recommended because it is less secure than RSA256. The following describes how to use this signature mode

pom.xml

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.2</version>
</dependency>
Copy the code

Utility class

public class JwtUtils {
    private static final String PREFIX = "Prefix of key";
    private static final String SUFFIX = "Key suffix";

    /** * generates tokens that will never expire **@paramUserId userId *@param ip     IP
     * @paramSecret key *@return* /
    public static String create(String userId, String ip, String secret) {
        return create(userId, ip, secret, null);
    }

    public static String create(String userId, String ip, String secret, String other) {
        return create(userId, ip, secret, other, null);
    }

    /** * generates token **@paramUserId userId *@param ip     IP
     * @paramSecret key *@paramExpire expiration time, in milliseconds *@return* /
    public static String create(String userId, String ip, String secret, String other, Long expire) {
        JWTCreator.Builder builder = JWT.create().withAudience(userId, ip, String.valueOf(System.currentTimeMillis()), other);
        if(expire ! =null && expire > 0) {
            builder.withExpiresAt(new Date(System.currentTimeMillis() + expire));
        }
        return builder.sign(Algorithm.HMAC256(PREFIX + secret + SUFFIX));
    }

    /** * Get the token data **@param token token
     * @return
     * @throws TokenException
     */
    public static List<String> getAudienceList(String token) throws TokenException {
        try {
            return JWT.decode(token).getAudience();
        } catch (JWTDecodeException e) {
            throw newTokenException(e.getMessage()); }}/** * Verify that the token is valid **@param token  token
     * @paramSecret key *@return
     * @throws TokenException
     */
    public static boolean verify(String token, String secret) throws TokenException {
        try {
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(PREFIX + secret + SUFFIX)).build();
            jwtVerifier.verify(token);
            return true;
        } catch (JWTVerificationException e) {
            throw newTokenException(e.getMessage()); }}}Copy the code

usage

long expire = expireTime == null ? 7 * 24 * 3600 * 1000L : expireTime;
String token = JwtUtils.create(loginUser.getUsername(), HttpUtils.getIp(request), loginUser.getPassword(), other, expire);

/ / authentication token
if (JwtUtils.verify(token, loginUser.getPassword())) {
    // Get the data payload
    List<String> data = JwtUtils.getAudienceList(token);
}
Copy the code

The user name, IP address, current time, and Other are written to payload and the user password (encrypted password) is used for signature. Each user has a different signature key

RSA256 Signature mode

pom.xml

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>9.0.1</version>
</dependency>
Copy the code

Rsa tools

Generally, the length must be at least 1024 characters (2048 characters are recommended). For systems with higher security requirements, use 4096

@Slf4j
public class RsaUtils {

    private static final int KEY_SIZE = 2048;

    /** * Generates a key pair **@return* /
    public static KeyPair create(a) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            keyPairGenerator.initialize(KEY_SIZE);
            return keyPairGenerator.generateKeyPair();
        } catch (NoSuchAlgorithmException e) {
            return null; }}/** * Get public key ** from string@param key
     * @return* /
    public static PublicKey getPublicKey(String key) throws InvalidKeySpecException {
        try {
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(initKey(key));
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePublic(keySpec);
        } catch (InvalidKeySpecException e) {
            log.debug("Public key error", e);
            throw e;
        } catch (NoSuchAlgorithmException e) {
            return null; }}/** * get the private key ** from the string@param key
     * @return
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getPrivateKey(String key) throws InvalidKeySpecException {
        try {
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(initKey(key));
            KeyFactory factory = KeyFactory.getInstance("RSA");
            return factory.generatePrivate(keySpec);
        } catch (InvalidKeySpecException e) {
            log.debug("Private key error", e);
            throw e;
        } catch (NoSuchAlgorithmException e) {
            return null; }}/** * Public key conversion string **@param key
     * @return* /
    public static String toPublicKeyString(PublicKey key) {
        return Base64.encodeBase64String(key.getEncoded());
    }

    /** * Private key transfer string **@param key
     * @return* /
    public static String toPrivateKeyString(PrivateKey key) {
        return Base64.encodeBase64String(key.getEncoded());
    }

    /** * use private key to encrypt **@param string
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptByPrivateKey(String string, String key) throws Exception {
        PrivateKey privateKey = getPrivateKey(key);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);

        // Separate encryption is required due to length limitation
        byte[] data = string.getBytes();
        int blockLength = KEY_SIZE / 8 - 11;
        int offset = 0;
        int i = 0;
        int length = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] cache;
        while (length - offset > 0) {
            if (length - offset > blockLength) {
                cache = cipher.doFinal(data, offset, blockLength);
            } else {
                cache = cipher.doFinal(data, offset, length - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * blockLength;
        }

        byte[] bytes = out.toByteArray();
        out.close();

        return Base64.encodeBase64String(bytes);
    }

    /** * Use public key encryption **@param string
     * @param key
     * @return
     * @throws Exception
     */
    public static String encryptByPublicKey(String string, String key) throws Exception {
        PublicKey publicKey = getPublicKey(key);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);

        // Separate encryption is required due to length limitation
        byte[] data = string.getBytes();
        int blockLength = KEY_SIZE / 8 - 11;
        int offset = 0;
        int i = 0;
        int length = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] cache;
        while (length - offset > 0) {
            if (length - offset > blockLength) {
                cache = cipher.doFinal(data, offset, blockLength);
            } else {
                cache = cipher.doFinal(data, offset, length - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * blockLength;
        }

        byte[] bytes = out.toByteArray();
        out.close();

        return Base64.encodeBase64String(bytes);
    }

    /** * decrypt ** with private key@param string
     * @param key
     * @return
     * @throws Exception
     */
    public static String decryptByPrivateKey(String string, String key) throws Exception {
        PrivateKey privateKey = getPrivateKey(key);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        // Separate encryption is required due to length limitation
        byte[] data = Base64.decodeBase64(string);
        int length = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        int blockLength = KEY_SIZE / 8;
        while (length - offset > 0) {
            if (length - offset > blockLength) {
                cache = cipher.doFinal(data, offset, blockLength);
            } else {
                cache = cipher.doFinal(data, offset, length - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * blockLength;
        }

        byte[] bytes = out.toByteArray();
        out.close();

        return new String(bytes, "utf-8");
    }

    /** * decrypt ** using public key@param string
     * @param key
     * @return
     * @throws Exception
     */
    public static String decryptByPublicKey(String string, String key) throws Exception {
        PublicKey publicKey = getPublicKey(key);
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, publicKey);

        // Separate encryption is required due to length limitation
        byte[] data = Base64.decodeBase64(string);
        int length = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offset = 0;
        byte[] cache;
        int i = 0;
        int blockLength = KEY_SIZE / 8;
        while (length - offset > 0) {
            if (length - offset > blockLength) {
                cache = cipher.doFinal(data, offset, blockLength);
            } else {
                cache = cipher.doFinal(data, offset, length - offset);
            }
            out.write(cache, 0, cache.length);
            i++;
            offset = i * blockLength;
        }

        byte[] bytes = out.toByteArray();
        out.close();

        return new String(bytes, "utf-8");
    }

    /** * removes beginning and end and newline characters from the key and converts them to byte[] **@param key
     * @return* /
    private static byte[] initKey(String key) {
        if (key.contains("-----BEGIN PRIVATE KEY-----")) {
            key = key.substring(key.indexOf("-----BEGIN PRIVATE KEY-----") + 27);
        }
        if (key.contains("-----BEGIN PUBLIC KEY-----")) {
            key = key.substring(key.indexOf("-----BEGIN PUBLIC KEY-----") + 26);
        }
        if (key.contains("-----END PRIVATE KEY-----")) {
            key = key.substring(0, key.indexOf("-----END PRIVATE KEY-----"));
        }
        if (key.contains("-----END PUBLIC KEY-----")) {
            key = key.substring(0, key.indexOf("-----END PUBLIC KEY-----"));
        }
        key = key.replaceAll("\r\n"."");
        key = key.replaceAll("\n"."");
        returnBase64.decodeBase64(key); }}Copy the code

JwtRsa tools

@Slf4j
public class JwtRsaUtils {

    /** * Provides the public key string, returning RSAKey **@param keyId
     * @param publicKey
     * @return* /
    public static RSAKey getRsaKey(String keyId, String publicKey) throws InvalidKeySpecException {
        return getRsaKey(keyId, RsaUtils.getPublicKey(publicKey));
    }

    /** * Provides a string of public and private keys, returning RSAKey **@param keyId
     * @param publicKey
     * @param privateKey
     * @return* /
    public static RSAKey getRsaKey(String keyId, String publicKey, String privateKey) throws InvalidKeySpecException {
        return getRsaKey(keyId, RsaUtils.getPublicKey(publicKey), RsaUtils.getPrivateKey(privateKey));
    }

    /** * provide public key, return RSAKey **@param keyId
     * @param publicKey
     * @return* /
    public static RSAKey getRsaKey(String keyId, PublicKey publicKey) {
        return new RSAKey.Builder((RSAPublicKey) publicKey)
                .keyUse(KeyUse.SIGNATURE)
                .algorithm(JWSAlgorithm.RS256)
                .keyID(keyId)
                .build();
    }

    /** * provide public and private keys, return RSAKey **@param keyId
     * @param publicKey
     * @param privateKey
     * @return* /
    public static RSAKey getRsaKey(String keyId, PublicKey publicKey, PrivateKey privateKey) {
        return new RSAKey.Builder((RSAPublicKey) publicKey)
                .privateKey(privateKey)
                .keyUse(KeyUse.SIGNATURE)
                .algorithm(JWSAlgorithm.RS256)
                .keyID(keyId)
                .build();
    }

    /** * Sign according to RSAKey **@param rsaKey
     * @return
     * @throws JOSEException
     */
    public static String sign(RSAKey rsaKey) throws JOSEException {
        return sign(rsaKey, new JWTClaimsSet.Builder().build());
    }

    /** * Sign according to RSAKey **@param rsaKey
     * @param aud
     * @return
     * @throws JOSEException
     */
    public static String sign(RSAKey rsaKey, String... aud) throws JOSEException {
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .audience(Arrays.asList(aud))
                .build();
        return sign(rsaKey, claimsSet);
    }

    /** * Expiration time can be set according to RSAKey signature **@param rsaKey
     * @paramExpire expiration time, in milliseconds *@param aud
     * @return
     * @throws JOSEException
     */
    public static String sign(RSAKey rsaKey, long expire, String... aud) throws JOSEException {
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .audience(Arrays.asList(aud))
                .expirationTime(new Date(System.currentTimeMillis() + expire))
                .build();
        return sign(rsaKey, claimsSet);
    }

    /** * Expiration time can be set according to RSAKey signature **@param rsaKey
     * @param issuer  iss
     * @param subject sub
     * @paramExpire expiration time, in milliseconds *@param aud
     * @return
     * @throws JOSEException
     */
    public static String sign(RSAKey rsaKey, String issuer, String subject, long expire, String... aud) throws JOSEException {
        JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .issuer(issuer)
                .subject(subject)
                .audience(Arrays.asList(aud))
                .expirationTime(new Date(System.currentTimeMillis() + expire))
                .build();
        return sign(rsaKey, claimsSet);
    }

    /** ** signature **@param rsaKey
     * @param claimsSet
     * @return
     * @throws JOSEException
     */
    public static String sign(RSAKey rsaKey, JWTClaimsSet claimsSet) throws JOSEException {
        SignedJWT signedJWT = new SignedJWT(
                new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaKey.getKeyID()).build(),
                claimsSet);
        signedJWT.sign(new RSASSASigner(rsaKey));
        return signedJWT.serialize();
    }

    /** * verify signature **@param rsaKey
     * @param token
     * @return* /
    public static boolean verify(RSAKey rsaKey, String token) {
        try {
            SignedJWT signedJWT = SignedJWT.parse(token);

            if(signedJWT.getJWTClaimsSet().getExpirationTime() ! =null) {
                if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
                    return false;
                }
            }

            RSASSAVerifier verifier = new RSASSAVerifier(rsaKey);
            return signedJWT.verify(verifier);
        } catch (ParseException e) {
            log.debug("Failed to parse JWT", e);
            return false;
        } catch (JOSEException e) {
            log.debug("Key error while parsing JWT", e);
            return false; }}/** * validate the signature and return the JWT object **@param rsaKey
     * @param token
     * @returnIf null is returned, validation failed */
    public static SignedJWT verifyWithData(RSAKey rsaKey, String token) {
        try {
            SignedJWT signedJWT = SignedJWT.parse(token);

            if(signedJWT.getJWTClaimsSet().getExpirationTime() ! =null) {
                if (!new Date().before(signedJWT.getJWTClaimsSet().getExpirationTime())) {
                    return null;
                }
            }

            RSASSAVerifier verifier = new RSASSAVerifier(rsaKey);
            if (signedJWT.verify(verifier)) {
                return signedJWT;
            } else {
                return null; }}catch (ParseException e) {
            log.debug("Failed to parse JWT", e);
            return null;
        } catch (JOSEException e) {
            log.debug("Key error while parsing JWT", e);
            return null; }}}Copy the code

usage

// keyId is an arbitrary string
RSAKey rsaKey = JwtRsaUtils.getRsaKey(keyId, publicKey, privateKey);
String token = JwtRsaUtils.sign(rsaKey, issuer, subject, expireTime,
        defaultAccount.getId().toString(),
        defaultAccount.getUserId().toString(),
        defaultAccount.getCustomerId().toString(),
        defaultAccount.getName(),
        defaultAccount.getAccountType());

// This step does not require a key, because it is plaintext
SignedJWT jwt = SignedJWT.parse(token);
List<String> audience = jwt.getJWTClaimsSet().getAudience();

/ / verification
if (JwtRsaUtils.verify(rsaKey, token)) {
    // Verification passed
}

// Validate and return payload
SignedJWT jwt = JwtRsaUtils.verifyWithData(rsaKey, token);
if(jwt ! =null) {
    // Verification passed
    List<String> audience = jwt.getJWTClaimsSet().getAudience();
}
Copy the code

Public number: fly code