Beliebte Suchanfragen

Cloud Native

DevOps

IT-Security

Agile Methoden

Java

//

Project Nashorn – JavaScript on the JVM

29.6.2014 | 8 minutes of reading time

Suppose you have a password strength checker in your user interface to help users choose secure and easily remembered passwords. Since estimating password strength involves more than just checking character classes you use a library like Dropbox’s zxcvbn which avoids a few of the mistakes that ebay recently made . If you did this, then you are on the right track, but are you validating the password strength additionally on the server to reject weak passwords? It is common knowledge that one cannot trust user data and therefore needs to validate everything on the server. But how do you do this when the password strength check is written in JavaScript and the server is running a Java application?

Java 8 introduces a neat and performant way to solve this issue: Compile JavaScript to bytecode and execute it natively on the JVM! The eighth version of Java ships with a JavaScript engine named Nashorn. Nashorn is available through the JSR-223 scripting APIs and can be used in any Java 8 application with a few lines of code. The following listing shows the classic Hello World example with Nashorn. Note that it can be executed without any configuration or additionally libraries.

1ScriptEngineManager manager = new ScriptEngineManager();
2ScriptEngine engine = manager.getEngineByName("nashorn");
3 
4String js = "print('Hello World!');";
5engine.eval(js);

Short Introduction to JSR-223

JSR-223 is the specification request for scripting support on the Java platform. The specification describes how scripting engines such as Nashorn are located, retrieved and how bindings between variables in the scripting language and Java objects are created. Additionally, the specification also describes concepts such as script contexts, scopes and more.

A ScriptEngine is one of the core concepts that is being used to interact with the underlying language runtime. ScriptEngines can be retrieved via a ScriptEngineManager which in turn locates engines via a service provider mechanism. The service provider mechanism being used is java.util.ServiceLoader (JavaDoc ). This mechanism has been introduced with Java 6 and works by scanning the class path for configuration files. Java 8 ships with such a configuration file. This is the reason why Nashorn can be used without any manual configuration.

The Password Strength Check In Detail

Let’s take a detailed look on how one might implement the password example with Nashorn.

Step 1) Add the zxcvbn dependency

To implement the password check on the server side the zxcvbn library needs to be on the class path. This can be done quite easily with the help of the WebJars project. WebJars package client-side dependencies such as jQuery, Angular, zxcvbn and many more into JAR files. A dependency to such a JAR file can be managed with the standard Java tools, e.g. Maven, Ivy or Gradle. The following listing shows the necessary addition to the Maven pom.xml in order to use zxcvbn. Once added to the POM the file /META-INF/resources/webjars/zxcvbn/1.0/zxcvbn.js will be on the class path and can be read via standard Java mechanisms.

1<dependency>
2    <groupId>org.webjars</groupId>
3    <artifactId>zxcvbn</artifactId>
4    <version>1.0</version>
5</dependency>

Step 2) Run the Script

Now that the script is on the class path it can be executed with Nashorn. To run the script and check passwords, a few things need to do be done:

  • retrieve the Nashorn ScriptEngine via the ScriptEngineManager
  • evaluate the zxcvbn script in the context of the engine so that it registers itself globally as it would in a browser environment
  • run a password through the globally registered zxcvbn function and turn the return value into a Java POJO
1public class StrengthChecker {
2 
3  private static final String ZXCVBN_PATH = "/META-INF/resources/webjars/zxcvbn/1.0/zxcvbn.js";
4 
5  private final ScriptEngine engine;
6 
7  public StrengthChecker() {
8    // 1.
9    ScriptEngineManager manager = new ScriptEngineManager();
10    engine = manager.getEngineByName("nashorn");
11 
12    // 2.
13    Bindings engineScope = engine.getBindings(ScriptContext.ENGINE_SCOPE);
14    engineScope.put("window", engineScope);
15 
16    // 3.
17    try {
18      engine.eval(getResourceContents(ZXCVBN_PATH));
19    } catch (ScriptException e) {
20      throw new RuntimeException(e);
21    }
22  }
23 
24  public Strength check(String pw) {
25    try {
26      // 4.
27      Map<String, Object> result;
28      result = (Map<String, Object>) engine.eval("zxcvbn('" + pw + "');");
29 
30      // 5.
31      return new Strength(
32        ((Double) result.get("entropy")).intValue(),
33        (int) result.get("score"),
34        ((Double) result.get("crack_time")).intValue()
35      );
36    } catch (ScriptException e) {
37      throw new RuntimeException(e);
38    }
39  }
40 
41}

