There are many ways to design secure applications with Ice. Most of the principles that apply to traditional web applications can be transposed to Ice applications (session based authentication or token based authentication for example). Unlike HTTP which uses non-persistent connections, Ice also supports persistent connections that can be used for security as well.
We will explore in this article some common approaches for building secure Ice applications.
On this page:
Token based security
Security based on tokens is quite common with web applications: the client sends credentials to the server which in turn replies with a token. The token is opaque to the client and contains information used by the server to check whether the client has permission to access a resource. The advantage of this approach is that it doesn't require the server to maintain state for the client. The generated token can be used in any server to verify permissions to access a resource.
Below is a Slice interface which uses explicit tokens to verify permissions of a user to add or remove items from a cart:
The client application first requests a token from the server by calling
createSecurityToken on the security token factory. The returned token is then used to call
removeItem to modify the user's cart. The implementation of these two operations checks the token to make sure it's valid. An exception is thrown if the token check in the server fails.
The authentication in this example is based on a user identifier and password but it could also be based on any other type of credentials such as certificates (certificates would typically be provided as a sequence of bytes). If the client uses a X.509 certificate to establish the SSL/TLS connection with the server, the authentication could use the identity (distinguished name) contained in the client's certificate.
A sample Java implementation of the cart manager is shown below:
We implement the interfaces
Demo.CartManager that were generated for the Slice interfaces. The
createSessionToken method calls a
generateToken method with the credentials. This method returns a string token if the user credentials are valid. The token is an encrypted representation of various data, such as the user ID and a timestamp that limits the lifespan of the token. The server later retrieves this data by decrypting the token.
Showing the token generation is out of the scope of this article. The token generation and verification is typically handled by a 3rd party library.
removeItem methods are used to modify the user's cart. The token is explicitly provided to each call and the server calls
checkToken to ensure its validity. Again, the implementation of
checkToken isn't shown here but typically this method decrypts the token data, verifies it's still valid and checks the permissions of the user. Since the token includes the user identifier, we can extract it from the token to retrieve the user's cart and add or remove items.
Additional configuration for the SSL/TLS transport (such as the server certificate) needs to be provided through an Ice configuration file. The endpoint defined above allows a client to access the server with the SSL/TLS transport on port 10000. Since no
-h is specified, the server will listen to all the network interfaces available on the host computer.
Using the cart manager service is straightforward with Ice.
You will find below a sample client which obtains a security token and then adds and removes an item from the cart by calling on the cart manager Ice object:
The client creates a proxy with the Ice object identity
CartManager and the endpoint
ssl -p 10000 -h 127.0.0.1. We assume here that the server is running on the same machine as the client; if the client and servers are on different hosts, we would use the IP address or hostname of the server's host with the
-h option. We would also typically use a configuration property for this stringified proxy instead of hard-coding it the client's source code.
Using Ice contexts
Explicitly passing the token with each call on the cart manager is cumbersome, and the security context is not something the remote APIs of the application should be concerned with. A better approach is to use requests context to send the security token with each invocation without an explicit
token string parameter in each Slice operation signature:
The client uses instead implicit contexts to pass this token:
The Ice communicator also needs to be configured to use the implicit context with the Ice.ImplicitContext property.
An alternative to implicit contexts is to set the context on the proxy:
Invocations made on this new cart manager proxy will now always embed the configured context. This solution works well if you only use few proxies.
On the server side, the cart manager servant can retrieve the context using the Ice current parameter:
Using dispatch interceptors
An additional improvement we could make is to remove the security checks from the implementation of the cart manager interface. We can do this with an Ice dispatch interceptor to intercept requests on the cart manager Ice object. The security check on the token would no longer be done explicitly in the implementation of
removeItem. Instead, the interceptor implementation would take care of it:
Connection based security
Ice supports persistent connections with TCP based transports (tcp, ssl, ws, wss), so we can also keep track of authenticated clients through the connection associated with incoming requests. With this approach, the client authenticates with the server using credentials such as username/password. The server then checks these credentials and associates the current network connection with them. When the client sends a request to the server, the server can check the connection used for sending the request and see if the connection is known. Depending on the associated credentials it can reject or accept the request.
Let's update the Slice interfaces presented in the previous example to use connections instead of tokens:
A sample Java implementation of the cart manager is shown below:
As you can see, unlike the token based approach, the server is now required to keep client state with the new map of connections to user IDs. This map is populated when a client calls the
login method. The implementation verifies the credentials by calling
checkCredentials. We don't show the implementation of this method here but typically this method would perform a database lookup to check the password. Once the credentials are checked, the implementation adds the connection obtained from the Ice current object to a map and associates the user ID with the connection. The implementation of the
logout method is simple: it just removes the map entry.
removeItem methods retrieve the connection and obtain the user ID associated with the connection to retrieve the user's cart.
You will find below a sample client which uses the cart manager interface defined above:
With this approach, it is critical that the client's connection to the server remains open. If the connection is closed and a new connection is created, further requests from the client to the server will fail unless the client authenticates the new connection with a call to
This connection-based approach requires a good understanding of Ice connection management. For example, in the client above, the invocations on the cart manager proxy object will use the same underlying Ice connection because they are made on the same proxy, one after the other. If there is a 60 seconds delay between the
removeItem calls, with Ice's default configuration, Ice active connection management will close the connection. The
removeItem call will then fail because it gets called on a new un-authenticated connection.
Using ACM heartbeats
Ice provides configuration properties to keep Ice connections open and alive, and avoid the automatic closure of idle connections.
For example, you can set the following properties for the client and the server:
With this configuration:
- the client will never close a connection when it's idle, instead it will send a heartbeat message every 15 seconds to keep the connection alive,
- the server will forcefully close a connection if it doesn't receive activity on the connection within the 30s period.
Using connection callbacks
Ice also provides a callback mechanism to notify applications of connection closure. Such a close notification is useful to prevent leaks. In the server implementation shown above, if the client doesn't call
logout (because it crashes or the network connection is dropped), the entry for the client's connection won't be removed from the
_connections map. This creates a leak in the server.
To prevent such a leak, we can modify the
login method to ensure the entry is removed when the connection is closed:
Session based security
With this pattern, the client establishes a session with the server. The authentication information is sent by the client on session establishment. If the authentication succeeds, the server creates a session and returns a session identifier to the client. The client then uses the session identifier to perform further requests on the server application. The server persists the session information either in memory or in a database. In addition to the user credentials, the session stores extra information specific to the application. For example, in a shopping cart type application, the session can store the user's cart.
Here’s how we could write the Slice interfaces for such an application:
The object-oriented nature of Ice makes it easy to understand the interactions between the client and server. As you can see, there's also no added clutter to transmit the session identifier. The session identifier is implicitly encapsulated with the session proxy returned by the
Let's see how these interfaces can be implemented and used.
Here's a sample implementation of the session interface:
The session information is stored in-memory by instances of the
SessionImpl object. There's one instance of this object per client session. The
removeItem methods are called by Ice clients to add or remove items from the cart. The
destroy method is called by the client to notify the server it's no longer interested in the session. The implementation of this method un-registers the Ice object from the Ice object adapter; thereafter, Ice no longer accepts requests to this session object.
The implementation of the
createSession methods checks the credentials supplied by the client. In this example, the client provides a user identifier and password. Again, it could also provide another type of credential, such as a X.509 certificate.
This method then creates a new
SessionImpl servant object, and registers this servant with the Ice object adapter, using a UUID for the object identity. A proxy to this Ice object is then transmitted back to the client. This proxy embeds the Ice object identity and the endpoint information to allow the client to reach this Ice session object remotely.
This proxy is in effect the session identifier that a traditional web application would transmit back to a client. The random nature of the UUID used for the Ice object identity ensures that other client can't easily guess it and with a secured communication between the client and server, the proxy identity can't be discovered by eavesdropping on the network connection.
main method of the server is similar to the one we used for the token based model:
Below is a simple client that creates a session and update the session's cart.
The client creates a proxy for the session factory Ice object and invokes
createSession to provide the credentials and create the session. The client can then use the session proxy to add and remove items from the cart. Finally, it calls
destroy on the session to ensure the resources allocated by the server for the session are destroyed.
In the server implementation above, we rely on the client to call the session
destroy() method for releasing the resource allocated for the session. The implementation removes the servant from the Ice object adapter. If
destroy() isn't called, the servant will remain referenced by the Ice object adapter and this will cause the object to remain in memory until the Ice object adapter is destroyed when the server is shutdown.
Since there's no guarantee that the client will call
destroy() (the client could crash, the network connection could be lost, ...), we need to add a mechanism to remove the session when we detect that the client abandoned the session without calling
destroy(). How can the Ice server detect this? There are several solutions:
- the server requires the client to explicitly "ping" or keep-alive the session at regular intervals. The server keeps track of the pings from the clients and reaps sessions for which it didn't receive a ping message for a while. This can be implemented by setting a timer in the client to call the
ice_ping()method on the proxy and override the
ice_pingservant method in the
.The server checks at regular intervals which sessions can be reaped based on the last time the ping was received.
- the application can bind the session to the Ice connection (which represents the persistent network connection between the client and server) and rely on the connection closure callback to destroy the session. This approach requires that the client uses a single connection to invoke on the server. This is true unless the client explicitly requests the use of separate connections, or uses different connection timeouts or endpoints.
Binding the session to the client connection is the simplest solution. Here's how we can modify the session factory to setup a connection callback to cleanup the session when the connection is closed:
There is one additional consideration here: how quickly the
closed callback is called. In the event the operating system's TCP/IP stack doesn't detect a connection failure in a timely manner, it's possible for the callback
closed method to be called several hours after the connection failure. This is typically the case when the client connection to the server goes through several routers and the connection failed because of an expected failure on the connection path. In order for the server to release the session resource in a timely manner and quickly detect the connection loss, you can enable keep-alives on the connection. The Ice Manual provides more information on this topic in the active connection management section.
The approach above relies on the fact that other clients can't discover the UUID used for the Ice object identity of the session. In the improvement above, we saw that binding the session to the connection was a simple way to cleanup the session resources when the client goes away. To provide increased security, we can also rely on the Ice connection object to check that invocations on the session are only performed by the client that created the session. This requires to keep track of the Ice connection objects in the session and check this connection in each call:
Using dispatch interceptors
Just like for the token-based model, we can further improve the session implementation shown above and move the
checkConnection call to a dispatch interceptor: