parent
834a276767
commit
fb81242658
3 changed files with 56 additions and 12 deletions
|
@ -24,6 +24,7 @@ import javax.script.ScriptEngineManager;
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
|
|
||||||
import org.keycloak.models.ScriptModel;
|
import org.keycloak.models.ScriptModel;
|
||||||
|
import org.keycloak.services.ServicesLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ScriptingProvider} that uses a {@link ScriptEngineManager} to evaluate scripts with a {@link ScriptEngine}.
|
* A {@link ScriptingProvider} that uses a {@link ScriptEngineManager} to evaluate scripts with a {@link ScriptEngine}.
|
||||||
|
@ -32,14 +33,10 @@ import org.keycloak.models.ScriptModel;
|
||||||
*/
|
*/
|
||||||
public class DefaultScriptingProvider implements ScriptingProvider {
|
public class DefaultScriptingProvider implements ScriptingProvider {
|
||||||
|
|
||||||
private final ScriptEngineManager scriptEngineManager;
|
private final DefaultScriptingProviderFactory factory;
|
||||||
|
|
||||||
DefaultScriptingProvider(ScriptEngineManager scriptEngineManager) {
|
DefaultScriptingProvider(DefaultScriptingProviderFactory factory) {
|
||||||
if (scriptEngineManager == null) {
|
this.factory = factory;
|
||||||
throw new IllegalStateException("scriptEngineManager must not be null!");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scriptEngineManager = scriptEngineManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -69,7 +66,7 @@ public class DefaultScriptingProvider implements ScriptingProvider {
|
||||||
throw new IllegalArgumentException("script must not be null or empty");
|
throw new IllegalArgumentException("script must not be null or empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptEngine engine = createPreparedScriptEngine(scriptModel);
|
ScriptEngine engine = getPreparedScriptEngine(scriptModel);
|
||||||
|
|
||||||
if (engine instanceof Compilable) {
|
if (engine instanceof Compilable) {
|
||||||
return new CompiledEvaluatableScriptAdapter(scriptModel, tryCompile(scriptModel, (Compilable) engine));
|
return new CompiledEvaluatableScriptAdapter(scriptModel, tryCompile(scriptModel, (Compilable) engine));
|
||||||
|
@ -99,13 +96,26 @@ public class DefaultScriptingProvider implements ScriptingProvider {
|
||||||
/**
|
/**
|
||||||
* Looks-up a {@link ScriptEngine} with prepared {@link Bindings} for the given {@link ScriptModel Script}.
|
* Looks-up a {@link ScriptEngine} with prepared {@link Bindings} for the given {@link ScriptModel Script}.
|
||||||
*/
|
*/
|
||||||
private ScriptEngine createPreparedScriptEngine(ScriptModel script) {
|
private ScriptEngine getPreparedScriptEngine(ScriptModel script) {
|
||||||
|
// Try to lookup shared engine in the cache first
|
||||||
|
if (factory.isEnableScriptEngineCache()) {
|
||||||
|
ScriptEngine scriptEngine = factory.getScriptEngineCache().get(script.getMimeType());
|
||||||
|
if (scriptEngine != null) return scriptEngine;
|
||||||
|
}
|
||||||
|
|
||||||
ScriptEngine scriptEngine = lookupScriptEngineFor(script);
|
ScriptEngine scriptEngine = lookupScriptEngineFor(script);
|
||||||
|
|
||||||
if (scriptEngine == null) {
|
if (scriptEngine == null) {
|
||||||
throw new IllegalStateException("Could not find ScriptEngine for script: " + script);
|
throw new IllegalStateException("Could not find ScriptEngine for script: " + script);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServicesLogger.LOGGER.scriptEngineCreated(scriptEngine.getFactory().getEngineName(), scriptEngine.getFactory().getEngineVersion(), script.getMimeType());
|
||||||
|
|
||||||
|
// Nashorn scriptEngine is ok to cache and share across multiple threads
|
||||||
|
if (factory.isEnableScriptEngineCache()) {
|
||||||
|
factory.getScriptEngineCache().put(script.getMimeType(), scriptEngine);
|
||||||
|
}
|
||||||
|
|
||||||
return scriptEngine;
|
return scriptEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +126,7 @@ public class DefaultScriptingProvider implements ScriptingProvider {
|
||||||
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
ClassLoader cl = Thread.currentThread().getContextClassLoader();
|
||||||
try {
|
try {
|
||||||
Thread.currentThread().setContextClassLoader(DefaultScriptingProvider.class.getClassLoader());
|
Thread.currentThread().setContextClassLoader(DefaultScriptingProvider.class.getClassLoader());
|
||||||
return scriptEngineManager.getEngineByMimeType(script.getMimeType());
|
return factory.getScriptEngineManager().getEngineByMimeType(script.getMimeType());
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
Thread.currentThread().setContextClassLoader(cl);
|
Thread.currentThread().setContextClassLoader(cl);
|
||||||
|
|
|
@ -16,10 +16,16 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.scripting;
|
package org.keycloak.scripting;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCWellKnownProviderFactory;
|
||||||
|
|
||||||
|
import javax.script.ScriptEngine;
|
||||||
import javax.script.ScriptEngineManager;
|
import javax.script.ScriptEngineManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,20 +33,40 @@ import javax.script.ScriptEngineManager;
|
||||||
*/
|
*/
|
||||||
public class DefaultScriptingProviderFactory implements ScriptingProviderFactory {
|
public class DefaultScriptingProviderFactory implements ScriptingProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(DefaultScriptingProviderFactory.class);
|
||||||
|
|
||||||
static final String ID = "script-based-auth";
|
static final String ID = "script-based-auth";
|
||||||
|
|
||||||
private ScriptEngineManager scriptEngineManager;
|
private ScriptEngineManager scriptEngineManager;
|
||||||
|
|
||||||
|
private boolean enableScriptEngineCache;
|
||||||
|
|
||||||
|
// Key is mime-type. Value is engine for the particular mime-type. Cache can be used when the scriptEngine can be shared across multiple threads / requests (which is the case for nashorn)
|
||||||
|
private Map<String, ScriptEngine> scriptEngineCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScriptingProvider create(KeycloakSession session) {
|
public ScriptingProvider create(KeycloakSession session) {
|
||||||
lazyInit();
|
lazyInit();
|
||||||
|
|
||||||
return new DefaultScriptingProvider(scriptEngineManager);
|
return new DefaultScriptingProvider(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
//NOOP
|
this.enableScriptEngineCache = config.getBoolean("enable-script-engine-cache", true);
|
||||||
|
logger.debugf("Enable script engine cache: %b", this.enableScriptEngineCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptEngineManager getScriptEngineManager() {
|
||||||
|
return scriptEngineManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEnableScriptEngineCache() {
|
||||||
|
return enableScriptEngineCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, ScriptEngine> getScriptEngineCache() {
|
||||||
|
return scriptEngineCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -63,6 +89,9 @@ public class DefaultScriptingProviderFactory implements ScriptingProviderFactory
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (scriptEngineManager == null) {
|
if (scriptEngineManager == null) {
|
||||||
scriptEngineManager = new ScriptEngineManager();
|
scriptEngineManager = new ScriptEngineManager();
|
||||||
|
if (enableScriptEngineCache) {
|
||||||
|
scriptEngineCache = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -462,4 +462,9 @@ public interface ServicesLogger extends BasicLogger {
|
||||||
@LogMessage(level = ERROR)
|
@LogMessage(level = ERROR)
|
||||||
@Message(id=105, value="Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted")
|
@Message(id=105, value="Response_mode 'query.jwt' is allowed only when the authorization response token is encrypted")
|
||||||
void responseModeQueryJwtNotAllowed();
|
void responseModeQueryJwtNotAllowed();
|
||||||
|
|
||||||
|
@LogMessage(level = INFO)
|
||||||
|
@Message(id=106, value="Created script engine '%s', version '%s' for the mime type '%s'")
|
||||||
|
@Once
|
||||||
|
void scriptEngineCreated(String engineName, String engineVersion, String mimeType);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue