firebase | Verify ID tokens using a third-party JWT library - java

Attempting to validate firebase id tokens using jjwt. Using GoogleCredential class to pull the private key. But I'm not sure if that's correct. Receiving an error: JWT signature does not match locally computed signature.Am I supposed to be using the private key here from service account json? Maybe I'm misunderstanding what ...setSigningKey(...) takes in.
#Service
public class FirebaseAuthVerifier implements AuthVerifier {
private static final Logger logger = LoggerFactory.getLogger(FirebaseAuthVerifier.class);
#Autowired
private FirebaseProperties fbProps;
public boolean verify(AuthToken token) throws GeneralSecurityException, IOException {
// get google credential
InputStream stream = new FileInputStream("src/main/resources/service-account.json");
ByteArrayOutputStream streamCopy = new ByteArrayOutputStream();
ByteStreams.copy(stream, streamCopy);
stream.close();
GoogleCredential gc = GoogleCredential.fromStream(
new ByteArrayInputStream(streamCopy.toByteArray()),
new NetHttpTransport(),
GsonFactory.getDefaultInstance());
try {
Jwts.parser().setSigningKey(gc.getServiceAccountPrivateKey()).parse(token.getTokenId());
} catch(Exception e) {
// log
logger.info("Firebase auth token verification error: ");
logger.info(e.getMessage());
// claims may have been tampered with
return false;
}
return true;
}
}

You're on the right lines! The key from the service account is used when creating JWTs to send to Google/Firebase. You really don't want to put that in your APK, as any malicious individual could steal it and use it to create ID tokens as you!
When you're validating a token from Firebase, you need to check Firebase's own keys - luckily, these are public! You can grab them from https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com - they rotate every few hours. If you look in that file you'll see it's a JSON dictionary, like this:
"8226146523a1b8894ba03ad525667b9475d393f5": "---CERT---",
The key in this is the kid field in the header of the ID token JWT - it corresponds to the key the token was signed with, meaning the cert that corresponds can be used to verify the signature.
Take a look at the (server side) docs for validating ID tokens for more.

Using custom jwt id token validation
#Service
public class FirebaseAuthVerifier implements AuthVerifier {
private static final Logger logger = LoggerFactory.getLogger(FirebaseAuthVerifier.class);
private static final String pubKeyUrl = "https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com";
/**
*
* #param token
* #return
* #throws GeneralSecurityException
* #throws IOException
*/
public boolean verify(AuthToken token) throws GeneralSecurityException, IOException {
// get public keys
JsonObject publicKeys = getPublicKeysJson();
// verify count
int size = publicKeys.entrySet().size();
int count = 0;
// get json object as map
// loop map of keys finding one that verifies
for (Map.Entry<String, JsonElement> entry: publicKeys.entrySet()) {
// log
logger.info("attempting jwt id token validation with: ");
try {
// trying next key
count++;
// get public key
PublicKey publicKey = getPublicKey(entry);
// validate claim set
Jwts.parser().setSigningKey(publicKey).parse(token.getTokenId());
// success, we can return
return true;
} catch(Exception e) {
// log
logger.info("Firebase id token verification error: ");
logger.info(e.getMessage());
// claims may have been tampered with
// if this is the last key, return false
if (count == size) {
return false;
}
}
}
// no jwt exceptions
return true;
}
/**
*
* #param entry
* #return
* #throws GeneralSecurityException
*/
private PublicKey getPublicKey(Map.Entry<String, JsonElement> entry) throws GeneralSecurityException, IOException {
String publicKeyPem = entry.getValue().getAsString()
.replaceAll("-----BEGIN (.*)-----", "")
.replaceAll("-----END (.*)----", "")
.replaceAll("\r\n", "")
.replaceAll("\n", "")
.trim();
logger.info(publicKeyPem);
// generate x509 cert
InputStream inputStream = new ByteArrayInputStream(entry.getValue().getAsString().getBytes("UTF-8"));
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(inputStream);
return cert.getPublicKey();
}
/**
*
* #return
* #throws IOException
*/
private JsonObject getPublicKeysJson() throws IOException {
// get public keys
URI uri = URI.create(pubKeyUrl);
GenericUrl url = new GenericUrl(uri);
HttpTransport http = new NetHttpTransport();
HttpResponse response = http.createRequestFactory().buildGetRequest(url).execute();
// store json from request
String json = response.parseAsString();
// disconnect
response.disconnect();
// parse json to object
JsonObject jsonObject = new JsonParser().parse(json).getAsJsonObject();
return jsonObject;
}
}