From top to bottom:

  1. a ScriptEngineManager is created and the Nashorn engine is retrieved by name. The engine is remembered as an instance variable so that it can be reused.
  2. zxcvbn has a few expectations for the environment in which it is running. By registering a global variable window that points to the engine scope a sufficient browser environment can be simulated.
  3. The zxcvbn file is evaluated with Nashorn. The global zxcvbn variable will be registered on the engine scope. The checked ScriptException is wrapped in a RuntimeException for the sake of this simple example.
  4. The previously registered function zxcvbn is called with the user provided password. The return value of engine.eval(...) is the return value of zxcvbn(...). Nashorn is smart enough to identify that zxcvbn is returning a JavaScript object. Sine a JavaScript object is effectively a map with keys of type String and arbitrary values, the return value can be casted to Map.
  5. Users of the StrengthChecker class should not know about the usage of JavaScript. StrengthChecker should behave like any other Java class and therefore returns a POJO instead of a Map.

A few lines of code are sufficient to integrate a JavaScript library into a Java application. StrengthChecker hides the usage of JavaScript from the rest of the application and shows how simple it can be to go polyglott on the JVM!

Some readers might have noticed that the implementation is broken and dangerous. The user provided password was handed to Nashorn via String concatenation and is therefore vulnerable to script injection. This vulnerability can be avoided by manipulating the engine’s scopes, i.e. setting the password as a value on the engine or global scope. How this can be done can be seen in the GitHub repository which contains this article’s source code.

Nashorn is only a JavaScript Engine

Nashorn is not the same as Node.js nor is it the same as the JavaScript environment in the browser. Nashorn is only a JavaScript engine, i.e. an implementation of the ECMAScript 5.1 language specification plus memory management. This means that global JavaScript functions such as setTimeout, setInterval or XMLHttpRequest do not exist in Nashorn. Neither does it have an event loop or a task queue. This means that many JavaScript libraries cannot be used with Nashorn because such libraries commonly expect a browser-like environment. When mapping these functions and concepts to the various specifications that make up a browser-like JavaScript environment, it becomes obvious why Nashorn is missing these.

When mapping JavaScript concepts to specifications it becomes clearer what makes up a JavaScript engine.

So Nashorn is only an implementation of ECMAScript. It does not implement the Timers section of the HTML 5 specification nor the XMLHttpRequest specification . This is a big difference to Node.js. Node.js adopted the browsers’ concepts of event loops and task queues to reduce the conceptual gap between server- and client-side JavaScript. Luckily, the Nashorn environment is very extensible. Scripts running in the Nashorn engine can manipulate the global scope and access standard Java APIs to extend the environment.

1var Timer = Java.type('java.util.Timer');
2var eventLoop = new Timer('jsEventLoop', false);
3 
4this.setTimeout = function(fn, millis /*, args... */) {
5  // ...
6 
7  eventLoop.schedule(function() {
8    // call fn with optional args
9  }, millis);
10 
11  // ...
12};

The first line retrieves a reference to the java.util.Timer class. It shows how simple it is to interact with standard and third-party Java libraries and frameworks. The Timer class itself is a “facility for threads to schedule tasks for future execution” (JavaDoc ) and thus a very simple event loop. The fourth line extends the global scope with a new function setTimeout(fn, timeoutMillis[, args...]) to implement a part of the HTML 5 specification.

Patching the global environment with setTimeout, setInterval and other global JavaScript functions is not a simple task. Luckily though an open source project called env.js exists that has exactly this goal: Simulating browser environments for various JavaScript engines. Since Nashorn is still relatively new, the only available version of env.js for Nashorn exists in the Nashorn issue tracker . In case you only need a trimmed down browser environment and cannot wait until the OpenJDK finishes the env.js implementation, you can also take a look at a small polyfill . It supports setTimeout, setInterval, the respective clearTimeout and clearInterval functions as well as some XMLHttpRequest features. Bear in mind though that the polyfill is not battle-proven (but it is small enough to fix issues without spending too much time)!

Conclusion

JavaScript is more and more becoming an ubiquitous language. All major browsers ship with blazingly fast JavaScript engines that are used create fantastic web experiences. Node.js adopted one of these engines, added low-level standard libraries and is gaining momentum. On top of that we now have a JavaScript engine that is part of the JVM which compiles JavaScript to bytecode. In the future we will even be able to use avatar.js to get a complete JavaScript environment with timer APIs, an event loop and Node.js compatibility for the JVM.

Future projects will be able to benefit from this development in various ways. Starting from simple things like validation to an increasing amount of business logic which could be modelled in JavaScript and shared between client and server. Movements such as offline first will become much more viable.

share post

Likes

0

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.