Added token refresh
This commit is contained in:
parent
6da43964b5
commit
452e78139f
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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("", "");
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -82,6 +82,17 @@ public class SecretHolder {
|
|||
}
|
||||
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() {
|
||||
for(Secret secret : secrets) {
|
||||
|
@ -92,5 +103,49 @@ 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 "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.gcube.common.authorization.utils.manager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
|
||||
|
@ -16,8 +17,6 @@ public class SecretManager {
|
|||
|
||||
public static final InheritableThreadLocal<SecretManager> instance = new InheritableThreadLocal<SecretManager>() {
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected SecretManager initialValue() {
|
||||
return new SecretManager();
|
||||
|
@ -92,6 +91,10 @@ public class SecretManager {
|
|||
return currentSecretHolder.getContext();
|
||||
}
|
||||
|
||||
public Collection<String> getRoles() {
|
||||
return currentSecretHolder.getRoles();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
initialSecretHolder.reset();
|
||||
if(initialSecretHolder!=currentSecretHolder) {
|
||||
|
@ -99,4 +102,20 @@ public class SecretManager {
|
|||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
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.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.UserInfo;
|
||||
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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -21,15 +29,46 @@ public class JWTSecret extends Secret {
|
|||
|
||||
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) {
|
||||
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
|
||||
public void setToken() throws Exception {
|
||||
AccessTokenProvider.instance.set(token);
|
||||
AccessTokenProvider.instance.set(getTokenString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,26 +76,26 @@ public class JWTSecret extends Secret {
|
|||
AccessTokenProvider.instance.reset();
|
||||
}
|
||||
|
||||
protected JWToken getJWToken() throws Exception {
|
||||
if(jwt==null) {
|
||||
protected AccessToken getAccessToken() throws Exception {
|
||||
if(accessToken==null) {
|
||||
String realUmaTokenEncoded = token.split("\\.")[1];
|
||||
String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes()));
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
try {
|
||||
jwt = mapper.readValue(realUmaToken, JWToken.class);
|
||||
accessToken = mapper.readValue(realUmaToken, AccessToken.class);
|
||||
}catch(Exception e){
|
||||
logger.error("Error parsing JWT token",e);
|
||||
throw new Exception("Error parsing JWT token", e);
|
||||
}
|
||||
}
|
||||
return jwt;
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ClientInfo getClientInfo() throws Exception {
|
||||
getJWToken();
|
||||
ClientInfo clientInfo = new UserInfo(jwt.getUsername(), jwt.getRoles(), jwt.getEmail(), jwt.getFirstName(), jwt.getLastName());
|
||||
getAccessToken();
|
||||
List<String> roles = new ArrayList<>(accessToken.getRealmAccess().getRoles());
|
||||
ClientInfo clientInfo = new UserInfo(accessToken.getPreferredUsername(), roles, accessToken.getEmail(), accessToken.getGivenName(), accessToken.getFamilyName());
|
||||
return clientInfo;
|
||||
}
|
||||
|
||||
|
@ -68,21 +107,78 @@ public class JWTSecret extends Secret {
|
|||
|
||||
@Override
|
||||
public String getContext() throws Exception {
|
||||
ScopeBean scopeBean = null;
|
||||
try {
|
||||
scopeBean = new ScopeBean(getJWToken().getContext());
|
||||
}catch(Exception e){
|
||||
logger.error("Invalid context in access token",e);
|
||||
throw new Exception("Invalid context in access token");
|
||||
String context = null;
|
||||
String[] audience = getAccessToken().getAudience();
|
||||
for (String aud : audience) {
|
||||
if (aud != null && aud.compareTo("") != 0) {
|
||||
try {
|
||||
context = URLDecoder.decode(context, StandardCharsets.UTF_8.toString());
|
||||
ScopeBean scopeBean = new ScopeBean(context);
|
||||
return scopeBean.toString();
|
||||
} catch (Exception e) {
|
||||
logger.error("Invalid context name for audience {} in access token. Trying next one if any.", aud, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return scopeBean.toString();
|
||||
throw new Exception("Invalid context in access token");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() throws Exception {
|
||||
return accessToken.getPreferredUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getHTTPAuthorizationHeaders() {
|
||||
Map<String, String> authorizationHeaders = new HashMap<>();
|
||||
authorizationHeaders.put("Authorization", "Bearer " + token);
|
||||
authorizationHeaders.put("Authorization", "Bearer " + getTokenString());
|
||||
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 "";
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package org.gcube.common.authorization.utils.secret;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -112,8 +113,18 @@ public abstract class Secret implements Comparable<Secret> {
|
|||
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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue