This the time for which we want the generated JWT to be valid for.
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationInMs)).signWith(SignatureAlgorithm.HS512, secret).compact();
}
Related
I am trying to generate a JWT token that has to be given to another system.
I have followed the below steps for doing so:
Created a Connect App. I have got consumer key using this App.
Created a self signed certificate. Downloaded the certificate(crt) file. Converted the crt file into key file using openSSL. I used command:
openssl req -newkey rsa:2048 -nodes -keyout SSO_Self_Signed.key -out SSO_Self_Signed.csr
SSO_Self_Signed is a label name of my certificate. The generated key file has private key in between -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----
3. I have created an APEX class that will generate JWT token using the creds we obtained in STEP 1 and STEP 2.
The code snippet of my code is also mentioned below:
jwtToken is generated on line 46.
When I am trying to validate the JWT in https://jwt.io/, I am getting invalid signature and I am not able to figure out the exact cause of the issue.
The inputs passed to the class includes - iss:mailaddress; sub:other system url; aud: consumer key from step1; privateKey: key from step2.
public class SSOJWTGenerator {
public static String generateJWT(String iss, String sub, String aud, String privateKey) {
String alg = 'RS256';
String typ = 'JWT';
// Create the JWT header
Map<String, Object> header = new Map<String, Object>{
'alg' => alg,
'typ' => typ
};
String encodedHeader = base64UrlEncode(JSON.serialize(header));
// Create the JWT claim set
Map<String, Object> claimSet = new Map<String, Object>{
'iss' => iss,
'sub' => sub,
'aud' => aud,
'exp' => String.valueOf(DateTime.now().getTime()/1000 + 300),
'iat' => String.valueOf(DateTime.now().getTime()/1000)
};
String encodedClaimSet = base64UrlEncode(JSON.serialize(claimSet));
// Create the signature
String input = encodedHeader + '.' + encodedClaimSet;
privateKey = privateKey.replace('-----BEGIN PRIVATE KEY-----', '');
privateKey = privateKey.replace('-----END PRIVATE KEY-----', '');
privateKey = privateKey.deleteWhitespace();
Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob signatureBlob = Crypto.sign('RSA-SHA256', Blob.valueOf(input), privateKeyBlob);
String signature = base64UrlEncode(signatureBlob);
// Combine the header, claim set, and signature to create the JWT token
String jwtToken = encodedHeader + '.' + encodedClaimSet + '.' + signature;
System.debug('jwtToken'+ jwtToken);
return jwtToken;
}
private static String base64UrlEncode(String input) {
// Replace + with -, / with _, and remove any trailing = signs
String base64 = EncodingUtil.base64Encode(Blob.valueOf(input));
base64 = base64.replace('+', '-').replace('/', '_').replaceAll('\\=+$', '');
return base64;
}
private static String base64UrlEncode(Blob input) {
// Replace + with -, / with _, and remove any trailing = signs
String base64 = EncodingUtil.base64Encode(input);
base64 = base64.replace('+', '-').replace('/', '_').replaceAll('\\=+$', '');
return base64;
}
}
I'm trying to find a way to change the "sub" format in JWT Token provided by Keycloak, I know it came from Keycloak User Id but i'm not sure we can't change it.
For example for now I have something like this :
"sub": "f:39989175-b393-4fad-8f84-628b9712f93b:testldap",
I would like it smaller 😅.
I'm not sure that modifying 'sub' is a good idea, but if you sure, you can use something like that:
/**
* Class for signing JWT (when you get tokens in base64 actually they are
* signed by issuer server see https://jwt.io)
*/
public static class JwtSigner {
private final KeyPair keyPair;
private final String kid;
public JwtSigner(String privateKeyPem) {
PrivateKey privateKey = PemUtils.decodePrivateKey(privateKeyPem);
PublicKey publicKey = KeyUtils.extractPublicKey(privateKey);
keyPair = new KeyPair(publicKey, privateKey);
kid = KeyUtils.createKeyId(keyPair.getPublic());
}
public String encodeToken(AccessToken accessToken) {
return new JWSBuilder()
.type("JWT")
.kid(kid)
.jsonContent(accessToken)
.sign(Algorithm.RS256, keyPair.getPrivate());
}
}
/**
* This class allows you to update several token fields and re-encode token
*/
public static class JwtTransformer<T extends AccessToken> {
private T token;
public JwtTransformer(String tokenString, Class<T> tokenType) throws JWSInputException {
try {
token = JsonSerialization.readValue(new JWSInput(tokenString).getContent(), tokenType);
} catch (IOException e) {
throw new JWSInputException(e);
}
}
public static <T extends AccessToken> T decode(String tokenString, Class<T> tokenType) throws JWSInputException {
return new JwtTransformer<>(tokenString, tokenType).decode();
}
public static JwtTransformer<AccessToken> forAccessToken(String tokenString) throws JWSInputException {
return new JwtTransformer<>(tokenString, AccessToken.class);
}
public static JwtTransformer<RefreshToken> forRefreshToken(String tokenString) throws JWSInputException {
return new JwtTransformer<>(tokenString, RefreshToken.class);
}
public T decode() {
return token;
}
public JwtTransformer transform(Consumer<T> consumer) {
consumer.accept(token);
return this;
}
public String encode(JwtSigner jwtSigner) {
return jwtSigner.encodeToken(token);
}
}
I used this classes for tests, but you can adopt them for your needs. Take a note that private key that required for JwtSigner initializaton is stored in keycloak DB, and can not be easily extracted via Admin Console UI. Check out result of
select VALUE
from KEYCLOAK.COMPONENT
inner join KEYCLOAK.COMPONENT_CONFIG
on KEYCLOAK.COMPONENT.ID = KEYCLOAK.COMPONENT_CONFIG.COMPONENT_ID
where PARENT_ID = '%YOUR_REALM_NAME%'
and PROVIDER_ID = 'rsa-generated'
and COMPONENT_CONFIG.NAME = 'privateKey';
So finally you can do something like
String new AccessToken = JwtTransformer.forAccessToken(accessTokenString)
.transform(token -> {
token.subject(subModificationFunction(token.getSubject()))
})
.encode();
We have an ERP application running on GCP .
For downloading data spanning more than three months or so ,we're uploading a file on GCS. Now i want to create a signed url so that to give limited access to the end users .
I have been trying this. But i get this error :
Signature does not match. Please check your Google secret key.
Can anyone tell how to go about this?
private static final int EXPIRATION_TIME = 5;
private static final String BASE_URL = "https://storage.googleapis.com";
private static final String httpVerb = "GET";
/*
* private static final String BUCKET = "my_bucket"; private static final String
* FOLDER = "folder";
*/
private final AppIdentityService identityService = AppIdentityServiceFactory.getAppIdentityService();
public String getSignedUrl(String bucket, final String fileName, String contentTpe) throws Exception {
final long expiration = expiration();
final String unsigned = stringToSign(bucket, expiration, fileName, contentTpe);
final String signature = sign(unsigned);
return new StringBuilder(BASE_URL).append("/").append(bucket).append("/").append(fileName)
.append("?GoogleAccessId=").append(clientId()).append("&Expires=").append(expiration)
.append("&Signature=").append(URLEncoder.encode(signature, "UTF-8")).toString();
}
private static long expiration() {
final long unitMil = 1000l;
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, EXPIRATION_TIME);
final long expiration = calendar.getTimeInMillis() / unitMil;
return expiration;
}
private String stringToSign(String bucket, final long expiration, String filename, String contentType) {
final String contentMD5 = "";
final String canonicalizedExtensionHeaders = "";
final String canonicalizedResource = "/" + bucket + "/" + filename;
final String stringToSign = httpVerb + "\n"+ contentMD5 + "\n" + contentType + "\n" + expiration + "\n"
+ canonicalizedExtensionHeaders + canonicalizedResource;
return stringToSign;
}
protected String sign(final String stringToSign) throws UnsupportedEncodingException {
final SigningResult signingResult = identityService.signForApp(stringToSign.getBytes());
final String encodedSignature = new String(Base64.encodeBase64(signingResult.getSignature()), "UTF-8");
return encodedSignature;
}
protected String clientId() {
return identityService.getServiceAccountName();
}
URL signing code is a bit tricky because by its nature it can be difficult to know what you've gotten wrong, other than just seeing that it's wrong. There are a few general tips that make it easier:
First, if possible, consider using URL signing functions in the google-cloud libraries. For example, the Java google-cloud library provides a Storage.signURL method, and you can use it like this:
URL signedUrl = storage.signUrl(
BlobInfo.newBuilder(bucketName, blobName).build(),
2, TimeUnit.DAYS);
Second, if you look at the error message, you'll notice that there's a <StringToSign> section. This section contains the exact string that GCS would calculate a signature for. Make sure that the string you're signing matches this string exactly. If it doesn't, that's your problem.
In your code's particular case, I didn't find the problem, but it might be that you're including a content-type line when signing the string, but GET requests don't provide a Content-Type header. It's just an idea, though, since I don't see your invocation of getSignedUrl.
I have designed a web application which uses very simple implementation of JWT token's to provide Authentication/Authorization.
My Implementation :
There are two types of urls's public and secure.
Public urls are to generate token with username/password.
I have added filter on secure url to check for the Authorization Header and JWT Token.
#Bean
public FilterRegistrationBean jwtFilter()
{
final FilterRegistrationBean registrationBean = new
FilterRegistrationBean();
registrationBean.setFilter(new JwtFilter());
registrationBean.addUrlPatterns("/secure/*");
return registrationBean;
}
Filter will validate the token. I haven't added expiration date yet.
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
final String authHeader = request.getHeader("authorization");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
chain.doFilter(req, res);
} else {
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
throw new ServletException("Missing or invalid Authorization header");
}
final String token = authHeader.substring(7);
try {
final Claims claims = Jwts.parser().setSigningKey(secretKey.toString).parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
} catch (final SignatureException e) {
throw new ServletException("Invalid token");
}
chain.doFilter(req, res);
}
This is providing authentication and also its is immune to CSRF.No one can create valid token without secret Key.
Are there other attacks possible on token base authentication service which i have missed?
I want to use jwt as authentication mechanism so I pick auth0-jwt java libs. To implement authentication services I did a test about token validation. The test is simple, use this token:
{ "alg":"HS256", "typ": "JWT" }
{ "sub": "dummySubject", "exp": "1498054620653" }
with supersecretpassphrase to verify signature. Given that token I expect back this (taken by jwt.io) one:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkdW1teVN1YmplY3QiLCJleHAiOiIxNDk4MDU0NjIwNjUzIn0.VTL9gIQ-POIoI8W0OI9y_Q7rDGFbniUGNcfd_EliK60
but test fails. The returned token is:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkdW1teVN1YmplY3QiLCJleHAiOjE0OTgwNTQ2MjB9.2dOy3HmWH7vMWiJoQvMOUsx2nR6l6phZ1nwIz-TSkNk
jwt.io says that that token has this shape:
{ "typ": "JWT", "alg": "HS256" }
{ "sub": "dummySubject", "exp": "1498054620" }
It looks like exp loose its milliseconds. This is how I fill my token:
private String computeToken(JwtToken token) throws Exception {
Algorithm algorithmHS = Algorithm.HMAC256(passphrase);
Date expirationTime = new Date(token.getExpirationTime().toInstant().toEpochMilli());
Builder jwtToken = JWT.create().withSubject(token.getSubject()).withExpiresAt(expirationTime);
Iterator<Entry<String, Object>> candidateClaimsIterator = token.getPayload().entrySet().iterator();
...
return jwtToken.sign(algorithmHS);
}
The JwtToken token parameter is a simple bean I use to ship and validate data I want to put in token,
public class JwtToken implements Serializable {
private static final long serialVersionUID = 1L;
#NotNull(message = "Subject can not be null")
#Size(min = 10)
private String subject;
#NotNull(message = "Expiration time can not be null")
private ZonedDateTime expirationTime;
private Map<String, Object> payload;
...
(getters and setters)
}
It seems that java.util.Date loose it's milliseconds while com.auth0.jwt.JWTCreator convert exp in a JSON string. Am I doing something wrong? Is this the expected behavior?