com.networknt.apikey.ApiKeyHandler Maven / Gradle / Ivy
package com.networknt.apikey;
import com.networknt.config.Config;
import com.networknt.handler.Handler;
import com.networknt.handler.MiddlewareHandler;
import com.networknt.utility.HashUtil;
import com.networknt.utility.ModuleRegistry;
import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
/**
* For some legacy applications to migrate from the monolithic gateway to light-gateway without changing
* any code, we need to support the API Key authentication on the light-gateway(LG) or light-client-proxy(LCP)
* to authenticate the consumer and then change the authentication from API Key to OAuth 2.0 for downstream
* API access.
*
* Only certain paths will have API Key set up and the header name for each application might be different. To
* support all use cases, we add a list of maps to the configuration apikey.yml to pathPrefixAuths property.
*
* Each config item will have pathPrefix, headerName and apiKey. The handler will try to match the path prefix
* first and then get the input API Key from the header. After compare with the configured API Key, the handler
* will return either ERR10057 API_KEY_MISMATCH or pass the control to the next handler in the chain.
*
* @author Steve Hu
*/
public class ApiKeyHandler implements MiddlewareHandler {
static final Logger logger = LoggerFactory.getLogger(ApiKeyHandler.class);
static final String API_KEY_MISMATCH = "ERR10075";
static ApiKeyConfig config;
private volatile HttpHandler next;
public ApiKeyHandler() {
if(logger.isTraceEnabled()) logger.trace("ApiKeyHandler is loaded.");
config = ApiKeyConfig.load();
}
/**
* This is a constructor for test cases only. Please don't use it.
* @param cfg BasicAuthConfig
*/
@Deprecated
public ApiKeyHandler(ApiKeyConfig cfg) {
config = cfg;
if(logger.isInfoEnabled()) logger.info("ApiKeyHandler is loaded.");
}
@Override
public HttpHandler getNext() {
return next;
}
@Override
public MiddlewareHandler setNext(HttpHandler next) {
Handlers.handlerNotNull(next);
this.next = next;
return this;
}
@Override
public boolean isEnabled() {
return config.isEnabled();
}
@Override
public void register() {
// As apiKeys are in the config file, we need to mask them.
List masks = new ArrayList<>();
// if hashEnabled, there is no need to mask in the first place.
if(!config.hashEnabled) {
masks.add("apiKey");
}
ModuleRegistry.registerModule(ApiKeyConfig.CONFIG_NAME, ApiKeyHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(ApiKeyConfig.CONFIG_NAME), masks);
}
@Override
public void reload() {
config.reload();
List masks = new ArrayList<>();
if(!config.hashEnabled) {
masks.add("apiKey");
}
ModuleRegistry.registerModule(ApiKeyConfig.CONFIG_NAME, ApiKeyHandler.class.getName(), Config.getNoneDecryptedInstance().getJsonMapConfigNoCache(ApiKeyConfig.CONFIG_NAME), masks);
if(logger.isInfoEnabled()) logger.info("ApiKeyHandler is reloaded.");
}
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if(logger.isDebugEnabled()) logger.debug("ApiKeyHandler.handleRequest starts.");
String requestPath = exchange.getRequestPath();
if(handleApiKey(exchange, requestPath)) {
if(logger.isDebugEnabled()) logger.debug("ApiKeyHandler.handleRequest ends.");
// only goes to the next handler the APIKEY verification is passed successfully.
Handler.next(exchange, next);
}
}
public boolean handleApiKey(HttpServerExchange exchange, String requestPath) {
if(logger.isTraceEnabled()) logger.trace("requestPath = " + requestPath);
if (config.getPathPrefixAuths() != null) {
boolean matched = false;
boolean found = false;
// iterate all the ApiKey entries to find if any of them matches the request path.
for(ApiKey apiKey: config.getPathPrefixAuths()) {
if(requestPath.startsWith(apiKey.getPathPrefix())) {
found = true;
// found the matched prefix, validate the apiKey by getting the header and compare.
String k = exchange.getRequestHeaders().getFirst(apiKey.getHeaderName());
if(config.hashEnabled) {
// hash the apiKey and compare with the one in the config.
try {
matched = HashUtil.validatePassword(k.toCharArray(), apiKey.getApiKey());
if(matched) {
if (logger.isTraceEnabled()) logger.trace("Found valid apiKey with prefix = " + apiKey.getPathPrefix() + " headerName = " + apiKey.getHeaderName());
break;
}
} catch (Exception e) {
// there is no way to get here as the validatePassword will not throw any exception.
logger.error("Exception:", e);
}
} else {
// if not hash enabled, then compare the apiKey directly.
if(apiKey.getApiKey().equals(k)) {
if (logger.isTraceEnabled()) logger.trace("Found matched apiKey with prefix = " + apiKey.getPathPrefix() + " headerName = " + apiKey.getHeaderName());
matched = true;
break;
}
}
}
}
if(!found) {
// the request path is no in the configuration, consider pass and go to the next handler.
return true;
}
if(!matched) {
// at this moment, if not matched, then return an error message.
logger.error("Could not find matched APIKEY for request path " + requestPath);
setExchangeStatus(exchange, API_KEY_MISMATCH, requestPath);
if(logger.isDebugEnabled()) logger.debug("ApiKeyHandler.handleRequest ends with an error.");
exchange.endExchange();
return false;
}
}
return true;
}
}