Added token refresh

This commit is contained in:
Luca Frosini 2021-12-06 17:43:18 +01:00
parent 6da43964b5
commit 452e78139f
9 changed files with 249 additions and 203 deletions

View File

@ -0,0 +1,39 @@
package org.gcube.common.authorization.utils.clientid;
import org.gcube.common.authorization.utils.secret.JWTSecret;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.keycloak.KeycloakClientFactory;
import org.gcube.common.keycloak.model.ModelUtils;
import org.gcube.common.keycloak.model.RefreshToken;
import org.gcube.common.keycloak.model.TokenResponse;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class ClienIDManager implements RenewalProvider {
protected final String clientID;
protected final String clientSecret;
public ClienIDManager(String clientID, String clientSecret) {
this.clientID = clientID;
this.clientSecret = clientSecret;
}
public Secret getSecret() throws Exception {
TokenResponse tr = KeycloakClientFactory.newInstance().queryUMAToken(clientID, clientSecret, null);
JWTSecret jwtSecret = new JWTSecret(tr.getAccessToken());
jwtSecret.setRenewalProvider(this);
RefreshToken refreshToken = ModelUtils.getRefreshTokenFrom(tr);
jwtSecret.setRefreshToken(refreshToken);
return jwtSecret;
}
@Override
public Secret renew() throws Exception {
return getSecret();
}
}

View File

