The Codion server provides RMI and HTTP connection options to clients.

1. Features

  • Firewall friendly RMI; uses one way communications without callbacks and can be configured to serve on a single fixed port

  • Integrated web server for serving HTTP client connections, based on Javalin and Jetty

  • All user authentication left to the database by default

  • Comprehensive administration and monitoring facilities via the ServerMonitor

  • Featherweight server with moderate memory and CPU usage

2. Security

Here’s a great overview of RMI security risks and mitigations.

2.1. Authentication

The Codion server does not perform any user authentication by default, it leaves that up the underlying database. An authentication layer can be added by implementing a Authenticator and registering it with the ServiceLoader.

2.1.1. Authenticator examples

2.2. RMI SSL encryption

To enable SSL encryption between client and server, create a keystore and truststore pair and set the following system properties.

2.2.1. Server side

codion.server.connection.sslEnabled=true # (1)
javax.net.ssl.keyStore=keystore.jks
javax.net.ssl.keyStorePassword=password
  1. This property is 'true' by default, included here for completeness' sake

2.2.2. Client side

codion.client.trustStore=truststore.jks
codion.client.trustStorePassword=password

2.3. Class loading

No dynamic class loading is required.

2.4. Serialization filtering

The framework provides a way to configure a ObjectInputFilter for deserialization, by specifying a ObjectInputFilterFactory implementation class with the following system property.

codion.server.objectInputFilterFactoryClassName=\
    my.serialization.filter.MyObjectInputFilterFactory

2.4.1. Serialization whitelist

To use the serialization whitelist filter provided by the framework, set the following system property.

codion.server.objectInputFilterFactoryClassName=\
    is.codion.common.rmi.server.WhitelistInputFilterFactory

The whitelist is configured via the following system property.

codion.server.serializationFilterWhitelist=config/whitelist.txt
codion.server.serializationFilterWhitelist=classpath:whitelist.txt

A whitelist can be created during a server dry-run by adding the following system property. The whitelist containing all classes deserialized during the run is written to disk on server shutdown.

codion.server.serializationFilterDryRun=true
Example whitelist
ch.qos.logback.classic.Level
com.sun.proxy.$Proxy*
java.lang.Boolean
java.lang.Character
java.lang.Double
java.lang.Enum
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Number
java.lang.Object
java.lang.String
java.lang.reflect.Proxy
java.math.BigDecimal
java.math.BigInteger
java.time.LocalDate
java.time.LocalDateTime
java.time.LocalTime
java.time.Ser
java.time.ZoneId
java.time.ZoneRegion
java.util.ArrayList
java.util.Arrays$ArrayList
java.util.Collections$EmptyList
java.util.Collections$EmptyMap
java.util.Collections$SingletonList
java.util.Collections$SingletonMap
java.util.Collections$SingletonSet
java.util.Collections$UnmodifiableCollection
java.util.Collections$UnmodifiableList
java.util.Collections$UnmodifiableMap
java.util.Collections$UnmodifiableSet
java.util.Collections$UnmodifiableRandomAccessList
java.util.Date
java.util.HashMap
java.util.HashSet
java.util.LinkedHashMap
java.util.LinkedHashSet
java.util.Locale
java.util.Map$Entry
java.util.UUID
net.sf.jasperreports.compilers.ConstantExpressionEvaluation
net.sf.jasperreports.compilers.FieldEvaluation
net.sf.jasperreports.compilers.ReportExpressionEvaluationData
net.sf.jasperreports.engine.JRPropertiesMap
net.sf.jasperreports.engine.JasperReport
net.sf.jasperreports.engine.JRBand
net.sf.jasperreports.engine.JRExpressionChunk
net.sf.jasperreports.engine.JRField
net.sf.jasperreports.engine.JRParameter
net.sf.jasperreports.engine.JRQueryChunk
net.sf.jasperreports.engine.JRVariable
net.sf.jasperreports.engine.base.JRBaseBand
net.sf.jasperreports.engine.base.JRBaseBoxBottomPen
net.sf.jasperreports.engine.base.JRBaseBoxLeftPen
net.sf.jasperreports.engine.base.JRBaseBoxPen
net.sf.jasperreports.engine.base.JRBaseBoxRightPen
net.sf.jasperreports.engine.base.JRBaseBoxTopPen
net.sf.jasperreports.engine.base.JRBaseDataset
net.sf.jasperreports.engine.base.JRBaseElement
net.sf.jasperreports.engine.base.JRBaseElementGroup
net.sf.jasperreports.engine.base.JRBaseExpression
net.sf.jasperreports.engine.base.JRBaseExpressionChunk
net.sf.jasperreports.engine.base.JRBaseField
net.sf.jasperreports.engine.base.JRBaseLineBox
net.sf.jasperreports.engine.base.JRBaseParagraph
net.sf.jasperreports.engine.base.JRBaseParameter
net.sf.jasperreports.engine.base.JRBasePen
net.sf.jasperreports.engine.base.JRBaseQuery
net.sf.jasperreports.engine.base.JRBaseQueryChunk
net.sf.jasperreports.engine.base.JRBaseReport
net.sf.jasperreports.engine.base.JRBaseSection
net.sf.jasperreports.engine.base.JRBaseStaticText
net.sf.jasperreports.engine.base.JRBaseTextElement
net.sf.jasperreports.engine.base.JRBaseTextField
net.sf.jasperreports.engine.base.JRBaseVariable
net.sf.jasperreports.engine.design.JRReportCompileData
net.sf.jasperreports.engine.type.CalculationEnum
net.sf.jasperreports.engine.type.EvaluationTimeEnum
net.sf.jasperreports.engine.type.HorizontalTextAlignEnum
net.sf.jasperreports.engine.type.IncrementTypeEnum
net.sf.jasperreports.engine.type.OrientationEnum
net.sf.jasperreports.engine.type.PositionTypeEnum
net.sf.jasperreports.engine.type.PrintOrderEnum
net.sf.jasperreports.engine.type.ResetTypeEnum
net.sf.jasperreports.engine.type.RunDirectionEnum
net.sf.jasperreports.engine.type.SectionTypeEnum
net.sf.jasperreports.engine.type.SplitTypeEnum
net.sf.jasperreports.engine.type.StretchTypeEnum
net.sf.jasperreports.engine.type.TextAdjustEnum
net.sf.jasperreports.engine.type.WhenResourceMissingTypeEnum
is.codion.common.Conjunction
is.codion.common.Operator
is.codion.common.db.operation.DefaultFunctionType
is.codion.common.db.operation.DefaultProcedureType
is.codion.common.db.report.AbstractReport
is.codion.common.db.report.DefaultReportType
is.codion.common.rmi.client.DefaultConnectionRequest
is.codion.common.user.DefaultUser
is.codion.common.version.DefaultVersion
is.codion.framework.db.DefaultSelect
is.codion.framework.db.DefaultUpdate
is.codion.framework.domain.DefaultDomainType
is.codion.framework.domain.entity.attribute.DefaultAttribute
is.codion.framework.domain.entity.attribute.DefaultAttribute$DefaultType
is.codion.framework.domain.entity.attribute.DefaultColumn
is.codion.framework.domain.entity.attribute.DefaultForeignKey
is.codion.framework.domain.entity.attribute.DefaultForeignKey$DefaultReference
is.codion.framework.domain.entity.condition.AbstractCondition
is.codion.framework.domain.entity.condition.AbstractColumnCondition
is.codion.framework.domain.entity.condition.DefaultAllCondition
is.codion.framework.domain.entity.condition.DefaultConditionCombination
is.codion.framework.domain.entity.condition.DefaultCustomCondition
is.codion.framework.domain.entity.condition.AbstractColumnCondition
is.codion.framework.domain.entity.condition.DualValueColumnCondition
is.codion.framework.domain.entity.condition.MultiValueColumnCondition
is.codion.framework.domain.entity.condition.SingleValueColumnCondition
is.codion.framework.domain.entity.condition.DefaultConditionType
is.codion.framework.domain.entity.DefaultEntity
is.codion.framework.domain.entity.DefaultEntity$EntityInvoker
is.codion.framework.domain.entity.DefaultEntityType
is.codion.framework.domain.entity.DefaultForeignKey
is.codion.framework.domain.entity.DefaultForeignKey$DefaultReference
is.codion.framework.domain.entity.DefaultKey
is.codion.framework.domain.entity.DefaultOrderBy
is.codion.framework.domain.entity.DefaultOrderBy$DefaultOrderByColumn
is.codion.framework.domain.entity.Entity
is.codion.framework.domain.entity.ImmutableEntity
is.codion.framework.domain.entity.OrderBy$NullOrder
is.codion.demos.chinook.domain.Chinook
is.codion.demos.chinook.domain.Chinook$Invoice
is.codion.demos.chinook.domain.Chinook$Playlist$RandomPlaylistParameters
is.codion.demos.chinook.domain.Chinook$Track
is.codion.demos.chinook.domain.Chinook$Track$RaisePriceParameters
is.codion.demos.employees.domain.*
is.codion.demos.world.domain.api.*
is.codion.demos.petclinic.domain.*
is.codion.plugin.jasperreports.DefaultJRReportType

