Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Java Maven CircleCI 2.0 configuration file
# Java Maven CircleCI 2.1 configuration file
#
# Check https://circleci.com/docs/2.0/language-java/ for more details
#
version: 2
version: 2.1

jobs:
build:
parameters:
jdk-version:
type: string
docker:
# specify the version you desire here
- image: cimg/openjdk:17.0.8
- image: cimg/openjdk:<< parameters.jdk-version >>

working_directory: ~/repo

Expand Down Expand Up @@ -46,3 +49,13 @@ jobs:
- store_test_results:
path: target/surefire-reports

workflows:
build-and-test:
jobs:
- build:
matrix:
parameters:
jdk-version:
- "17.0"
- "21.0"
- "25.0"
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## master

## 3.0.0 (2026-06-11)

- Add webhook signature verification (`Castle#verifyWebhookSignature`) and the `X-Castle-Signature` header constant
- Add the Events API: `eventsSchema`, `queryEvents` and `groupEvents`
- Add the privacy methods `requestUserData` and `deleteUserData`
- Remove the legacy `authenticate` and `track` endpoints
- Remove device management (`approve`, `report`, `userDevices`, `device`) and impersonation (`impersonateStart`, `impersonateEnd`)
- Remove the `recover` endpoint and the `removeUser` privacy method
- Remove the authenticate failover configuration (`AuthenticateFailoverStrategy`, `withAuthenticateFailoverStrategy`, the `failover_strategy` setting) and the `Verdict`/`CastleMessage` models
- Build and test against Java 17, 21 and 25 (CI matrix); bump JaCoCo to 0.8.15, Mockito to 5.23.0, System Stubs to 2.1.8 and AssertJ to 3.27.7
- Upgrade runtime dependencies: Gson 2.14.0, Guava 33.6.0-jre, SLF4J 2.0.18, jackson-databind-nullable 0.2.10, swagger-annotations 1.6.16, ThreeTen-Backport 1.7.3; declare an explicit `jsr305` dependency for the JSR-305 annotations
- Upgrade test dependencies: Logback 1.5.34, JSONassert 1.5.3, spring-test 6.1.21; remove the unused System Rules dependency
- Upgrade build plugins: maven-compiler-plugin 3.14.0, maven-surefire-plugin 3.5.6, maven-source-plugin 3.4.0, maven-javadoc-plugin 3.12.0, versions-maven-plugin 2.18.0, maven-gpg-plugin 3.2.8 and central-publishing-maven-plugin 0.10.0

## 2.6.1 (2025-07-28)

