de.cidaas.oauth.interceptor.TokenHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cidaas-interceptor-java Show documentation
Show all versions of cidaas-interceptor-java Show documentation
Interceptor for Cidaas Java Clients
package de.cidaas.oauth.interceptor;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.annotation.security.RolesAllowed;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.protocol.HTTP;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import de.cidaas.jwt.JWT;
import de.cidaas.jwt.Options;
import de.cidaas.oauth.cache.LRUCache;
import de.cidaas.oauth.model.ResolvedUserInfoFromToken;
import de.cidaas.oauth.model.TokenCheckEntity;
public class TokenHelper {
private static final Logger LOG = LoggerFactory.getLogger(TokenHelper.class);
LRUCache tokenCache = LRUCache.getInstance();
public TokenCheckEntity validateAccessToken(TokenCheckEntity tokenInfo, Method method, String access_token) {
if (StringUtils.isEmpty(access_token)) {
tokenInfo.setSuccess(false);
return tokenInfo;
}
if (access_token.contains(".")) {
LOG.info("JWT Token Try to resolve from local");
String[] pieses = access_token.split("\\.");
if (pieses.length == 5 && StringUtils.isEmpty(Constants.get_private_key_path())) {
return validateAccessTokenFromServer(tokenInfo, method, access_token);
} else {
return validateAccessTokenLocal(tokenInfo, method, access_token);
}
} else {
return validateAccessTokenFromServer(tokenInfo, method, access_token);
}
}
private TokenCheckEntity validateAccessTokenFromServer(TokenCheckEntity tokenInfo, Method method,
String accessToken) {
ResolvedUserInfoFromToken userInfo = null;
try {
userInfo = getUserInfoByToken(tokenInfo);
if (userInfo == null) {
LOG.info("No userid found for accesstoken, {} ", accessToken);
tokenInfo.setSuccess(false);
return tokenInfo;
}
tokenInfo.setUserId(userInfo.getUserId());
tokenInfo.setClientId(userInfo.getClientId());
tokenCache.put(accessToken.hashCode(), new Date().getTime());
} catch (HttpException | IOException ex) {
LOG.error("OAuth-Exception ", ex);
tokenInfo.setSuccess(false);
return tokenInfo;
} catch (Exception ex) {
LOG.error("Exception ", ex);
tokenInfo.setSuccess(false);
return tokenInfo;
}
tokenInfo.setSuccess(true);
tokenInfo.setNeedServerSubmit(false);
return tokenInfo;
}
private TokenCheckEntity validateAccessTokenLocal(TokenCheckEntity tokenInfo, Method method, String access_token) {
Map claims = parseAccessToken(access_token);
if (claims == null) {
LOG.info("claims cannot be null ");
tokenInfo.setSuccess(false);
tokenInfo.setRequestedScopes(getAnotationRequestedScopeJoined(method));
tokenInfo.setRequestedRoles(getAnotationRequestedRolesJoined(method));
return tokenInfo;
}
Object exp = null;
if (claims.containsKey("exp")) {
exp = claims.get("exp").toString();
}
if (exp == null) {
tokenInfo.setSuccess(false);
LOG.info("exp cannot be null ");
return tokenInfo;
}
boolean is_jwt_latest = false;
if (claims.containsKey("is_jwt_latest")) {
is_jwt_latest = true;
}
if (!validateToken(exp, is_jwt_latest)) {
tokenInfo.setSuccess(false);
return tokenInfo;
}
String userId = null;
if (claims.containsKey("sub")) {
userId = claims.get("sub").toString();
}
if (StringUtils.isEmpty(userId)) {
LOG.info("sub cannot be null ");
tokenInfo.setSuccess(false);
return tokenInfo;
}
tokenInfo.setUserId(userId);
// check for the cache time , if not resolve it from server.
if (!tokenCache.isPresent(access_token)) {
return validateAccessTokenFromServer(tokenInfo, method, access_token);
}
String clientId = null;
if (claims.containsKey("clientid")) {
clientId = claims.get("clientid").toString();
}
if (StringUtils.isEmpty(clientId)) {
LOG.info("clientid cannot be null ");
tokenInfo.setSuccess(false);
return tokenInfo;
}
tokenInfo.setClientId(clientId);
List scopesAllowedInToken = new ArrayList<>();
String scopes = "";
String scopeKey = "scope";
if (claims.containsKey("is_jwt_latest")) {
scopeKey = "scopes";
}
if (claims.containsKey(scopeKey)) {
Object val = claims.get(scopeKey);
if (val != null) {
if (val instanceof List) {
scopesAllowedInToken = (List) val;
scopes = String.join(" ", scopesAllowedInToken);
} else {
String castedVal = (String) val;
if (StringUtils.isNotEmpty(castedVal)) {
String[] scopesArray = castedVal.split(" ");
scopes = String.join(" ", scopesArray);
scopesAllowedInToken = Arrays.asList(scopesArray);
}
}
}
}
String[] scopesRequired = getAnotationRequestedScope(method);
if (scopesRequired != null && scopesRequired.length > 0) {
tokenInfo.setRequestedScopes(getAnotationRequestedScopeJoined(method));
tokenInfo.setAllowedScopes(scopes);
if (!validateScope(scopesRequired, scopesAllowedInToken)) {
LOG.info("Scope validation failed, Requested scopes {}, Allowed Scopes {} ",
tokenInfo.getRequestedScopes(), tokenInfo.getAllowedScopes());
tokenInfo.setSuccess(false);
return tokenInfo;
}
}
List rolesAllowedInToken = new ArrayList<>();
String roles = "";
String roleKey = "role";
if (claims.containsKey("is_jwt_latest")) {
roleKey = "roles";
}
if (claims.containsKey(roleKey)) {
Object val = claims.get(roleKey);
if (val != null) {
if (val instanceof List) {
rolesAllowedInToken = (List) val;
roles = String.join(",", rolesAllowedInToken);
} else {
String castedVal = (String) val;
if (StringUtils.isNotEmpty(castedVal)) {
String[] roleArray = castedVal.split(",");
roles = String.join(",", roleArray);
rolesAllowedInToken = Arrays.asList(roleArray);
}
}
}
}
String[] rolesRequired = getAnotationRequestedRoles(method);
if (rolesRequired != null && rolesRequired.length > 0) {
tokenInfo.setRequestedRoles(getAnotationRequestedRolesJoined(method));
tokenInfo.setAllowedRoles(roles);
if (!validateRole(rolesRequired, rolesAllowedInToken)) {
LOG.info("Role validation failed, Requested Roles {}, Allowed Roles {} ", tokenInfo.getRequestedRoles(),
tokenInfo.getAllowedRoles());
tokenInfo.setSuccess(false);
return tokenInfo;
}
}
tokenInfo.setSuccess(true);
return tokenInfo;
}
protected String getAnotationRequestedScopeJoined(Method method) {
String[] scopes = getAnotationRequestedScope(method);
if (scopes != null && scopes.length > 0) {
return StringUtils.join(getAnotationRequestedScope(method), " ");
}
return null;
}
protected String[] getAnotationRequestedScope(Method method) {
if (method.isAnnotationPresent(OAuthScopes.class)) {
OAuthScopes oAuthScopes = method.getAnnotation(OAuthScopes.class);
if (oAuthScopes != null && oAuthScopes.scopes().length > 0) {
return oAuthScopes.scopes();
}
}
return null;
}
protected String getAnotationRequestedRolesJoined(Method method) {
String[] roles = getAnotationRequestedRoles(method);
if (roles != null && roles.length > 0) {
return StringUtils.join(roles, ",");
}
return null;
}
protected String[] getAnotationRequestedRoles(Method method) {
if (method.isAnnotationPresent(RolesAllowed.class)) {
RolesAllowed rolesAnnotation = method.getAnnotation(RolesAllowed.class);
if (rolesAnnotation != null && rolesAnnotation.value().length > 0) {
return rolesAnnotation.value();
}
}
return null;
}
public boolean validateToken(Object exp, boolean jwtlatest) {
long expireTime = 0;
if (exp instanceof Date) {
expireTime = ((Date) exp).getTime();
} else {
String _exp = (String) exp;
try {
Long expSeconds = Long.parseLong(_exp);
if (jwtlatest) {
// hack for to support old token exp time
try {
Date tempDate = new Date(expSeconds);
if (tempDate.getYear() > 2017) {
expSeconds = expSeconds / 1000l;
}
} catch (Exception e) {
}
expireTime = expSeconds * 1000;
} else {
expireTime = expSeconds;
}
} catch (Exception e) {
}
}
if (expireTime == 0) {
return false;
}
long currentMillisec = System.currentTimeMillis();
if (currentMillisec >= expireTime) {
return false;
}
return true;
}
private Map parseAccessToken(String access_token) {
String[] pieses = access_token.split("\\.");
if (pieses.length == 3) {
// It is JWT or JWS token
try {
return JWT.parsePlainJWT(access_token);
} catch (Exception e) {
LOG.info("Error while parsing the Plain JWT token, Error : {}", e);
}
} else if (pieses.length == 5) {
// It is JWE token
try {
String key_file_path = Constants.get_private_key_path();
if (StringUtils.isNotEmpty(key_file_path)) {
Options options = new Options();
options.setJWEToken(true);
options.setPrivateKeyPath(key_file_path);
return JWT.decodeJWT(access_token, options);
}
} catch (Exception e) {
LOG.info("Error while parsing the Plain JWT token, Error : {}", e);
}
}
return null;
}
private boolean validateScope(String[] scopesNeededForMethod, List scopesAllowedInToken) {
// Check if the method need scope, if not allow them for next methods
if (scopesNeededForMethod == null || scopesNeededForMethod.length == 0) {
return true;
}
// Check if the scopes allowed in the access token if is empty don't
// allow for next step. since we checked already the above 3 lines for
// scope required.
if (scopesAllowedInToken == null || scopesAllowedInToken.size() == 0) {
return false;
}
// Check if the scope allowed in access token contains openid, if yes
// allow further without checking further scopes
if (scopesAllowedInToken.contains("openid")) {
return true;
}
// Check if the required scope allowed in the access token
boolean allowFurtherSteps = false;
for (String scopeNeeded : scopesNeededForMethod) {
if (scopesAllowedInToken.stream().filter(c -> c.equalsIgnoreCase(scopeNeeded)).findFirst().isPresent()) {
allowFurtherSteps = true;
break;
}
}
return allowFurtherSteps;
}
private boolean validateRole(String[] rolesNeededForMethod, List rolesAllowedInToken) {
// Check if the method need role, if not allow them for next methods
if (rolesNeededForMethod == null || rolesNeededForMethod.length == 0) {
return true;
}
// Check if the roles allowed in the access token if is empty don't
// allow for next step. since we checked already the above 3 lines for
// role required.
if (rolesAllowedInToken == null || rolesAllowedInToken.size() == 0) {
return false;
}
// Check if the required role allowed in the access token
boolean allowFurtherSteps = false;
for (String roleNeeded : rolesNeededForMethod) {
if (rolesAllowedInToken.stream().filter(c -> c.equalsIgnoreCase(roleNeeded)).findFirst().isPresent()) {
allowFurtherSteps = true;
break;
}
}
return allowFurtherSteps;
}
private ResolvedUserInfoFromToken getUserInfoByToken(TokenCheckEntity tokenInfo)
throws HttpException, ClientProtocolException, IOException, URISyntaxException {
try {
ObjectMapper om = new ObjectMapper();
String uri = Constants.get_user_info_by_token();
HttpPost resetPost = new HttpPost(new URI(uri));
resetPost.addHeader(Constants.get_tokenKey(), tokenInfo.getAccessToken());
if (tokenInfo != null) {
StringEntity se = new StringEntity(om.writeValueAsString(tokenInfo), ContentType.APPLICATION_JSON);
resetPost.setEntity(se);
resetPost.addHeader(HTTP.CONTENT_TYPE, "application/json; charset=UTF-8");
}
HttpResponse response = HttpClientBuilder.create().build().execute(resetPost);
int responseStatusCode = response.getStatusLine().getStatusCode();
LOG.info("User Info By Token Status Code:" + responseStatusCode);
if (responseStatusCode == HttpStatus.SC_OK) {
om = om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
om = om.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
return om.readValue(response.getEntity().getContent(), ResolvedUserInfoFromToken.class);
} else {
LOG.error("User id by token fails. URL : " + uri);
}
} catch (Exception ex) {
LOG.error("Exception at getUserInfoByToken {}", ex);
throw ex;
}
return null;
}
}