Class EndUserSecurityContext
The security context in which the end user of an application executes
database operations. An EndUserSecurityContext allows an
Oracle JDBC Connection to operate under the security policy
of a specific end user, rather than the security policy of a database
user. This class is designed for integration with the Oracle Deep Data
Security feature of Oracle Database (i.e., DeepSec).
Instances of EndUserSecurityContext encapsulate the set of
values listed below.
- Database Access Token
-
An
EndUserSecurityContextmust have a database access token. The claims of this token should be mapped to a DATA ROLE or APPLICATION IDENTITY defined in the database. - End User Token or End User Name
-
An
EndUserSecurityContextmust either have an end user token or end user name. The claims of an end user token should be mapped to a DATA ROLE defined in the database. An end user name should be defined as an END USER in the database. - Application Managed Data Roles
-
An
EndUserSecurityContextmay have data roles that are enabled and disabled based on application logic, rather than a database security policy. This type of DATA ROLE is defined in the database with a DISABLED clause. - Attributes
-
An
EndUserSecurityContextmay have attributes. Attributes are contained in JSON objects conforming to a JSON schema of an END USER CONTEXT declared in the database. Usage of attributes can be seen in this code example. - Look-Up Key
-
Every
EndUserSecurityContexthas a look-up key which the database associates to a set of END USER CONTEXT attributes. The database applies the same set of attributes to all database operations executed with the same look-up key. A user-defined look-up key may be provided when thecreateWithName(CharSequence, String, CharSequence)method is used to identify an end user by a name. Otherwise, a look-up key will be automatically generated when an end user is identified by a token, or when no look-up key is provided for an end user identified by a name. An automatically generated look-up key is derived from the database access token, the token or name of the end user, and application managed data roles. The effect of look-up keys can be seen in this code example.
API Contract
Instances ofEndUserSecurityContext have the following
API contract.
-
Instances of
EndUserSecurityContextare immutable. Mutation is accomplished through "wither" methods, such aswithDatabaseAccessToken(CharSequence). These methods return a copy of anEndUserSecurityContextwith an old value replaced by a new value. - No method ever returns null.
-
No method accepts null. All methods throw a
NullPointerExceptionif a parameter is null or the parameter is a collection or array that contains null. -
Methods do not retain references to any mutable objects provided as
parameters or returned as results. For example, if a
CharSequenceis passed to a method, no reference to the original object will be kept, since aCharSequencemay be mutable. Likewise, no reference will be retained to achar[]returned by a method, because achar[]is mutable. This behavior ensures that all instances ofEndUserSecurityContextare immutable, while also allowing security-sensitive values to be cleared from memory, as shown in this code example.
Usage
A Connection from the Oracle JDBC Driver may be configured
with an instance of EndUserSecurityContext in either of the
following ways:
- Application code may call setEndUserSecurityContext and clearEndUserSecurityContext as shown in the code example below.
-
An implementation of
EndUserSecurityContextProvidercan be installed as a service provider. Before each database operation, anOracleConnectionretrieves the currentEndUserSecurityContextfrom that provider. Open-source provider implementations are available from the ojdbc-extensions project. IfsetEndUserSecurityContextis called when a provider is also installed, Oracle JDBC ignores the provider untilclearEndUserSecurityContextis called.
Code Examples
This first example shows how to use an EndUserSecurityContext
with a database access token and end user token. In this example, the
application defines a getDatabaseAccessToken() method that
returns a token authorizing the application to access the database. An OAUTH
client library might be used to request a database access token. The
application also defines a getCurrentUser() method that returns
information about the currently authenticated end user. This method
might be implemented using a security library that provides authentication
details to a thread when handling the end user's request.
public void example(DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection()) {
char[] databaseAccessToken = getDatabaseAccessToken();
char[] endUserToken = getCurrentUser().getSecurityToken();
final EndUserSecurityContext endUserSecurityContext;
try {
endUserSecurityContext = EndUserSecurityContext.createWithToken(
CharBuffer.wrap(databaseAccessToken),
CharBuffer.wrap(endUserToken));
}
finally {
Arrays.fill(databaseAccessToken, (char)0);
Arrays.fill(endUserToken, (char)0);
}
OracleConnection oracleConnection =
connection instanceof OracleConnection
? (OracleConnection)connection
: connection.unwrap(OracleConnection.class);
oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
try {
executeSqlAsEndUser(connection);
}
finally {
oracleConnection.clearEndUserSecurityContext();
}
}
}
The next example shows how to check the expiration time of a database
access token and end user token. In this example, the expiration times are
checked to ensure the tokens won't expire in the next two minutes. The
database only requires that tokens be valid at the start of a SQL
operation, and so a two-minute threshold would be excessive if only
one SQL command needs to be executed with the
EndUserSecurityContext. But if multiple SQL commands are
executed, the tokens will need to remain valid until the last SQL command
begins executing. Two minutes is just an example of a threshold that might be
used. The actual threshold should depend on application specific timeouts for
JDBC Connection usage, such as those that a connection pool might offer.
void example(EndUserSecurityContext endUserSecurityContext, Connection connection) throws SQLException {
OffsetDateTime deadline = OffsetDateTime.now().plusMinutes(2);
OffsetDateTime databaseAccessTokenExpiration = endUserSecurityContext.databaseAccessTokenExpiration();
if (databaseAccessTokenExpiration.isBefore(deadline)) {
String refreshedToken = refreshDatabaseAccessToken();
endUserSecurityContext = endUserSecurityContext.withDatabaseAccessToken(refreshedToken);
}
OffsetDateTime endUserTokenExpiration = endUserSecurityContext.endUserTokenExpiration();
if (endUserTokenExpiration.isBefore(deadline)) {
String refreshedToken = refreshEndUserToken();
endUserSecurityContext = endUserSecurityContext.withEndUserToken(refreshedToken);
}
OracleConnection oracleConnection =
connection instanceof OracleConnection
? (OracleConnection)connection
: connection.unwrap(OracleConnection.class);
oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
try {
executeSqlAsEndUser(connection);
}
finally {
oracleConnection.clearEndUserSecurityContext();
}
}
The next example shows how to configure an
EndUserSecurityContext with attributes of two END USER CONTEXT
objects defined as:
CREATE END USER CONTEXT user_info USING JSON SCHEMA '{
"type": "object",
"properties": {
"first_name": { "type": "string", "default": "(GUEST)" },
"last_name": { "type": "string", "default": "(GUEST)" }
}
}';
CREATE END USER CONTEXT location_info USING JSON SCHEMA '{
"type" : "object",
"properties" : {
"address" : {
"type" : "object",
"properties" : {
"street_name": {"type": "string", "default": "(UNKNOWN)"},
"address_number": {"type": "integer", "default": 0}
}
},
"country": { "type": "string", "default": "(UNKNOWN)"},
"state": { "type": "string", "default": "(UNKNOWN)"},
"city": { "type": "string", "default": "(UNKNOWN)"}
}
}';
While the attributes of an END USER CONTEXT are represented as a JSON object,
an application might use a different representation to store information
about its end users. In the code that follows, an application defines a
UserDetails object that encapsulates attributes of its end
users, and it translates this information into JSON objects. The JSON
objects are represented as instances of OracleJsonObject created by
an OracleJsonFactory.
It is important to note that a fully-qualified END USER CONTEXT name must be
provided in the Map passed to the withAttributes
method. A fully-qualified name includes the name of the database schema in
which an END USER CONTEXT is declared, using this format:
{schema}.{name}. The database will not recognize unqualified
names of an END USER CONTEXT.
void example(
UserDetails userDetails,
EndUserSecurityContext endUserSecurityContext,
Connection connection)
throws SQLException {
OracleJsonFactory jsonFactory = new OracleJsonFactory();
OracleJsonObject jsonUserInfo = jsonFactory.createObject();
Name name = userDetails.name();
jsonUserInfo.put("first_name", name.firstName());
jsonUserInfo.put("last_name", name.lastName());
OracleJsonObject jsonLocationInfo = jsonFactory.createObject();
Location location = userDetails.location();
jsonLocationInfo.put("city", location.city());
jsonLocationInfo.put("state", location.state());
jsonLocationInfo.put("country", location.country());
OracleJsonObject jsonAddress = jsonFactory.createObject();
jsonAddress.put("street_name", location.streetName());
jsonAddress.put("address_number", location.addressNumber());
jsonLocationInfo.put("address", jsonAddress);
// END USER CONTEXT names MUST be fully qualified with their schema name
endUserSecurityContext = endUserSecurityContext.withAttributes(Map.of(
"app_schema.user_info", jsonUserInfo,
"app_schema.location_info", jsonLocationInfo));
OracleConnection oracleConnection =
connection instanceof OracleConnection
? (OracleConnection)connection
: connection.unwrap(OracleConnection.class);
oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
try {
executeSqlAsEndUser(connection);
}
finally {
oracleConnection.clearEndUserSecurityContext();
}
}
The next example shows how the database stores attributes based on a
look-up key that is automatically generated from the database access
token, end user token, and application managed data roles of an
EndUserSecurityContext.
void example(EndUserSecurityContext endUserSecurityContext, Connection connection) throws SQLException {
OracleConnection oracleConnection =
connection instanceof OracleConnection
? (OracleConnection) connection
: connection.unwrap(OracleConnection.class);
try {
// Executes SQL with: {"a":1, "b":1}
OracleJsonObject attributes = new OracleJsonFactory().createObject();
attributes.put("a", 1);
attributes.put("b", 1);
endUserSecurityContext = endUserSecurityContext.withAttributes("scott.example", attributes);
oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
executeSqlAsEndUser(connection);
// Executes SQL with: {"a":1, "b":2}, even though "a" is not present in
// the attributes of the EndUserSecurityContext. The value of "a" is 1
// because the look-up key is identical to that of the previous
// operation. The look-up key is identical because the database
// access token, end user token, and application managed data roles are
// identical.
attributes.remove("a");
attributes.put("b", 2);
endUserSecurityContext = endUserSecurityContext.withAttributes("scott.example", attributes);
oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
executeSqlAsEndUser(connection);
// Executes SQL with: {"a":1, "b":2}, even though no attributes are
// present in the EndUserSecurityContext, the database will again see
// an identical look-up key.
endUserSecurityContext = endUserSecurityContext.withAttributes(Collections.emptyMap());
oracleConnection.setEndUserSecurityContext(endUserSecurityContext);
executeSqlAsEndUser(connection);
}
finally {
oracleConnection.clearEndUserSecurityContext();
}
}
- Since:
- 26
-
Method Summary
Modifier and TypeMethodDescriptionReturns anunmodifiableMapof END USER CONTEXT attributes, represented as unmodifiable instances ofOracleJsonObject.static EndUserSecurityContextcreateWithName(CharSequence databaseAccessToken, String endUserName) Creates a security context in which an application with a database access token executes database operations for an end user identified by a name.static EndUserSecurityContextcreateWithName(CharSequence databaseAccessToken, String endUserName, CharSequence lookUpKey) Creates a security context in which an application with a database access token executes database operations for an end user identified by a name.static EndUserSecurityContextcreateWithToken(CharSequence databaseAccessToken, CharSequence endUserToken) Creates a security context in which an application with a database access token executes database operations for an end user identified by a security token.char[]Returns the database access token of thisEndUserSecurityContext.Returns the expiration time of the database access token.Returns anunmodifiable setof application managed data roles that have been enabled for the end user.Returns anOptionalcontaining an end user name,Optional.empty()if the end user is identified by a security token instead of a name.Optional<char[]>Returns anOptionalcontaining the end user token, orOptional.empty()if the end user is identified by a name instead of a token.Returns the expiration time of the end user token, orOffsetDateTime.MAXif the end user is identified by a name instead of a token.booleanReturnstrueif an object is anEndUserSecurityContextencapsulating values which are all equal to those of thisEndUserSecurityContext.inthashCode()Returns a hash code computed from all values encapsulated by thisEndUserSecurityContext.Optional<char[]>toString()Returns a String containing non-security-sensitive values encapsulated by thisEndUserSecurityContext; This method will NOT return the clear text form of security tokens, look-up keys, or any other information that grants access to a secured system.withAttributes(String contextName, OracleJsonObject attributes) Returns a copy of thisEndUserSecurityContextwith a new set of attributes mapped to a fully qualified END USER CONTEXT name.withAttributes(Map<String, OracleJsonObject> attributes) Returns a copy of thisEndUserSecurityContextwith a new set of attributes mapped to their fully qualified END USER CONTEXT names.withDatabaseAccessToken(CharSequence databaseAccessToken) Returns a copy of thisEndUserSecurityContextwith a new database access token.withDataRoles(String... dataRoles) Returns a copy of thisEndUserSecurityContextwith a new set of application managed data roles.withDataRoles(Collection<String> dataRoles) Returns a copy of thisEndUserSecurityContextwith a new set of application managed data roles.withEndUserName(String endUserName) Returns a copy of thisEndUserSecurityContextwith a new end user name and automatically generated look-up key.withEndUserName(String endUserName, CharSequence lookUpKey) withEndUserToken(CharSequence endUserToken) Returns a copy of thisEndUserSecurityContextwith a new end user token.
-
Method Details
-
databaseAccessTokenExpiration
Returns the expiration time of the database access token. This method can be used to check if the database access token needs to be refreshed before a long-running sequence of SQL operations, as shown in this example.
- Returns:
- The expiration time of the database access token. Not null.
-
databaseAccessToken
public char[] databaseAccessToken()Returns the database access token of this
EndUserSecurityContext.- Returns:
- The database access token. Not null. Not retained.
-
withDatabaseAccessToken
Returns a copy of this
EndUserSecurityContextwith a new database access token.- Parameters:
databaseAccessToken- A database access token. Not null. Not retained.- Returns:
- A new
EndUserSecurityContextwith a new database access token. Not null.
-
endUserTokenExpiration
Returns the expiration time of the end user token, or
OffsetDateTime.MAXif the end user is identified by a name instead of a token. This method can be used to check if the end user token needs to be refreshed before a long-running sequence of SQL operations, as shown in this example.- Returns:
- The expiration time of the end user token, or
OffsetDateTime.MAXif the end user is identified by a name instead of a token. Not null.
-
endUserToken
Returns an
Optionalcontaining the end user token, orOptional.empty()if the end user is identified by a name instead of a token.- Returns:
- An
Optionalcontaining an end user token, orOptional.empty(). Not null. Thechar[]of a non-emptyOptionalis not retained.
-
withEndUserToken
Returns a copy of this
EndUserSecurityContextwith a new end user token.- Parameters:
endUserToken- An end user token. Not null. Not retained.- Returns:
- A new
EndUserSecurityContextwith a new end user token.
-
endUserName
Returns an
Optionalcontaining an end user name,Optional.empty()if the end user is identified by a security token instead of a name.- Returns:
- The name of an end user,
Optional.empty()if the end user is identified by a security token instead of a name.
-
withEndUserName
Returns a copy of this
EndUserSecurityContextwith a new end user name and automatically generated look-up key. The name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.- Parameters:
endUserName- The name of an end user. Not null.- Returns:
- A new
EndUserSecurityContextwith a new end user name and automatically generated look-up key. Not null.
-
withEndUserName
Returns a copy of this
EndUserSecurityContextwith a new end user name and look-up key. The name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.- Parameters:
endUserName- The name of an end user. Not null.lookUpKey- A look-up key for the end user context. Not null. Not retained.- Returns:
- A new
EndUserSecurityContextwith a new end user name and look-up key. Not null.
-
lookUpKey
Returns an
Optionalcontaining the look-up key for thisEndUserSecurityContext.It is possible that the value returned by this method does not match the look-up key which Oracle Database associates to this
EndUserSecurityContext. This can occur when anEndUserSecurityContextidentifies the end user with a token, rather than a name, and the Oracle JDBC Driver version is different from the Oracle Database version. For example, the value will not match if the Oracle Database version is 23.26.2.0.0 and the Oracle JDBC version is 23.26.3.0.0 or newer. When an end user is identified by a token, any value returned by this method should be used only for diagnostics and testing. Critical application logic should not assume the value will be identical to the look-up key that a past or future version of Oracle Database will associate to thisEndUserSecurityContext.This method is primarily intended for deconstruction of an
EndUserSecurityContextwhen the end user is identified by a name, rather than a token. If the end user is identified by a name, this method is guaranteed to return a non-emptyOptionalcontaining the same look-up key which the database associates to thisEndUserSecurityContext.- Returns:
- An
Optionalcontaining a look-up key, orOptional.empty(). TheCharSequenceof a non-emptyOptionalis not retained.
-
dataRoles
Returns an
unmodifiable setof application managed data roles that have been enabled for the end user.This method does not query the database to obtain all data roles that are enabled for the end user; It only returns the names of data roles provided to methods such as
withDataRoles(Collection).- Returns:
- A set of DATA ROLE names. Not null.
-
withDataRoles
Returns a copy of this
EndUserSecurityContextwith a new set of application managed data roles. A data role name should be enclosed in double-quotes if the DATA ROLE was declared using a quoted identifier.- Parameters:
dataRoles- An array of data role names. Not null, and must not contain null. Not retained.- Returns:
- A new
EndUserSecurityContextwith a new set of data roles. Not null.
-
withDataRoles
Returns a copy of this
EndUserSecurityContextwith a new set of application managed data roles. A data role name should be enclosed in double-quotes if the DATA ROLE was declared using a quoted identifier.- Parameters:
dataRoles- A Collection of data role names. Not null, and must not contain null. Not retained.- Returns:
- A new
EndUserSecurityContextwith a new set of data roles. Not null.
-
attributes
Returns an
unmodifiableMapof END USER CONTEXT attributes, represented as unmodifiable instances ofOracleJsonObject. TheMapreturned by this method maps the name of an END USER CONTEXT to anOracleJsonObjectcontaining the values of its attributes.This method does not query the database to obtain all attributes for an end user. It only returns attributes configured by methods such as
withAttributes(Map).- Returns:
- A Map containing the values of END USER CONTEXT attributes. The Map is not null, does not contain a null key, and does not contain null values.
-
withAttributes
Returns a copy of this
EndUserSecurityContextwith a new set of attributes mapped to a fully qualified END USER CONTEXT name. A fully qualified name includes both a schema name and the END USER CONTEXT name, as in "app_schema.user_context" rather than "user_context". An end user context name should be enclosed in double-quotes if the END USER CONTEXT was declared using a quoted identifier.- Parameters:
contextName- The fully qualified name of an END USER CONTEXT. Not null.attributes- The attributes of an END USER CONTEXT. Not null. Not retained.- Returns:
- A new
EndUserSecurityContextwith a new set of attributes. Not null.
-
withAttributes
Returns a copy of this
EndUserSecurityContextwith a new set of attributes mapped to their fully qualified END USER CONTEXT names. A fully qualified name includes both a schema name and the END USER CONTEXT name, as in "app_schema.user_context" rather than "user_context". An end user context name should be enclosed in double-quotes if an END USER CONTEXT was declared using a quoted identifier.- Parameters:
attributes- A mapping of fully qualified END USER CONTEXT names to their attributes. The Map must not be null and must not contain null keys or values. TheMapand itsOracleJsonObjectvalues are not retained.- Returns:
- A new
EndUserSecurityContextwith a new set of attributes. Not null.
-
equals
Returnstrueif an object is anEndUserSecurityContextencapsulating values which are all equal to those of thisEndUserSecurityContext. -
hashCode
public int hashCode()Returns a hash code computed from all values encapsulated by thisEndUserSecurityContext. -
toString
Returns a String containing non-security-sensitive values encapsulated by thisEndUserSecurityContext; This method will NOT return the clear text form of security tokens, look-up keys, or any other information that grants access to a secured system. -
createWithToken
public static EndUserSecurityContext createWithToken(CharSequence databaseAccessToken, CharSequence endUserToken) Creates a security context in which an application with a database access token executes database operations for an end user identified by a security token.
- Parameters:
databaseAccessToken- A security token that authorizes an application to access the database. Not null. Not retained.endUserToken- A security token that identifies an end user. Not null. Not retained.- Returns:
- An
EndUserSecurityContext. Not null.
-
createWithName
public static EndUserSecurityContext createWithName(CharSequence databaseAccessToken, String endUserName) Creates a security context in which an application with a database access token executes database operations for an end user identified by a name. The look-up key of the
EndUserSecurityContextis automatically generated by this class. The end user name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.- Parameters:
databaseAccessToken- A security token that authorizes an application to access the database. Not null. Not retained.endUserName- The name of an end user. Not null. Not retained.- Returns:
- An
EndUserSecurityContext. Not null.
-
createWithName
public static EndUserSecurityContext createWithName(CharSequence databaseAccessToken, String endUserName, CharSequence lookUpKey) Creates a security context in which an application with a database access token executes database operations for an end user identified by a name. The look-up key of the
EndUserSecurityContextis provided to this method, rather than automatically generated. The end user name should be enclosed in double-quotes if the END USER was declared using a quoted identifier.- Parameters:
databaseAccessToken- A security token that authorizes an application to access the database. Not null. Not retained.endUserName- The name of an end user. Not null.lookUpKey- The look-up key of theEndUserSecurityContext. Not null. Not retained.- Returns:
- An
EndUserSecurityContext. Not null.
-