Related

How to connect to Azure Blob Storage using Service Principal with Java/Spring-Boot

I'm writing an application using Spring Boot and Java that will be writing files to Azure Blob Storage. How can I use a Service Principal to authenticate? The details of the SP should ideally be read in via some properties or an external file.
I've been wading through the reams of documentation and examples, all of which don't seem to be quite what I'm looking for. Most examples that I've seen use the Storage Account Key which I don't want to do.
Some example code would be really appreciated. As I said, I'm struggling to find a decent example (both of how to use an SP but also generally how to write to Azure BLOB Storage in Java) as there seems to be so many different ways of accessing storage scattered around in the microsoft docs.
You can use ADAL4J to acquire a token, and then use the token to write to blobs.
Add role assignment to your principal.
Get token.
public static String getToken() throws Exception {
String TENANT_ID = "your tenant id or name, e4c9*-*-*-*-*57fb";
String AUTHORITY = "https://login.microsoftonline.com/" + TENANT_ID;
String CLIENT_ID = "your application id, dc17*-*-*-*a5e7";
String CLIENT_SECRET = "the secret, /pG*32";
String RESOURCE = "https://storage.azure.com/";
String ACCESS_TOKEN = null;
ExecutorService service = Executors.newFixedThreadPool(1);
AuthenticationContext context = null;
try {
context = new AuthenticationContext(AUTHORITY, false, service);
ClientCredential credential = new ClientCredential(CLIENT_ID, CLIENT_SECRET);
Future<AuthenticationResult> future = context.acquireToken(RESOURCE, credential, null);
ACCESS_TOKEN = future.get().getAccessToken();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} finally {
service.shutdown();
}
return ACCESS_TOKEN;
}
Access blob.
public static void main(String[] args) throws Exception {
String token = getToken();
StorageCredentialsToken credentialsToken = new StorageCredentialsToken("storagetest789", token);
CloudBlobClient blobClient = new CloudBlobClient(new URI("https://storagetest789.blob.core.windows.net/"), credentialsToken);
CloudBlobContainer blobContainer = blobClient.getContainerReference("pub");
CloudBlockBlob blockBlob = blobContainer.getBlockBlobReference("test.txt");
blockBlob.uploadText("Test!");
}
Hope it helps.
I have created an article on connecting Spring Boot App with Azure Storage account using Serivce Principal you can refer that.
https://medium.com/#iamdeveshkumar/using-azure-blob-storage-with-a-spring-boot-app-6238c137df7
pom.xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.12.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.1</version>
</dependency>
application.properties
app.config.azure.client-id=xxxxxxxxxxx
app.config.azure.client-secret=xxxxxxxxxxx
app.config.azure.tenant-id=xxxxxxxxxxx
app.config.azure.storage-id=xxxxxxxxxxx
app.config.azure.storage-endpoint=https://{STORAGE-ID}.blob.core.windows.net
app.config.azure.storage.container=xxxxxxxxxxx
AzureStorageConfiguration.java
#Data
#Configuration
#Slf4j
public class AzureStorageConfiguration {
private static final Logger logger = LoggerFactory.getLogger(AzureStorageConfiguration.class);
#Value("${app.config.azure.client-id}")
private String clientId;
#Value("${app.config.azure.client-secret}")
private String clientSecret;
#Value("${app.config.azure.tenant-id}")
private String tenantId;
#Value("${app.config.azure.storage-id}")
private String storageId;
#Value("${app.config.azure.storage-endpoint}")
private String storageEndpoint;
#Value("${app.config.azure.storage.container}")
private String storageContainer;
/**
* Blob service client builder blob service client builder.
*
* #return the blob service client builder
*/
#Bean
public BlobServiceClientBuilder blobServiceClientBuilder() {
return new BlobServiceClientBuilder()
.credential(getAzureClientCredentials())
.endpoint(getStorageEndpoint());
}
private ClientSecretCredential getAzureClientCredentials() {
return new ClientSecretCredentialBuilder()
.clientId(clientId)
.clientSecret(clientSecret)
.tenantId(tenantId)
.build();
}
/**
* Gets storage endpoint.
*
* #return the storage endpoint
*/
public String getStorageEndpoint() {
return storageEndpoint.replace("{STORAGE-ID}", storageId);
}
/**
* A util method to upload a file to Azure Storage.
*
* #param blobServiceClientBuilder service client builder
* #return BlobServiceAsyncClient blob service async client
*/
#Bean(name = "blobServiceAsyncClient")
public BlobServiceAsyncClient blobServiceAsyncClient(
BlobServiceClientBuilder blobServiceClientBuilder) {
/*
retryDelay is by default 4ms and maxRetryDelay is by default 120ms
*/
return blobServiceClientBuilder.retryOptions(
new RequestRetryOptions(
RetryPolicyType.EXPONENTIAL,
5,
Duration.ofSeconds(300L),
null,
null,
null)).buildAsyncClient();
}
}
Then you can use the BlobServiceAsyncClient to create BlobAsyncClient for various blob operations.
/**
* Get blob async client blob async client.
*
* #param container the container
* #param blobName the blob name
* #return the blob async client
*/
public BlobAsyncClient getBlobAsyncClient(String container, String blobName) {
BlobContainerAsyncClient blobContainerAsyncClient =
blobServiceAsyncClient.getBlobContainerAsyncClient(container);
return blobContainerAsyncClient.getBlobAsyncClient(blobName);
}
/**
* Upload to azure blob.
*
* #param container the container
* #param blobName the blob name
* #param data the data
*/
public void uploadToAzureBlob(String container, String blobName, byte[] data) {
BlobAsyncClient blobAsyncClient = getBlobAsyncClient(container, blobName);
long blockSize = 2L * 1024L * 1024L; //2MB
blobAsyncClient.upload(covertByteArrayToFlux(data),
getTransferOptions(blockSize), true)
.doOnSuccess(blockBlobItem -> logger.info("Successfully uploaded !!"))
.doOnError(throwable -> logger.error(
"Error occurred while uploading !! Exception:{}",
throwable.getMessage()))
.subscribe();
}
/**
* Covert byte array to flux flux.
*
* #param byteArray the byte array
* #return the flux
*/
public Flux<ByteBuffer> covertByteArrayToFlux(byte[] byteArray) {
return Flux.just(ByteBuffer.wrap(byteArray));
}
/**
* Creating TransferOptions.
*
* #param blockSize represents block size
* #return ParallelTransferOptions transfer options
*/
public ParallelTransferOptions getTransferOptions(long blockSize) {
return new ParallelTransferOptions()
.setBlockSizeLong(blockSize)
.setMaxConcurrency(5)
.setProgressReceiver(
bytesTransferred -> logger.info("Uploading bytes:{}", bytesTransferred));
}
For more details and code you can refer to my github repo
https://github.com/kdevesh/azure-storage-spring-boot-app
P.S. I am using the async flavour of Blob Client there is a sync flavour also available if somebody wants to use that.
Another way to get the Access token is
using the MS authentication lib
This library used "builders" to build out the confidential client. If you use that class, it handles refreshing the token for you and handles the cache
I use the method as Jack Jia , but it don't work.....i can got the token ,but when i upload , something wrong enter image description here

