The org.bzdev.ejws package
The org.bzdev.ejws package provides a class for
configuring instances of HttpServer
or HttpsServer, and several classes
that are useful for implementing an embedded web server.
The following sections describe how this package is used:
- Constructors.
- Authenticators.
- Adding Contexts.
- Built-in WebMap subclasses.
- Certificate managers and SSLSetup.
- Certificate creation.
- Coding example.
Constructors
The class EmbeddedWebServer will
configure an HttpServer or HttpsServer, including setting up a
thread pool. Its Constructors use various combinations of the
following arguments:
- addr. The internet address (some constructors do not provide this explicitly, in which case the wildcard address is used).
- port. The TCP port to use.
- backlog. The TCP backlog.
- nthreads. The number of threads the server uses.
- sslSetup. An object whose type
is
EmbeddedWebServer.SSLSetupused to configure SSL for HTTPS. - certManager. An object whose type
is
CertManagerused to configure SSL for HTTPS.
CertManager can be used to automatically
renew SSL certificates if an appropriate provider is available.
Examples:
and for the SSL case,EmbeddedWebServer ews = new EmbeddedWebServer(8080, 48, 8);
EmbeddedWebServer ews = new EmbeddedWebServer(8080, 48, 8, new EmbeddedWebServer.SSLSetup("TLS") .keystore(new FileInputStream("ks.jks"));
Authenticators
Two authenticators are provided by EJWS:
EjwsBasicAuthenticator. This is an abstract class with a single method to implement:checkCredentials, which checks that a given password is the one provided for a given user name. The simplest implementations use aMapto look up the password for a user and compares that to the given one.EjwsSecureBasicAuth. This class implements secure basic authentication, which uses public-key encryption to generate a one-time password that is resistant to man in the middle attacks.
BiConsumer objects whose "accept" methods
are called
- when a login request succeeds.
- when an an authentication request has succeeded while already logged in.
- when a logout request is seen.
BiConsumer has two type parameter:
-
EjwsPrincipal. This is the type of the {link java.util.function.BiConsumer BiConsumer}'sBiConsumer.accept(T, U)method's first argument. -
HttpExchange. This is the type of theBiConsumer'sBiConsumer.accept(T, U)method's second argument.
BiConsumer.accept(T, U)
methods will be called in each HTTP/HTTPS transaction. Typically,
authenticators should be created before prefixes and contexts are
added. Examples:
andEjwsBasicAuthenticator auth = new EjwsBasicAuthenticator("realm"); auth.add("user", "password");
EjwsBasicAuthenticator auth = new EjwsSecureBasicAuthenticator("realm" ews.getCertificates()); // These strings can be created using the program sbl. String user = "user" String publicKey = [user's public key in PEM format] String password = "password", auth.add(user, publicKey, password);
One can also add functions to call at a login, logout, or when authentication is successful:
auth.setLoginFunction((p,t) -> { System.out.println(p.getName() + " logged in"); }; auth.setLogoutFunction((p,t) -> { System.out.println(p.getName() + " logged out"); }; auth.setAuthorizedFunction((p,t) -> { System.out.println(p.getName() + " authorized"); };
Adding contexts
After a server is created one will create instances of
HttpHandler and associate each with
with a prefix (the initial portion of a URL's path). These methods
are named "add", where
- the method's first argument is the prefix.
- the method's second argument is either an
HttpHandler, aStringnaming a subclass ofWebMap, or the class of a subclass ofWebMap. In the latter two cases, the third argument is an object that will be passed to a WebMap's constructor, and the "add" method will return the WebMap that was just created. The packageorg.bzdev.ejws.mapscontain several subclasses ofWebMapcovering common cases. These are described below. AnHttpHandlerwill also be created&emdash;an instance ofFileHandler. - the method's third argument, or forth argument when the third
argument is used to initialize a WebMap, is an instance of
Authenticatoror null if authentication is not needed.
WebMap provide
operations that map URLs to resources such as files in a file
system, entries in a ZIP file, or a light-weight servlet
implementation. The class WebMap
contains several methods that return their instance so that methods
can be chained together for convenience. These are
-
setLoginAlias(String alias, String target, boolean required),setLoginAlias(String alias, URI target, boolean required), and several variants with fewer arguments that provide defaults. The arguments are- alias—a relative, non-hierarchical path from the WebMap's context indicating that a login is requested. There may be no corresponding resource.
- target—a relative path from the WebMap's context to the page that should be visited after a successful login
- required—true if all logins must start by visiting the page indicated by the alias.
-
setLogoutAlias(String alias, URI target). The arguments are- alias—a relative, non-hierarchical path from the WebMap's context indicating that a logout is requested. There may be no corresponding resource.
- target—a relative path from the WebMap's context to the page that should be visited after a logout.
-
addWelcome(String path)—add a "welcome" page. The argument is a path starting from the base URL for the WebMap to a welcome page. Multiple welcome pages can be added. -
addMapping(String suffix, String mtype)—associate a media type with a file-name suffix or extension. The first argument is the file-name extension (an initial period will be ignored), and the second argument is the media type (MIME type). -
addGzipSuffix(String suffix)—indicate that a suffix implies the use of GZIP compression. The argument, suffix is a suffix indicating that a file was compressed using GZIP. if a resource does not exist and adding such a suffix will match a resource that does exist, then that compressed version is sent and the headers are set to indicate that the recipient should automatically decompress the resource. The standard suffix is "gz" but it may be desirable in some cases to add additional ones (e.g., "gzip"). This allows compressed text files to be stored on a server and transferred while still compressed, and then automatically decompressed by a client's HTTP implementation.
The classews.add("/", DirWebMap.class, new File("api"), auth, true, true, true); .setLoginAlias("login.html", "index.html", true) .setLogoutAlias("logout.html", "http://example.com") .addMapping("sbl", "application/vnd.bzdev.sblauncher") .addWelcome("index.html") .addGZipSuffix("gzip");
EmbeddedWebServer also includes
partial support for WAR (Web ARchive) files and JSP (Java Server
Pages). For WAR files, the WEB-INF/web.xml file is processed to find
encodings for pages, media-types (MIME types) for file-name
extensions, error pages for particular error codes or exceptions, and
a welcome-file list. The use of the web.xml file is describe in the
documentation for WebMap. JSP support is really
minimal and is restricted to processing error messages. This is also
describe in the documentation for WebMap.
Built-in WebMap subclasses
DirWebMap. This WebMap associates paths in a URL with files in a directory and its subdirectories. The constructor's argument is either an instance ofFilefor a directory or an instance ofDirWebMap.Config, whose constructor's arguments include aFileand some strings representing various colors used when directories are displayed.PropertiesWebMap. This WebMap associates paths in a URL with resources stored in aPropertiesinstance. The constructor's argument is either aPropertiesinstance or an instance ofPropertiesWebMap.Config, whose constructor's arguments include an instance ofPropertiesand some strings representing various colors used when directories are displayed.RedirectWebMap. This WebMap will redirect its base path to URL representing another base path.ResourceWebMap.This WebMap provides access to resources on an applications class path. The base path will be mapped to the initial portion of a resource's path, and the remainder of the path provided in a URL will be appended. The constructor's argument is either this path prefix or an instance ofResourceWebMap.Config, which provides that path and a series of colors.ServletWebMap. This WebMap configures a light-weight Servlet— an instance ofServletAdapter. TheServletAdapterclass is more or less a simplified Servlet implementation, providing the methods needed for the simplest cases. TheServletWebMapconstructor's argument is an instance ofServletWebMap.Config, whose arguments include an instance ofServletAdapter, aMapproviding initialization parameters, a boolean flag indicating if theServletAdaptercan process HTTP queries, and the HTTP methods theServletAdaptercan process.URLWebMap. This WebMap will obtain a resource by fetching it from another server. The constructor's argument is either a URL or an instance ofURLWebMap.Params, which provides a base URL and indicates if HTTPS requests should not be verified. The portion of a path following the context path is appended to the base URL. Then the corresponding object is downloaded by this server and sent to the requester.ZipWebMap. This WebMap associates paths in a URL with entries in a ZIP file. The constructor's argument is either a String naming a ZIP file, aFilereferencing a ZIP file, or an instance ofZipWebMap.Config, which provides the ZIP file and some colors used when displaying directories.
Certificate Managers and SSLSetup
Both EmbeddedWebServer.SSLSetup and
CertManager configure SSL by providing
a keystore, truststore, and the corresponding passwords. They
can also set a
Configurator
for applying additional SSL configuration options. If it necessary
to modify a server's certificate, the method
modifyServerSetup(SSLSetup)
should be called before the server is stopped. A similar method
will typically be called by a certificate manager automatically.
Instances of SSLSetup are
created by calling its constructor:
SSLSetup(). This will create an instance of SSLSetup using TLS.SSLSetup(String). This will create an instance of SSLSetup using a secure-socket protocol. The argument should be either "SSL", "TLS", or a protocol listed in the Java API documentation.
SSLSetup include
Additional methods can configure a trust store, if desired. If a
server needs a trust store to open connections to a server,
use the methods in SSLUtilities.
Certificate managers are created by calling either
CertManager.newInstance() or
CertManager.newInstance(String), whose argument
is a provider name. Each instance will use a keystore that is expected
to contain an entry using the alias servercert.
There are two default providers
- default. This provider creates a self-signed certificate, replacing it when the certificate is close to expiring. The keystore entry whose alias is servercert will be created or modified when close to expiring.
- external. This provider reloads the keystore when its servercert certificate is close to expiring, but does not modify the keystore.
The methods used to configure a CertManager return the certificate manager itself so that the methods can be chained together for programming convenience. The methods used to configure how certificates are renewed and how to find certificates are
setTimeOffset(int). This method provides a time offset in seconds from midnight and is used to schedule checks for expired certificates at a time at which the server is not likely to be busy: the server has to be stopped temporarily to install new certificates.setInterval(int). This method provides the interval in days between certificate renewal attempts. A certificate will not be renewed if it is not close to expiring. If set to zero, the value will be treated as one minute, which is useful for testing but not for actual use.setStopDelay(int). This method provides the time in seconds to wait before fully shutting down a server when necessary to renew certificates.setTimeZone(String)orsetTimeZone(TimeZone). These methods provide the time zone for the server.CertManager.setProtocol(String). This method provides the HTTPS protocol to use ("SSL" or "TLS").setKeystoreFile(File). This method provides theFilethe contains the keystore.setKeystorePW(char[]). This method provides the 'store' password used to access entries in a keystore (public keys and certificates). The default is changeit.setKeyPW(char[]). This method provides the password used to access the private keys in a keystore. The default is changeit.setConfigurator(EmbeddedWebServer.Configurator). This method provides a functional interface that has a 'configure' method whose first argument is an instance ofSSLContextand whose second argument is an instance ofHttpsParameters.
The methods used to create (or renew) certificates are
setCertName(String). This method provides an identifier to name a certificate, which is needed by some implementations.-
setAlias(String). This method provides a user-specified name for an alias. Some certificate managers may not support this method and will throw an exception if it is used. setDomain(String). This method provides the domain name used in a certificate.setEmail(String). This method provides an email address used to contact a user for administrative purposes (e.g., if a certificate is about to expire).setHelper(EmbeddedWebServer). This method provides a 'helper' web server that some certificate managers require to automatically manage certificates. a 'helper' can be created when needed, but one might prefer that the helper is always running, in which case this method should be used. A 'helper' typically runs HTTP instead of HTTPS. The documentation for a certificate manager that needs a helper should describe how to create it.
setTrustManagers(TrustManager[])orsetTruststoreFile(File). These methods are mutually exclusive and provide either trust managers to use or the file containing a trust store created using keytool. They are not needed when the server's certificate was signed by a certificate authority listed in Java's cacerts file (e.g., /user/lib/jvm/.../lib/security/cacerts).setTruststorePW(char[]). This method provides the 'store' password used to access a trust store.
Several configuration methods, mostly used for testing or debugging,
are not described:
alwaysCreate
and setMode.
Certificate creation
Creating and signing certificates using the Java program keytool is somewhat of a black art, particularly when generating certificates for testing. The cases shown below use elliptic-curve cryptography to generate public and private keys.
In the following,
- KS should be replaced with the file name of a keystore, excluding a ".jks" file-name suffix. This file is needed to configure a server.
- TS should be replaced with the file name of a truststore, excluding a ".jks" file-name suffix. This is needed to configure applications so that they will accept a certificate chain whose root does not appear in the standard Java cacerts file.
- ALIAS should be replaced with an alias for a server private key and certificate.
- HOST should replaced with the server's host name. In many caaes, this should be a fully qualified name.
- ROOT_ALIAS should be replaced with an alias for a root certificate when added to a Java cacerts file.
Stand-alone certificates
The simplest case is a stand-alone certificate. The following example creates one using elliptic curves, and placing it in a keystore named KS.jks with a 'store' and 'key' password "changeit":
keytool -genkey -keyalg EC -groupname secp256r1 \ -sigalg SHA256withECDSA -keystore KS.jks \ -keypass changeit -storepass changeit \ -dname CN=HOST -alias ALIAS -validity 90
HOST should be the host name for a server and ALIAS should be whatever alias is used to name the keystore entry. This certificate can also be copied to a trust store, although in this simple case, one can use the same keystore for both.:
keytool -importkeystore -srckeystore KS.jks \ -destkeystore TS.jks -srcalias ALIAS -destalias ALIAS \ -srcstorepass changeit -deststorepass changeit \ -srckeypass changeit -destkeypass changeit
or
keytool -keystore KS.jks -alias ALIAS -exportcert \ -storepass changeit -rfc \ | keytool -importcert -alias ALIAS -keystore TS.jks \ -keypass changeit -storepass changeit -noprompt
Two-level certificate chain
The following generates a certificate for a certificate authority:
keytool -genkey -keyalg EC -groupname secp256r1 \ -sigalg SHA256withECDSA -keystore KS.jks \ -keypass changeit -storepass changeit \ -ext bc=ca:true -dname CN=ca -alias ca -validity 365
The argument "-ext bc=ca:true" is a Basic Constraints X509 extension that indicates that this is a CA (Certificate Authority) certificate. It was created in the same keystore as the previous self-signed certificate. If it is in a different keystore, the process will fail: this certificate is being used as a certificate authority but is not in the CA file (e.g., /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts), and if the root certificate is not there, everything must be done in the same keystore.
The next step is to generate a certificate request and process that by generating a certificate:
keytool -keystore KS.jks -alias ALIAS \ -certreq -storepass changeit -keypass changeit | \ keytool -keystore KS.jks -storepass changeit \ -alias ca -gencert -ext san=dns:HOST | \ keytool -keystore KS.jks -storepass changeit \ -alias ALIAS -importcert
The extension "san=dns:HOST" indicates that HOST will be a subject alternate name for the signed certificate. There are three programs in the pipeline. The first generates a certificate request, the second processes that request to generate a signed certificate, and the third imports the signed certificate.
Finally, one can move the CA certificate and its private key to a trust store and delete it from the keystore
keytool -importkeystore -srckeystore KS.jks \ -destkeystore TS.jks -srcalias ca -destalias ca \ -srcstorepass changeit -deststorepass changeit \ -srckeypass changeit -destkeypass changeit keytool -delete -alias ca -keystore KS.jks -storepass changeit
At this point, there is a single entry in the keystore with a certificate chain whose length is 2. A program such as SBL will have to include a trust store and its password, but not the keystore.
Three-level certificate
To create the root certificate authority and its private key, run the commands
keytool -genkey -keyalg EC -groupname secp256r1 \ -sigalg SHA256withECDSA -keystore root.jks \ -keypass changeit -storepass changeit \ -ext bc=ca:true -dname CN=root -alias root -validity 365 keytool -keystore root.jks -alias root -exportcert -rfc \ -storepass changeit -keypass changeit > root.pem keytool -keystore TS.jks -alias root -importcert -file root.pem \ -storepass changeit -keypass changeit -noprompt
This places the root certificate and its private key in root.jks, and places the root certificate without its private key in TS.jks. It also stores the certificate in a file root.pem for later use. To import the root certificate into the standard Java cacerts file, use
keytool -keystore $JAVA_HOME/jre/lib/security/cacerts -file root.pem \ -alias ROOT_ALIAS -importcert -storepass changeit -keypass changeit \ -trustcacerts
where ROOT_ALIAS does not conflict with entries already in cacerts.
To create an intermediate certificate authority, run the commands
keytool -genkey -keyalg EC -groupname secp256r1 \ -sigalg SHA256withECDSA -keystore ca.jks \ -keypass changeit -storepass changeit \ -ext bc=ca:true -dname CN=ca -alias ca -validity 365 keytool -storepass changeit -keypass changeit -keystore ca.jks \ -certreq -alias ca | keytool -storepass changeit -keypass changeit -keystore root.jks \ -gencert -alias root -ext BC=0 -rfc > ca.pem keytool -keystore ca.jks -importcert -alias ca -file ca.pem \ -keypass changeit -storepass changeit
This creates the intermediate certificate ca, stores that certificate and its private key in ca.jks, and provides a file ca.pem containing this certificate for later use.
To create a server certificate and private key in a keystore KS.jks, run the commands
keytool -genkey -keyalg EC -groupname secp256r1 \ -sigalg SHA256withECDSA -keystore KS.jks \ -keypass changeit -storepass changeit \ -dname CN=HOST -alias servercert -validity 90 keytool -storepass changeit -keypass changeit -keystore server.jks \ -certreq -alias servercert | keytool -storepass changeit -keypass changeit -keystore ca.jks\ -gencert -alias ca \ -ext san=dns:wtz-thelio -ext ku:c=dig,kE -rfc > server.pem
This will store the server certificate in the file server.pem.
The final step is to add the server certificate to the keystore. containing the server's private key.
The procedure above is based on an example in the keytool manual page (under "GENERATING CERTIFICATES FOR AN SSL SERVER"), and some examples from Using Keytool to create certificate chain. This code will work if the commands are entered in a terminal window or a script, but the keytool commands with the optioncat root.pem ca.pem server.pem | keytool -keystore KS.jks -importcert -alias servercert \ -storepass changeit -keypass changeit -noprompt
-importcert will print an error message and return a
non-zero exit code, although the operation actually completed.
This will cause a failure if the commands are used in a Makefile.
A fix is to add "|| true" to
Coding Example
For a simple coding example based on code in the EPTS program, consider the case of a web server embedded in an application for providing on-line help. The first step is to create an embedded web server:
The argument 8080 is the TCP port number to use, 48 is the TCP backlog, 2 is the number of threads the server uses, and 'false' indicates that HTTP is used rather than HTTPS. The next step is to map a path to some service.EmbeddedWebServer ews = new EmbeddedWebServer(8080, 48, 2, false);
ews.add("/", ResourceWebMap.class, "org/bzdev/epts/manual/", null, true, false, true);
For these arguments, "/" indicates the common portion of
the URL paths for the resources being added,
and the following argument
specify a mapping class that will find classes whose paths in the
application's code base start
with ResourceWebMap.classorg/bzdev.epts/manual/, null indicates that an
authenticator is not used, the first true indicates that
a web.xml file should not be used , false indicates that
the server should not display directories, and the
final true indicates that any WEB-INF directory should
not be visible.
The sequence of statements
WebMap wmap = epts.getWebMap("/"); wmap.addWelcome("index.html"); webmap.addMapping("html", "text/html; charset=utf-8");
looks up a web map for the context "/", configures the
web map so that "index.html" is the 'welcome' page, and configures the
web map so that files ending in the suffix "html" will have a media type
of "text/html; charset=utf-8".
Finally
ews.start();
will start the web server. The URL http://localhost:8080/
can be typed into a browser to view this web page on the same computer
as the server.