package org.carlspring.strongbox.users.security; import org.carlspring.strongbox.security.exceptions.SecurityTokenException; import javax.inject.Inject; import java.io.UnsupportedEncodingException; import java.security.Key; import java.util.Map; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.MalformedClaimException; import org.jose4j.jwt.NumericDate; import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.keys.HmacKey; import org.jose4j.lang.JoseException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; /** * Used to get and verify security tokens. <br> * This implementation based on JSON Web Token (JWT) which is RFC 7519 standard. <br> * * * @author Sergey Bespalov */ @Component public class SecurityTokenProvider { private static final String MESSAGE_INVALID_JWT = "Invalid JWT: value-[%s]"; /** * Secret key which is used to encode and verify tokens.<br> * All previous tokens will be invalid, if it changed. */ private Key key; /** * Creates {@link Key} instance using Secret string from application configuration. * * @param secret * @throws UnsupportedEncodingException * @throws Exception */ @Inject public void init(@Value("${strongbox.security.jwtSecret:secret}") String secret) throws UnsupportedEncodingException { key = new HmacKey(secret.getBytes("UTF-8")); } /** * Generates an encrypted token. * * @param subject * a Subject which is used as token base. * @param claimMap * an additional Claims which will also present in token. * @param expireSeconds * @return encrypted token string. * @throws JoseException */ public String getToken(String subject, Map<String, String> claimMap, Integer expireSeconds) throws JoseException { JwtClaims claims = new JwtClaims(); claims.setIssuer("Strongbox"); claims.setGeneratedJwtId(); claims.setSubject(subject); claimMap.entrySet().stream().forEach((e) -> { claims.setClaim(e.getKey(), e.getValue()); }); if (expireSeconds != null) { claims.setExpirationTime(NumericDate.fromMilliseconds(System.currentTimeMillis() + expireSeconds * 1000)); } JsonWebSignature jws = new JsonWebSignature(); jws.setPayload(claims.toJson()); jws.setKey(key); jws.setDoKeyValidation(false); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); return jws.getCompactSerialization(); } public String getSubject(String token) { JwtClaims jwtClaims = getClimes(token); String subject; try { subject = jwtClaims.getSubject(); } catch (MalformedClaimException e) { throw new SecurityTokenException(String.format(MESSAGE_INVALID_JWT, token), e); } return subject; } private JwtClaims getClimes(String token) { JwtConsumer jwtConsumer = new JwtConsumerBuilder().setRequireSubject() .setVerificationKey(key) .setRelaxVerificationKeyValidation() .build(); JwtClaims jwtClaims; try { jwtClaims = jwtConsumer.processToClaims(token); } catch (InvalidJwtException e) { throw new SecurityTokenException(String.format(MESSAGE_INVALID_JWT, token), e); } return jwtClaims; } /** * @param token * @param targetSubject * @param claimMap */ public void verifyToken(String token, String targetSubject, Map<String, String> claimMap) { JwtClaims jwtClaims = getClimes(token); String subject; try { subject = jwtClaims.getSubject(); } catch (MalformedClaimException e) { throw new SecurityTokenException(String.format(MESSAGE_INVALID_JWT, token), e); } if (!targetSubject.equals(subject)) { throw new SecurityTokenException(String.format(MESSAGE_INVALID_JWT, token)); } boolean claimMatch = claimMap.entrySet().stream().allMatch((e) -> { return e.getValue().equals(jwtClaims.getClaimValue(e.getKey())); }); if (!claimMatch) { throw new SecurityTokenException(String.format(MESSAGE_INVALID_JWT, token)); } } }