How can I access the detail of Firebase Cloud messages that been sent using FCM API?

I've build a prototype lambda function that can send automated push notifications by querying my database based on the rules stored in Firebase. This function is scheduled to run everyday. By this function below, I am calling the Messaging object
private void sentAutomatedMessages(List<String> tokens, CardAbandonmentRule rule) {
for (String token : tokens) {
//Create Messaging object for every user that fits in this user
Messaging msgHandler = new Messaging(rule.getTitle(), rule.getMessage(), token);
try {
msgHandler.handleSingleDevicePush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
The class definition and the methods for sending push notifications =>
public class Messaging {
private static final String PROJECT_ID = "<project_id>";
private static final String BASE_URL = "https://fcm.googleapis.com";
private static final String FCM_SEND_ENDPOINT = "/v1/projects/" + PROJECT_ID + "/messages:send";
private static final String MESSAGING_SCOPE = "https://www.googleapis.com/auth/firebase.messaging";
private static final String[] SCOPES = {MESSAGING_SCOPE};
private String title;
private String message;
private String token;
public Messaging(String title, String message, String token) {
this.title = title;
this.message = message;
this.token = token; // <FCM_token>
}
/**
* Retrieve a valid access token that can be use to authorize requests to the FCM REST
* API.
*
* #return Access token.
* #throws IOException
*/
private static String getAccessToken() throws IOException {
GoogleCredential googleCredential = GoogleCredential
.fromStream(new FileInputStream("<firebase_private_key.json>"))
.createScoped(Arrays.asList(SCOPES));
googleCredential.refreshToken();
return googleCredential.getAccessToken();
}
/**
* Create HttpURLConnection that can be used for both retrieving and publishing.
*
* #return Base HttpURLConnection.
* #throws IOException
*/
private static HttpURLConnection getConnection() throws IOException {
// [START use_access_token]
URL url = new URL(BASE_URL + FCM_SEND_ENDPOINT);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
String accessToken = getAccessToken();
System.out.println(accessToken);
httpURLConnection.setRequestProperty("Authorization", "Bearer " + accessToken);
httpURLConnection.setRequestProperty("Content-Type", "application/json; UTF-8");
return httpURLConnection;
// [END use_access_token]
}
/**
* Construct the body of a notification message request.
*
* #return JSON of notification message.
*/
private JsonObject buildNotificationMessage() {
JsonObject jNotification = new JsonObject();
jNotification.addProperty("title", this.title);
jNotification.addProperty("body", this.message);
JsonObject jMessage = new JsonObject();
jMessage.add("notification", jNotification);
jMessage.addProperty("token", this.token);
JsonObject jFcm = new JsonObject();
jFcm.add("message", jMessage);
return jFcm;
}
/**
* Send request to FCM message using HTTP.
*
* #param fcmMessage Body of the HTTP request.
* #throws IOException
*/
private static void sendtoSingleDevice(JsonObject fcmMessage) throws IOException {
HttpURLConnection connection = getConnection();
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(fcmMessage.toString());
outputStream.flush();
outputStream.close();
int responseCode = connection.getResponseCode();
if (responseCode == 200) {
String response = inputstreamToString(connection.getInputStream());
System.out.println("Message sent to Firebase for delivery, response:");
System.out.println(response);
} else {
System.out.println("Unable to send message to Firebase:");
String response = inputstreamToString(connection.getErrorStream());
System.out.println(response);
}
}
/**
* Public method to send Push Notification
*
* #throws IOException
*/
public void handleSingleDevicePush() throws IOException {
JsonObject notificationMessage = buildNotificationMessage();
sendtoSingleDevice(notificationMessage);
}
After I run the buildNotificationMessage(), the object is formed like example below.
// Example Notification Message to send over HTTP
{
"message": {
"notification": {
"title": "title",
"body": "body"
},
"token": "<FCM_token>"
}
}
The response is =>
{ "name": "projects/<project_id>/messages/1542324302450893"}
I have to develop a dashboard for listing the sent messages, open rate and analytics. However, I need some guidance.
1 - What can I do with this name given as a response from the FCM REST API ? I didn't see anything in the documentation for getting the details of messages.
2 - Is there a better way for sending bulk messages for multiple unique FCM token ? I see some stuff about device groups but Firebase says it's for a different purpose.
Typically, "group" refers a set of different devices that belong to a single user.
Thanks
After I've contacted with Firebase Support, they recommend me to use BigQuery by Google to see datasets for firebase messaging functionality.
After you enable the BigQuery integration in Firebase settings, you just need to go to BigQuery console.
A query like down below, will give you details for given message.
SELECT *
FROM `<project_name>.firebase_messaging.data`
WHERE
_PARTITIONTIME = TIMESTAMP('<date as YYYY-MM-DD>')
AND message_id = '<your message id>'
AND instance_id = '<your instance id>'
ORDER BY event_timestamp;
Link to see more examples and read about the BigQuery integration for FCM =>
Understanding the message delivery

JWT Token verification with Java

I am facing a issue that whenever I am signing a token also I parse it and it is not throwing any signature exception.
You can see the key are different still it giving me the proper response.
public class JwtUtil {
public String parseToken(String token) {
try {
Jws<Claims> jwt = Jwts.parser()
.setSigningKey("Test#12")
.parseClaimsJws(token);
System.out.println(jwt.getBody().getSubject());
return "Valid";
} catch (SignatureException jwtException) {
jwtException.printStackTrace();
return null;
}
}
public String generateToken() {
Claims claim = Jwts.claims();
claim.put("GivenName", "Johnny");
claim.put("Surname", "Rocket");
claim.put("Email", "jrocket#example.com");
return Jwts.builder().setHeaderParam("typ", "JWT").setClaims(claim)
.setIssuer("Online JWT Builder")
.setAudience("www.example.com").setSubject("jrocket#example.com")
.signWith(SignatureAlgorithm.HS256, "Test#123").compact();
}
public static void main(String[] args) {
JwtUtil jwtUtil = new JwtUtil();
String token = jwtUtil.generateToken();
System.out.println(token);
JwtUtil jwtUtil1 = new JwtUtil();
jwtUtil1.parseToken(token);
}
}
Really Test#12 and Test#123 are the same key
It is due to JwtBuilder.signWith(SignatureAlgorithm alg, String base64EncodedSecretKey). assumes that you are providing a key in base64 and your keys are not base64. When the method decodes from base64 to byte[] the java converter used by jjwt provides a representation of the string. Test#12 and Test#123 are encoded with the byte array
See https://stackoverflow.com/a/38269014/6371459
You can test yourself with
System.out.println(
javax.xml.bind.DatatypeConverter.printBase64Binary(
javax.xml.bind.DatatypeConverter.parseBase64Binary("Test#12")));
System.out.println(
javax.xml.bind.DatatypeConverter.printBase64Binary(
javax.xml.bind.DatatypeConverter.parseBase64Binary("Test#123")));
Try a (more) different key and the SignatureException will be thrown

How to Add new column dynamically to already existed table in bigquery..?

I created a csv file with three columns in a row..in google bigquery in created a dataset with one table with csv file ....for this i completed my java code...but now i have to add a new column to existed row dynamically in java code..?
// Main method for data print.
#SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) throws IOException, InterruptedException {
// Create a new BigQuery client authorized via OAuth 2.0 protocol
Bigquery bigquery = createAuthorizedClient();
TableRow row = new TableRow();
row.set("Column1", "Sample23");
row.set("Column2", 7);
row.set("Column3", "Sample25");
TableDataInsertAllRequest.Rows rows = new TableDataInsertAllRequest.Rows();
rows.setJson(row);
List rowList = new ArrayList();
rowList.add(rows);
TableDataInsertAllRequest content =
new TableDataInsertAllRequest().setRows(rowList);
TableDataInsertAllResponse response = bigquery.tabledata().insertAll(PROJECT_ID, DATASET_ID, TABLE_ID, content).execute();
System.out.println("Final response = " + response);
}
There are two table operations Update and Patch.
You need to use the Update command, to add new columns to your schema.
Important side notes:
order is important. If you change the ordering, it will look like an incompatible schema.
you can only add new fields at the end of the table. On the old columns, you have the option to change required to nullable.
you cannot add a required field to an existing schema.
you cannot remove old fields, once a table's schema has been specified you cannot change it without first deleting all the of the data associated with it. If you want to change a table's schema, you must specify a writeDisposition of WRITE_TRUNCATE. For more information, see the Jobs resource.
Here is an example of a curl session that adds fields to a schema. It should be relatively easy to adapt to Java. It uses auth.py from here
When using Table.Update(), you must include the full table schema again. If you don't provide an exact matching schema you can get: Provided Schema does not match Table. For example I didn't paid attention to details and in one of my update calls I didn't include an old field like created and it failed.
Actually I didn't use any jobs in my java code. I simply created a dataset with one table with a row in three columns. Now I have to add new column at java code not in csv file. I am posting my complete source code:
public class BigQueryJavaGettingStarted {
// Define required variables.
private static final String PROJECT_ID = "nvjsnsb";
private static final String DATASET_ID = "nvjjvv";
private static final String TABLE_ID = "sampledata";
private static final String CLIENTSECRETS_LOCATION = "client_secrets.json";
static GoogleClientSecrets clientSecrets = loadClientSecrets(CLIENTSECRETS_LOCATION);
// Static variables for API scope, callback URI, and HTTP/JSON functions
private static final List<String> SCOPES = Arrays.asList(BigqueryScopes.BIGQUERY);
private static final String REDIRECT_URI = "https://www.example.com/oauth2callback";
// Global instances of HTTP transport and JSON factory objects.
private static final HttpTransport TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
private static GoogleAuthorizationCodeFlow flow = null;
// Main method for data print.
#SuppressWarnings({ "rawtypes", "unchecked" })
public static void main(String[] args) throws IOException, InterruptedException {
// Create a new BigQuery client authorized via OAuth 2.0 protocol
Bigquery bigquery = createAuthorizedClient();
TableRow row = new TableRow();
row.set("Column1", "OneMoreCol1");
row.set("Column2", 79);
row.set("Column3", "OneMoreCol2");
TableDataInsertAllRequest.Rows rows = new TableDataInsertAllRequest.Rows();
rows.setJson(row);
List rowList = new ArrayList();
rowList.add(rows);
TableDataInsertAllRequest content = new TableDataInsertAllRequest().setRows(rowList);
TableDataInsertAllResponse response = bigquery.tabledata().insertAll(PROJECT_ID, DATASET_ID, TABLE_ID, content).execute();
System.out.println("Final response = " + response);
}
// Create Authorized client.
public static Bigquery createAuthorizedClient() throws IOException {
String authorizeUrl = new GoogleAuthorizationCodeRequestUrl(
clientSecrets,
REDIRECT_URI,
SCOPES).setState("").build();
System.out.println("Paste this URL into a web browser to authorize BigQuery Access:\n" + authorizeUrl);
System.out.println("... and type the code you received here: ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String authorizationCode = in.readLine();
// Exchange the auth code for an access token and refresh token
Credential credential = exchangeCode(authorizationCode);
return new Bigquery(TRANSPORT, JSON_FACTORY, credential);
}
// Exchange code method.
static Credential exchangeCode(String authorizationCode) throws IOException {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
}
// Get flow.
static GoogleAuthorizationCodeFlow getFlow() {
if (flow == null) {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport,
jsonFactory,
clientSecrets,
SCOPES)
.setAccessType("offline").setApprovalPrompt("force").build();
}
return flow;
}
// Load client secrets.
private static GoogleClientSecrets loadClientSecrets(String clientSecretsLocation) {
try {
clientSecrets = GoogleClientSecrets.load(new JacksonFactory(),
new InputStreamReader(BigQueryJavaGettingStarted.class.getResourceAsStream(clientSecretsLocation)));
} catch (Exception e) {
System.out.println("Could not load client_secrets.json");
e.printStackTrace();
}
return clientSecrets;
}
}

How to follow a request in the logs when using Restlet?

What is the best way of tracking a specific request in a log file of a Restlet servlet?
If there are multiple requests at the same time, I would like to be able to follow a single request in the logfile.
Since I use the slf4j Restlet extension, I thought about using a slf4j.MDC and adding a hash of the source-IP+port+timestamp to it. Or a number which increases with every request.
But perhaps there is another, easier way?
Probably easier to use the thread ID, and ensure that you log the start and end of each request. That gives you what you want without any additional coding (or CPU cycles).
Update:
If you're running multiple requests per thread then you should probably just generate a random ID and use that for tracking. Something like:
import java.security.SecureRandom;
public class StringUtils
{
private static final SecureRandom RANDOMSOURCE;
private static String CANDIDATES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static
{
RANDOMSOURCE = new SecureRandom();
}
/**
* Generate a random string of alphanumeric characters.
* <p>
* The string returned will contain characters randomly
* selected from upper- and lower-case a through z as
* well as the digits 0 through 9.
* #param length the length of the string to generate
* #return a string of random alphanumeric characters of the requested length
*/
public static String generateRandomString(int length)
{
final StringBuffer sb = new StringBuffer(length);
for (int i = 0; i < length; i++)
{
sb.append(CANDIDATES.charAt(RANDOMSOURCE.nextInt(62)));
}
return sb.toString();
}
}
And then, as you say, you can use MDC to create your request ID for each request and log the details in a filter (this is a Jersey filter but should look similar for Restlet):
...
private static final String REQUESTHIDEADER = "Request-ID";
private static final String REQUESTID = "REQUESTID";
private static final String REQUESTSTARTTIME = "RSTARTTIME";
#Override
public ContainerRequest filter(final ContainerRequest request)
{
final String requestid = Long.toHexString(Double.doubleToLongBits(Math.random()));
MDC.put(REQUESTID, requestid);
MDC.put(REQUESTSTARTTIME, String.valueOf(System.currentTimeMillis()));
if (LOGGER.isInfoEnabled())
{
LOGGER.info("Started: {} {} ({})", request.getMethod(), request.getPath(), requestid);
}
return request;
}
#Override
public ContainerResponse filter(final ContainerRequest request, final ContainerResponse response)
{
try
{
final Long startTime = Long.parseLong(MDC.get(REQUESTSTARTTIME));
final String rid = MDC.get(REQUESTID);
final long duration = System.currentTimeMillis() - startTime;
response.getHttpHeaders().add(REQUESTHIDEADER, rid);
LOGGER.info("Finished: {} {} ({} ms)", request.getMethod(), request.getPath(), String.valueOf(duration));
}
catch (Exception e)
{
LOGGER.warn("Finished {} {}", request.getMethod(), request.getPath());
}
return response;
}
As a bonus this also passes the request ID back to the requestor to allow for tracking in case of problems.

Categories

Resources