3. Configuration

3.1. Example configuration file

# Database configuration
codion.db.url=jdbc:h2:mem:h2db
codion.db.useOptimisticLocking=true
codion.db.countQueries=true
codion.db.initScripts=\
    ../config/employees/create_schema.sql,\
    ../config/chinook/create_schema.sql,\
    ../config/petstore/create_schema.sql,\
    ../config/world/create_schema.sql

# The admin user credentials, used by the server monitor application
codion.server.admin.user=scott:tiger

# Client logging disabled by default
codion.server.clientLogging=false

# A connection pool based on this user is created on startup
codion.server.connectionPoolUsers=scott:tiger

# The port used by clients
codion.server.port=2222

# The port for the admin interface, used by the server monitor
codion.server.admin.port=4444

# RMI Registry port
codion.server.registryPort=1099

# Any auxiliary servers to run alongside this server
codion.server.auxiliaryServerFactoryClassNames=\
    is.codion.framework.servlet.EntityServletServerFactory

# The http port
codion.server.http.port=8080

# Specifies whether or not to use https
codion.server.http.secure=false

# The ObjectInputFilterFactory class to use
codion.server.objectInputFilterFactoryClassName=\
    is.codion.common.rmi.server.WhitelistInputFilterFactory

# The serialization whitelist to use for RMI deserialization
codion.server.serializationFilterWhitelist=\
    ../config/serialization-whitelist.txt

# RMI configuration
java.rmi.server.hostname=localhost
java.rmi.server.randomIDs=true

# SSL configuration
javax.net.ssl.keyStore=../config/keystore.jks
javax.net.ssl.keyStorePassword=crappypass

# Used to connect to the server to shut it down
#codion.client.trustStore=../config/truststore.jks

4. Code examples

Absolute bare-bones examples of how to run the EntityServer and connect to it.

4.1. RMI

    Database database = H2DatabaseFactory
            .createDatabase("jdbc:h2:mem:testdb",
                    "src/main/sql/create_schema.sql");

    EntityServerConfiguration configuration = EntityServerConfiguration.builder(SERVER_PORT, REGISTRY_PORT)
            .domainClassNames(List.of(Store.class.getName()))
            .database(database)
            .sslEnabled(false)
            .build();

    EntityServer server = EntityServer.startServer(configuration);

    RemoteEntityConnectionProvider connectionProvider =
            RemoteEntityConnectionProvider.builder()
                    .port(SERVER_PORT)
                    .registryPort(REGISTRY_PORT)
                    .domainType(Store.DOMAIN)
                    .user(parse("scott:tiger"))
                    .clientType("ClientServer")
                    .build();

    EntityConnection connection = connectionProvider.connection();

    List<Entity> customers = connection.select(all(Customer.TYPE));
    customers.forEach(System.out::println);

    connection.close();

    server.shutdown();

4.2. HTTP

    Database database = H2DatabaseFactory
            .createDatabase("jdbc:h2:mem:testdb",
                    "src/main/sql/create_schema.sql");

    EntityService.HTTP_SERVER_PORT.set(HTTP_PORT);

    EntityServerConfiguration configuration = EntityServerConfiguration.builder(SERVER_PORT, REGISTRY_PORT)
            .domainClassNames(List.of(Store.class.getName()))
            .database(database)
            .sslEnabled(false)
            .auxiliaryServerFactoryClassNames(List.of(EntityServiceFactory.class.getName()))
            .build();

    EntityServer server = EntityServer.startServer(configuration);

    HttpEntityConnectionProvider connectionProvider =
            HttpEntityConnectionProvider.builder()
                    .port(HTTP_PORT)
                    .https(false)
                    .domainType(Store.DOMAIN)
                    .user(parse("scott:tiger"))
                    .clientType("ClientServer")
                    .build();

    EntityConnection connection = connectionProvider.connection();

    List<Entity> customers = connection.select(all(Customer.TYPE));
    customers.forEach(System.out::println);

    connection.close();

    server.shutdown();