- [#153](https://github.com/castle/castle-java/pull/153) Remove string json serialization cap
Expand Down
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@

# Requirements

- **Java Version:** This SDK requires **Java 17** or higher.
- **Java Version:** This SDK requires **Java 17** or higher, and is built and tested against Java 17, 21 and 25.

# Supported APIs

The SDK exposes the modern Castle API surface:

| Group | Methods |
| --- | --- |
| Scoring | `risk`, `filter`, `log` |
| Lists | `createList`, `list`, `updateList`, `deleteList` |
| List items | `createListItem`, `createOrUpdateListItems`, `searchListItems`, `countListItems`, `getListItem`, `updateListItem`, `archiveListItem`, `unarchiveListItem` |
| Privacy | `requestUserData`, `deleteUserData` |
| Events | `eventsSchema`, `queryEvents`, `groupEvents` |
| Webhooks | `verifyWebhookSignature` |
| Secure mode | `secureUserID` |
| Generic | `get`, `post`, `put`, `delete` |

# Usage
See the [documentation](https://docs.castle.io) for how to use this SDK with the Castle APIs
Expand All @@ -16,7 +31,7 @@ When using Maven, add the following dependency to your `pom.xml` file:
<dependency>
<groupId>io.castle</groupId>
<artifactId>castle-java</artifactId>
<version>2.6.1</version>
<version>3.0.0</version>
</dependency>
```

Expand Down
54 changes: 27 additions & 27 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<groupId>io.castle</groupId>
<artifactId>castle-java</artifactId>
<packaging>jar</packaging>
<version>2.6.1</version>
<version>3.0.0</version>

<name>${project.groupId}:${project.artifactId}:${project.version}</name>
<description>Castle adds real-time monitoring of your authentication stack, instantly notifying you and your users
Expand Down Expand Up @@ -51,7 +51,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jdkVersion>17</jdkVersion>
<targetJdk>17</targetJdk>
<jacoco.version>0.8.10</jacoco.version>
<jacoco.version>0.8.15</jacoco.version>
</properties>

<profiles>
Expand All @@ -62,7 +62,7 @@
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.8.0</version>
<version>0.10.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>ossrh</publishingServerId>
Expand All @@ -71,7 +71,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<version>3.2.8</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand All @@ -91,22 +91,27 @@
<dependency>
<groupId>org.openapitools</groupId>
<artifactId>jackson-databind-nullable</artifactId>
<version>0.2.6</version>
<version>0.2.10</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.threeten</groupId>
<artifactId>threetenbp</artifactId>
<version>1.6.8</version>
<version>1.7.3</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.11</version>
<version>1.6.16</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
Expand All @@ -127,23 +132,23 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
<version>2.0.18</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.12</version>
<version>1.5.34</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
<version>33.6.0-jre</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
Expand All @@ -160,18 +165,18 @@
<dependency>
<groupId>uk.org.webcompere</groupId>
<artifactId>system-stubs-jupiter</artifactId>
<version>2.0.2</version>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.4.0</version>
<version>5.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<version>3.27.7</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -183,19 +188,13 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.11</version>
<version>6.1.21</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
<version>1.5.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<version>1.19.0</version>
<version>1.5.3</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -218,7 +217,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<version>3.14.0</version>
<configuration>
<source>17</source>
<target>17</target>
Expand All @@ -229,7 +228,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.0.1</version>
<version>3.12.0</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
Expand All @@ -246,7 +245,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<version>3.4.0</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -259,9 +258,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<version>3.5.6</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<argLine>@{argLine} --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED</argLine>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -297,7 +297,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.7</version>
<version>2.18.0</version>
</plugin>
</plugins>
</build>
Expand Down
44 changes: 38 additions & 6 deletions src/main/java/io/castle/client/Castle.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.castle.client.internal.config.CastleSdkInternalConfiguration;
import io.castle.client.internal.json.CastleGsonModel;
import io.castle.client.internal.utils.CastleContextBuilder;
import io.castle.client.internal.utils.Webhook;
import io.castle.client.model.CastleResponse;
import io.castle.client.model.CastleSdkConfigurationException;
import org.slf4j.Logger;
Expand All @@ -26,17 +27,16 @@
* Once set the {@code this#instance()} method will return that instance
*/
public class Castle {
public static final String URL_TRACK = "/v1/track";
public static final String URL_AUTHENTICATE = "/v1/authenticate";
public static final String URL_DEVICES = "/v1/devices/";
public static final String URL_USERS = "/v1/users/";
public static final String URL_IMPERSONATE = "/v1/impersonate";
public static final String URL_PRIVACY = "/v1/privacy/";
public static final String URL_RISK = "/v1/risk";
public static final String URL_FILTER = "/v1/filter";
public static final String URL_LOG = "/v1/log";
public static final String URL_EVENTS = "/v1/events";

public static final String URL_RECOVER = "/v1/users/%s/recover";
/**
* Header used by Castle to sign webhook payloads.
*/
public static final String WEBHOOK_SIGNATURE_HEADER = "X-Castle-Signature";

public static final String URL_LISTS = "/v1/lists";
public static final String URL_LISTS_ID = "/v1/lists/%s";
Expand Down Expand Up @@ -270,6 +270,38 @@ public String secureUserID(String userId) {
return hashFunction.hashString(userId,com.google.common.base.Charsets.UTF_8).toString();
}

/**
* Verifies a Castle webhook signature against the raw request body.
* <p>
* Castle signs every webhook with an HMAC-SHA256 of the raw request body using
* the account API secret, base64 encoded and sent in the
* {@code X-Castle-Signature} header.
*
* @param signature the value of the {@code X-Castle-Signature} header
* @param body the raw request body bytes
* @return {@code true} when the signature matches the computed signature
*/
public boolean verifyWebhookSignature(String signature, byte[] body) {
return Webhook.verifySignature(internalConfiguration.getConfiguration().getApiSecret(), body, signature);
}

/**
* Verifies a Castle webhook signature for the given servlet request.
* <p>
* The signature is read from the {@code X-Castle-Signature} header and verified
* against the supplied raw request body bytes.
*
* @param request the incoming webhook request
* @param body the raw request body bytes
* @return {@code true} when the signature matches the computed signature
*/
public boolean verifyWebhookSignature(HttpServletRequest request, byte[] body) {
if (request == null) {
return false;
}
return verifyWebhookSignature(request.getHeader(WEBHOOK_SIGNATURE_HEADER), body);
}

/**
* Make a GET request to a Castle API endpoint such as /v1/{userId}/devices
*
Expand Down
Loading