Skip to content

Commit b9945af

Browse files
authored
Add files via upload
1 parent 7440e19 commit b9945af

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.*/
2+
/*
3+
DESCRIPTION
4+
This code example shows how to use JDBC and UCP's programmatic APIs for
5+
database authentication, using a token issued by the Oracle Cloud
6+
Infrastructure (OCI) Identity Service.
7+
8+
To run this example, Oracle Database must be configured for IAM
9+
authentication, as described in the Security Guide:
10+
https://docs.oracle.com/en/database/oracle/oracle-database/19/dbseg/authenticating-and-authorizing-iam-users-oracle-autonomous-databases.html
11+
12+
To run this example, the OCI SDK for Java must be configured with a
13+
configuration profile of an IAM user that is mapped to a database user.
14+
The OCI Developer Guide describes how to setup and configure the SDK:
15+
https://docs.oracle.com/en-us/iaas/Content/API/Concepts/devguidesetupprereq.htm
16+
17+
To run this example, use JDK 11 or newer, and have the classpath include
18+
the latest builds of Oracle JDBC, Oracle UCP, Oracle PKI, and the OCI SDK
19+
for Java. These artifacts can be obtained from Maven Central by declaring
20+
these dependencies:
21+
<dependency>
22+
<groupId>com.oracle.database.jdbc</groupId>
23+
<artifactId>ojdbc11-production</artifactId>
24+
<version>21.4.0.0.1</version>
25+
<type>pom</type>
26+
</dependency>
27+
<dependency>
28+
<groupId>com.oracle.oci.sdk</groupId>
29+
<artifactId>oci-java-sdk-identitydataplane</artifactId>
30+
<version>2.12.0</version>
31+
</dependency>
32+
33+
To run this example, set the values of static final fields declared in
34+
this class:
35+
DATABASE_URL = URL of an Autonomous Database that JDBC connects to
36+
OCI_PROFILE = A profile from $HOME/.oci/config of an IAM user that is mapped
37+
to an Autonomous Database user
38+
39+
NOTES
40+
Use JDK 11 or above
41+
MODIFIED (MM/DD/YY)
42+
Michael-A-McMahon 12/07/21 - Creation
43+
*/
44+
45+
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
46+
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
47+
import com.oracle.bmc.identitydataplane.DataplaneClient;
48+
import com.oracle.bmc.identitydataplane.model.GenerateScopedAccessTokenDetails;
49+
import com.oracle.bmc.identitydataplane.requests.GenerateScopedAccessTokenRequest;
50+
import oracle.jdbc.AccessToken;
51+
import oracle.jdbc.OracleConnectionBuilder;
52+
import oracle.jdbc.datasource.OracleDataSource;
53+
import oracle.ucp.jdbc.PoolDataSource;
54+
import oracle.ucp.jdbc.PoolDataSourceFactory;
55+
56+
import java.io.IOException;
57+
import java.security.KeyPair;
58+
import java.security.KeyPairGenerator;
59+
import java.security.NoSuchAlgorithmException;
60+
import java.security.PublicKey;
61+
import java.sql.Connection;
62+
import java.sql.ResultSet;
63+
import java.sql.SQLException;
64+
import java.sql.Statement;
65+
import java.util.Base64;
66+
import java.util.concurrent.ExecutorService;
67+
import java.util.concurrent.Executors;
68+
import java.util.function.Supplier;
69+
70+
import static java.util.concurrent.TimeUnit.SECONDS;
71+
72+
/**
73+
* The following is a summary of methods that can be found in this class,
74+
* with a brief description of what task each method performs:
75+
*
76+
* requestToken(PublicKey) shows how to request a token from OCI.
77+
*
78+
* createAccessToken() shows how to create an instance of
79+
* oracle.jdbc.AccessToken using the token requested from OCI.
80+
*
81+
* connectJdbc() shows how to create a single JDBC connection using an
82+
* AccessToken.
83+
*
84+
* connectJdbcDataSource() shows how to create multiple JDBC connections
85+
* using a Supplier that outputs a cached AccessToken.
86+
*
87+
* connectUcpDataSource() shows how to create a pool of JDBC connections
88+
* using a Supplier that outputs a cached AccessToken.
89+
*/
90+
public class JdbcTokenAuthentication {
91+
92+
/**
93+
* An Oracle Cloud Infrastructure (OCI) configuration profile name. Profiles
94+
* are typically defined in $HOME/.oci/config. An access token is requested
95+
* for the user identified by this profile, and access is requested for all
96+
* databases within that user's tenancy.
97+
*/
98+
private static final String OCI_PROFILE =
99+
/*TODO: Set this to your profile name*/ "DEFAULT";
100+
101+
/**
102+
* The URL that JDBC connects with. The default value is using an
103+
* alias from $TNS_ADMIN/tnsnames.ora
104+
*/
105+
private static final String DATABASE_URL =
106+
/*TODO: Set this to your database url*/ "jdbc:oracle:thin:@your_db_name_tp?TNS_ADMIN=/path/to/your/wallet";
107+
108+
// Print the configured values in this static block
109+
static {
110+
System.out.println("DATABASE_URL is set to: " + DATABASE_URL);
111+
System.out.println("OCI_PROFILE is set to: " + OCI_PROFILE);
112+
}
113+
114+
/**
115+
* This main method executes example code to connect with both JDBC and UCP
116+
*/
117+
public static void main(String[] args) {
118+
connectJdbc();
119+
connectJdbcDataSource();
120+
connectUcpDataSource();
121+
}
122+
123+
/**
124+
* Creates an {@link AccessToken} that JDBC or UCP can use to authenticate
125+
* with Oracle Database. The token is requested from the OCI Identity Service.
126+
* @return An AccessToken from OCI
127+
*/
128+
private static AccessToken createAccessToken() {
129+
130+
// Generate a public/private key pair. This is used to protect the token
131+
// from replay attacks. A client must prove possession of the private
132+
// key in order to access the database using the token.
133+
final KeyPair keyPair;
134+
try {
135+
keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
136+
}
137+
catch (NoSuchAlgorithmException noSuchAlgorithmException) {
138+
// Not recovering if an RSA KeyPairGenerator is not installed
139+
throw new RuntimeException(noSuchAlgorithmException);
140+
}
141+
142+
// Request an access token from the OCI Identity Service. The token
143+
// will identify the public key that is paired to the private key
144+
String token = requestToken(keyPair.getPublic());
145+
146+
// Create an AccessToken object with the JWT string and the private key
147+
return AccessToken.createJsonWebToken(
148+
token.toCharArray(), keyPair.getPrivate());
149+
}
150+
151+
/**
152+
* Requests an access token from the OCI Identity service. The token will
153+
* identify a {@code publicKey} that is paired to a private key. Possession of
154+
* the private key must be proven in order to access the database using the
155+
* token.
156+
* @param publicKey Public key identified by the token
157+
* @return Base 64 encoding of a JWT access token
158+
*/
159+
private static String requestToken(PublicKey publicKey) {
160+
161+
// Read the configuration identified by the OCI_PROFILE
162+
final AuthenticationDetailsProvider authentication;
163+
try {
164+
authentication = new ConfigFileAuthenticationDetailsProvider(OCI_PROFILE);
165+
}
166+
catch (IOException ioException) {
167+
// Not recovering if the profile can not be read
168+
throw new RuntimeException(ioException);
169+
}
170+
171+
// Request the token with the public key encoded as base 64 text
172+
String base64Key =
173+
Base64.getEncoder()
174+
.encodeToString(publicKey.getEncoded());
175+
176+
// This scope uses the * character to identify all databases in the cloud
177+
// tenancy of the authenticated user. The * could be replaced with the OCID
178+
// of a compartment, or of a particular database within a compartment
179+
String scope = "urn:oracle:db::id::*";
180+
181+
// Create a GenerateScopedAccessTokenDetails object with the public key
182+
// and the scope
183+
GenerateScopedAccessTokenDetails tokenDetails =
184+
GenerateScopedAccessTokenDetails.builder()
185+
.publicKey(base64Key)
186+
.scope(scope)
187+
.build();
188+
189+
// Request an access token using a DataplaneClient
190+
try (DataplaneClient client = new DataplaneClient(authentication)) {
191+
return client.generateScopedAccessToken(
192+
GenerateScopedAccessTokenRequest.builder()
193+
.generateScopedAccessTokenDetails(tokenDetails)
194+
.build())
195+
.getSecurityToken()
196+
.getToken();
197+
}
198+
}
199+
200+
/**
201+
* Creates a single connection using Oracle JDBC. A call to
202+
* {@link oracle.jdbc.OracleConnectionBuilder#accessToken(AccessToken)}
203+
* configures JDBC to authenticate with a token requested from the OCI
204+
* Identity Service.
205+
*/
206+
private static void connectJdbc() {
207+
try {
208+
// Create a single AccessToken
209+
AccessToken accessToken = createAccessToken();
210+
211+
// Configure an OracleConnectionBuilder to authenticate with the
212+
// AccessToken
213+
OracleDataSource dataSource = new oracle.jdbc.pool.OracleDataSource();
214+
dataSource.setURL(DATABASE_URL);
215+
OracleConnectionBuilder connectionBuilder =
216+
dataSource.createConnectionBuilder()
217+
.accessToken(accessToken);
218+
219+
// Connect and print the database user name
220+
try (Connection connection = connectionBuilder.build()) {
221+
System.out.println(
222+
"Authenticated with JDBC as: " + queryUser(connection));
223+
}
224+
}
225+
catch (SQLException sqlException) {
226+
// Not recovering if the connection fails
227+
throw new RuntimeException(sqlException);
228+
}
229+
}
230+
231+
/**
232+
* Creates multiple connections with Oracle JDBC. A call
233+
* to {@link OracleDataSource#setTokenSupplier(Supplier)} configures JDBC to
234+
* authenticate with tokens output by the {@link Supplier}. The
235+
* {@code Supplier} requests tokens from the OCI Identity Service.
236+
*/
237+
private static void connectJdbcDataSource() {
238+
try {
239+
240+
// Define a Supplier that outputs a cached AccessToken. Caching the
241+
// token will minimize the number of OCI Identity Service requests. New
242+
// tokens will only be requested after a previously cached token has
243+
// expired.
244+
Supplier<? extends AccessToken> tokenCache =
245+
AccessToken.createJsonWebTokenCache(() -> createAccessToken());
246+
247+
// Configure an OracleConnectionBuilder to authenticate with the
248+
// AccessToken
249+
OracleDataSource dataSource = new oracle.jdbc.pool.OracleDataSource();
250+
dataSource.setURL(DATABASE_URL);
251+
dataSource.setTokenSupplier(tokenCache);
252+
253+
// Create multiple connections and print the database user name
254+
for (int i = 0; i < 3; i++) {
255+
try (Connection connection = dataSource.getConnection()) {
256+
System.out.println(
257+
"Authenticated with JDBC as: " + queryUser(connection));
258+
}
259+
}
260+
}
261+
catch (SQLException sqlException) {
262+
// Not recovering if the connection fails
263+
throw new RuntimeException(sqlException);
264+
}
265+
}
266+
267+
/**
268+
* Creates multiple connections with Universal Connection Pool (UCP). A call
269+
* to {@link PoolDataSource#setTokenSupplier(Supplier)} configures UCP to
270+
* authenticate with tokens output by the {@link Supplier}. The
271+
* {@code Supplier} requests tokens from the OCI Identity Service.
272+
*/
273+
private static void connectUcpDataSource() {
274+
275+
// Define a Supplier that outputs a cached AccessToken. Caching the
276+
// token will minimize the number of OCI Identity Service requests. New
277+
// tokens will only be requested after a previously cached token has
278+
// expired.
279+
Supplier<? extends AccessToken> tokenCache =
280+
AccessToken.createJsonWebTokenCache(() -> createAccessToken());
281+
282+
// Configure UCP to use the cached token supplier when creating
283+
// Oracle JDBC connections
284+
final PoolDataSource poolDataSource;
285+
try {
286+
poolDataSource = PoolDataSourceFactory.getPoolDataSource();
287+
poolDataSource.setConnectionFactoryClassName(
288+
oracle.jdbc.pool.OracleDataSource.class.getName());
289+
poolDataSource.setURL(DATABASE_URL);
290+
poolDataSource.setMaxPoolSize(2);
291+
poolDataSource.setTokenSupplier(tokenCache);
292+
}
293+
catch (SQLException sqlException) {
294+
// Not recovering if UCP configuration fails
295+
throw new RuntimeException(sqlException);
296+
}
297+
298+
// Execute multiple threads that share the pool of connections
299+
ExecutorService executorService =
300+
Executors.newFixedThreadPool(poolDataSource.getMaxPoolSize());
301+
try {
302+
for (int i = 0; i < poolDataSource.getMaxPoolSize() * 2; i++) {
303+
executorService.execute(() -> {
304+
try (Connection connection = poolDataSource.getConnection()) {
305+
System.out.println(
306+
"Authenticated with UCP as: " + queryUser(connection));
307+
}
308+
catch (SQLException sqlException) {
309+
sqlException.printStackTrace();
310+
}
311+
});
312+
}
313+
}
314+
finally {
315+
executorService.shutdown();
316+
try {
317+
executorService.awaitTermination(60, SECONDS);
318+
}
319+
catch (InterruptedException interruptedException) {
320+
// Print the error if interrupted
321+
interruptedException.printStackTrace();
322+
}
323+
}
324+
}
325+
326+
/**
327+
* Queries the database to return the user that a {@code connection} has
328+
* authenticated as.
329+
* @param connection Connection to a database
330+
* @return Database user of the connection
331+
* @throws SQLException If the database query fails
332+
*/
333+
private static String queryUser(Connection connection) throws SQLException {
334+
try (Statement statement = connection.createStatement()) {
335+
ResultSet resultSet =
336+
statement.executeQuery("SELECT USER FROM sys.dual");
337+
resultSet.next();
338+
return resultSet.getString(1);
339+
}
340+
}
341+
}

0 commit comments

Comments
 (0)