@ -1,90 +0,0 @@
package org.gcube.common.authorization.utils.clientid;
import java.util.Map;
import java.util.Objects;
import org.gcube.common.authorization.library.provider.ClientInfo;
import org.gcube.common.authorization.library.utils.Caller;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.keycloak.KeycloakClientFactory;
import org.gcube.common.keycloak.model.TokenResponse;
import org.gcube.common.scope.api.ScopeProvider;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class ClienIDSecret extends Secret {
protected String clientID;
public ClienIDSecret(String clientID, String token) {
super(30, token);
this.clientID = clientID;
}
@Override
public String getContext() throws Exception {
return null;
}
@Override
public String getUsername() throws Exception {
return clientID;
}
@Override
public Map<String, String> getHTTPAuthorizationHeaders() {
return null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(clientID);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
ClienIDSecret other = (ClienIDSecret) obj;
return Objects.equals(clientID, other.clientID);
}
@Override
public int compareTo(Secret obj) {
int res = super.compareTo(obj);
return res == 0 ? clientID.compareTo(clientID) : res;
}
@Override
public void setToken() throws Exception {
// TODO
TokenResponse tr = KeycloakClientFactory.newInstance().queryUMAToken(clientID, token, ScopeProvider.instance.get(), null);
logger.trace("{}", tr);
}
@Override
public void resetToken() throws Exception {
// TODO Auto-generated method stub
}
@Override
public ClientInfo getClientInfo() throws Exception {
// TODO Auto-generated method stub
return null;
}
@Override
public Caller getCaller() throws Exception {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -1,16 +0,0 @@
package org.gcube.common.authorization.utils.clientid;
import org.gcube.common.authorization.utils.provider.SecretProvider;
import org.gcube.common.authorization.utils.secret.Secret;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class ClientIDSecretProvider implements SecretProvider {
@Override
public Secret getSecret() {
return new ClienIDSecret("", "");
}
}

View File

@ -0,0 +1,8 @@
package org.gcube.common.authorization.utils.clientid;
import org.gcube.common.authorization.utils.secret.Secret;
public interface RenewalProvider {
public Secret renew() throws Exception;
}

View File

@ -83,6 +83,17 @@ public class SecretHolder {
return ScopeProvider.instance.get(); return ScopeProvider.instance.get();
} }
public Collection<String> getRoles() {
for(Secret secret : secrets) {
try {
return secret.getRoles();
}catch (Exception e) {
// trying the next one
}
}
return null;
}
public void reset() { public void reset() {
for(Secret secret : secrets) { for(Secret secret : secrets) {
try { try {
@ -93,4 +104,48 @@ public class SecretHolder {
} }
} }
public String getName() {
for(Secret secret : secrets) {
try {
secret.getName();
}catch (Exception e) {
// trying the next one
}
}
return null;
}
public String getSurname() {
for(Secret secret : secrets) {
try {
secret.getSurname();
}catch (Exception e) {
// trying the next one
}
}
return null;
}
public String getEMail() {
for(Secret secret : secrets) {
try {
secret.getEMail();
}catch (Exception e) {
// trying the next one
}
}
return null;
}
public String getTitle() {
for(Secret secret : secrets) {
try {
secret.getTitle();
}catch (Exception e) {
// trying the next one
}
}
return "";
}
} }

View File

@ -1,6 +1,7 @@
package org.gcube.common.authorization.utils.manager; package org.gcube.common.authorization.utils.manager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.SortedSet; import java.util.SortedSet;
@ -16,8 +17,6 @@ public class SecretManager {
public static final InheritableThreadLocal<SecretManager> instance = new InheritableThreadLocal<SecretManager>() { public static final InheritableThreadLocal<SecretManager> instance = new InheritableThreadLocal<SecretManager>() {
@Override @Override
protected SecretManager initialValue() { protected SecretManager initialValue() {
return new SecretManager(); return new SecretManager();
@ -92,6 +91,10 @@ public class SecretManager {
return currentSecretHolder.getContext(); return currentSecretHolder.getContext();
} }
public Collection<String> getRoles() {
return currentSecretHolder.getRoles();
}
public void reset() { public void reset() {
initialSecretHolder.reset(); initialSecretHolder.reset();
if(initialSecretHolder!=currentSecretHolder) { if(initialSecretHolder!=currentSecretHolder) {
@ -99,4 +102,20 @@ public class SecretManager {
} }
instance.remove(); instance.remove();
} }
public String getName() {
return currentSecretHolder.getName();
}
public String getSurname() {
return currentSecretHolder.getSurname();
}
public String getEMail() {
return currentSecretHolder.getEMail();
}
public String getTitle() {
return currentSecretHolder.getTitle();
}
} }

View File

@ -1,7 +1,12 @@
package org.gcube.common.authorization.utils.secret; package org.gcube.common.authorization.utils.secret;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
@ -9,7 +14,10 @@ import org.gcube.common.authorization.library.provider.AccessTokenProvider;
import org.gcube.common.authorization.library.provider.ClientInfo; import org.gcube.common.authorization.library.provider.ClientInfo;
import org.gcube.common.authorization.library.provider.UserInfo; import org.gcube.common.authorization.library.provider.UserInfo;
import org.gcube.common.authorization.library.utils.Caller; import org.gcube.common.authorization.library.utils.Caller;
import org.gcube.common.authorization.utils.secret.jwt.JWToken; import org.gcube.common.authorization.utils.clientid.RenewalProvider;
import org.gcube.common.keycloak.model.AccessToken;
import org.gcube.common.keycloak.model.RefreshToken;
import org.gcube.common.keycloak.model.util.Time;
import org.gcube.common.scope.impl.ScopeBean; import org.gcube.common.scope.impl.ScopeBean;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -21,15 +29,46 @@ public class JWTSecret extends Secret {
private static final Logger logger = LoggerFactory.getLogger(JWTSecret.class); private static final Logger logger = LoggerFactory.getLogger(JWTSecret.class);
protected JWToken jwt; /**
*
*/
public static long TOLERANCE = 200;
protected AccessToken accessToken;
protected RefreshToken refreshToken;
protected RenewalProvider renewalProvider;
public JWTSecret(String token) { public JWTSecret(String token) {
super(10, token); super(10, token);
} }
private String getTokenString() {
try {
boolean expired = false;
getAccessToken();
if(Time.currentTimeMillis()>=(accessToken.getExp()-TOLERANCE)) {
expired = true;
if(refreshToken!=null) {
// TODO refresh
expired = false;
}
}
if(expired && renewalProvider!=null) {
JWTSecret renewed = (JWTSecret) renewalProvider.renew();
this.token = renewed.token;
this.accessToken = getAccessToken();
}
}catch (Exception e) {
// TODO log
}
return token;
}
@Override @Override
public void setToken() throws Exception { public void setToken() throws Exception {
AccessTokenProvider.instance.set(token); AccessTokenProvider.instance.set(getTokenString());
} }
@Override @Override
@ -37,26 +76,26 @@ public class JWTSecret extends Secret {
AccessTokenProvider.instance.reset(); AccessTokenProvider.instance.reset();
} }
protected JWToken getJWToken() throws Exception { protected AccessToken getAccessToken() throws Exception {
if(jwt==null) { if(accessToken==null) {
String realUmaTokenEncoded = token.split("\\.")[1]; String realUmaTokenEncoded = token.split("\\.")[1];
String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes())); String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes()));
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
try { try {
jwt = mapper.readValue(realUmaToken, JWToken.class); accessToken = mapper.readValue(realUmaToken, AccessToken.class);
}catch(Exception e){ }catch(Exception e){
logger.error("Error parsing JWT token",e); logger.error("Error parsing JWT token",e);
throw new Exception("Error parsing JWT token", e); throw new Exception("Error parsing JWT token", e);
} }
} }
return jwt; return accessToken;
} }
@Override @Override
public ClientInfo getClientInfo() throws Exception { public ClientInfo getClientInfo() throws Exception {
getJWToken(); getAccessToken();
ClientInfo clientInfo = new UserInfo(jwt.getUsername(), jwt.getRoles(), jwt.getEmail(), jwt.getFirstName(), jwt.getLastName()); List<String> roles = new ArrayList<>(accessToken.getRealmAccess().getRoles());
ClientInfo clientInfo = new UserInfo(accessToken.getPreferredUsername(), roles, accessToken.getEmail(), accessToken.getGivenName(), accessToken.getFamilyName());
return clientInfo; return clientInfo;
} }
@ -68,21 +107,78 @@ public class JWTSecret extends Secret {
@Override @Override
public String getContext() throws Exception { public String getContext() throws Exception {
ScopeBean scopeBean = null; String context = null;
String[] audience = getAccessToken().getAudience();
for (String aud : audience) {
if (aud != null && aud.compareTo("") != 0) {
try { try {
scopeBean = new ScopeBean(getJWToken().getContext()); context = URLDecoder.decode(context, StandardCharsets.UTF_8.toString());
}catch(Exception e){ ScopeBean scopeBean = new ScopeBean(context);
logger.error("Invalid context in access token",e); return scopeBean.toString();
} catch (Exception e) {
logger.error("Invalid context name for audience {} in access token. Trying next one if any.", aud, e);
}
}
}
throw new Exception("Invalid context in access token"); throw new Exception("Invalid context in access token");
} }
return scopeBean.toString();
@Override
public String getUsername() throws Exception {
return accessToken.getPreferredUsername();
} }
@Override @Override
public Map<String, String> getHTTPAuthorizationHeaders() { public Map<String, String> getHTTPAuthorizationHeaders() {
Map<String, String> authorizationHeaders = new HashMap<>(); Map<String, String> authorizationHeaders = new HashMap<>();
authorizationHeaders.put("Authorization", "Bearer " + token); authorizationHeaders.put("Authorization", "Bearer " + getTokenString());
return authorizationHeaders; return authorizationHeaders;
} }
public void setRenewalProvider(RenewalProvider renewalProvider) {
this.renewalProvider = renewalProvider;
}
public void setRefreshToken(RefreshToken refreshToken) {
this.refreshToken = refreshToken;
}
protected boolean isExpired(AccessToken accessToken) {
return Time.currentTimeMillis()>accessToken.getExp();
}
@Override
public boolean isExpired() {
return isExpired(accessToken);
}
@Override
public boolean isRefreshable() {
return refreshToken!=null && isExpired(refreshToken);
}
@Override
public Collection<String> getRoles() throws Exception {
return getAccessToken().getRealmAccess().getRoles();
}
@Override
public String getName() throws Exception {
return getAccessToken().getGivenName();
}
@Override
public String getSurname() throws Exception {
return getAccessToken().getFamilyName();
}
@Override
public String getEMail() throws Exception {
return getAccessToken().getEmail();
}
@Override
public String getTitle() throws Exception {
return "";
}
} }

View File

@ -1,5 +1,6 @@
package org.gcube.common.authorization.utils.secret; package org.gcube.common.authorization.utils.secret;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -112,8 +113,18 @@ public abstract class Secret implements Comparable<Secret> {
ScopeProvider.instance.reset(); ScopeProvider.instance.reset();
} }
public abstract boolean isExpired();
public abstract boolean isRefreshable();
public abstract Collection<String> getRoles() throws Exception;
public abstract String getName() throws Exception;
public abstract String getSurname() throws Exception;
public abstract String getEMail() throws Exception;
public abstract String getTitle() throws Exception;
} }

View File

@ -1,76 +0,0 @@
package org.gcube.common.authorization.utils.secret.jwt;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.gcube.com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.gcube.com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
public class JWToken {
@JsonProperty("aud")
private String context;
@JsonProperty("resource_access")
private Map<String, Roles> contextAccess = new HashMap<>();
@JsonProperty("preferred_username")
private String username;
@JsonProperty("given_name")
private String firstName;
@JsonProperty("family_name")
private String lastName;
@JsonProperty("email")
private String email;
public List<String> getRoles(){
return contextAccess.get(this.context).roles;
}
public String getContext() {
try {
return URLDecoder.decode(context, StandardCharsets.UTF_8.toString());
}catch (UnsupportedEncodingException e) {
return context;
}
}
public String getUsername() {
return username;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getEmail() {
return email;
}
@Override
public String toString() {
return "GcubeJwt [context=" + getContext() + ", roles=" + getRoles() + ", username=" + username
+ ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + "]";
}
public static class Roles {
@JsonProperty("roles")
List<String> roles = new ArrayList<>();
}
}