Skip to content

Commit 290a73f

Browse files
authored
JCL-443: Add a spring integration module (#775)
1 parent 368633e commit 290a73f

File tree

9 files changed

+333
-5
lines changed

9 files changed

+333
-5
lines changed

.github/workflows/cd-config.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ jobs:
2929
steps:
3030
- uses: actions/checkout@v4
3131

32-
- name: Set up JDK 11
32+
- name: Set up JDK 17
3333
uses: actions/setup-java@v3
3434
with:
3535
distribution: 'temurin'
36-
java-version: 11
36+
java-version: 17
3737
cache: 'maven'
3838
server-id: 'ossrh'
3939
server-username: MAVEN_REPO_USERNAME
@@ -66,11 +66,11 @@ jobs:
6666
steps:
6767
- uses: actions/checkout@v4
6868

69-
- name: Set up JDK 11
69+
- name: Set up JDK 17
7070
uses: actions/setup-java@v3
7171
with:
7272
distribution: 'temurin'
73-
java-version: 11
73+
java-version: 17
7474
cache: 'maven'
7575

7676
- name: Build the code with Maven

.github/workflows/ci-config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ jobs:
7777
runs-on: ubuntu-latest
7878
strategy:
7979
matrix:
80-
java: [ 11 ]
80+
java: [ 17 ]
8181

8282
steps:
8383
- uses: actions/checkout@v4

bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@
9898
<artifactId>inrupt-client-solid</artifactId>
9999
<version>${project.version}</version>
100100
</dependency>
101+
<dependency>
102+
<groupId>com.inrupt.client</groupId>
103+
<artifactId>inrupt-client-spring</artifactId>
104+
<version>${project.version}</version>
105+
</dependency>
101106
<dependency>
102107
<groupId>com.inrupt.client</groupId>
103108
<artifactId>inrupt-client-uma</artifactId>

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<json.bind.version>3.0.0</json.bind.version>
3535
<okhttp.version>4.12.0</okhttp.version>
3636
<slf4j.version>2.0.9</slf4j.version>
37+
<spring.security.version>6.1.5</spring.security.version>
3738
<inrupt.commons.rdf4j.version>0.6.0</inrupt.commons.rdf4j.version>
3839
<inrupt.rdf.wrapping.version>1.0.0</inrupt.rdf.wrapping.version>
3940

@@ -73,6 +74,8 @@
7374
<equalsverifier.version>3.15.3</equalsverifier.version>
7475
<glassfish.json.version>2.0.1</glassfish.json.version>
7576
<junit.version>5.10.1</junit.version>
77+
<smallrye.jwt.version>4.3.1</smallrye.jwt.version>
78+
<smallrye.config.version>3.4.1</smallrye.config.version>
7679
<yasson.version>3.0.3</yasson.version>
7780
<wiremock.version>3.2.0</wiremock.version>
7881

@@ -110,6 +113,7 @@
110113
<module>webid</module>
111114
<module>vocabulary</module>
112115
<module>runtime</module>
116+
<module>spring</module>
113117
<module>integration</module>
114118
<module>performance</module>
115119
<module>reports</module>

reports/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@
9292
<artifactId>inrupt-client-solid</artifactId>
9393
<version>${project.version}</version>
9494
</dependency>
95+
<dependency>
96+
<groupId>com.inrupt.client</groupId>
97+
<artifactId>inrupt-client-spring</artifactId>
98+
<version>${project.version}</version>
99+
</dependency>
95100
<dependency>
96101
<groupId>com.inrupt.client</groupId>
97102
<artifactId>inrupt-client-uma</artifactId>

spring/pom.xml

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?xml version="1.0"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>com.inrupt.client</groupId>
6+
<artifactId>inrupt-client</artifactId>
7+
<version>1.1.0-SNAPSHOT</version>
8+
</parent>
9+
10+
<artifactId>inrupt-client-spring</artifactId>
11+
<name>Inrupt Java Client Libraries - Spring integration</name>
12+
<description>
13+
Integration utilities for Spring support.
14+
</description>
15+
16+
<properties>
17+
<maven.compiler.target>11</maven.compiler.target>
18+
<maven.compiler.source>11</maven.compiler.source>
19+
</properties>
20+
21+
<dependencies>
22+
<dependency>
23+
<groupId>com.inrupt.client</groupId>
24+
<artifactId>inrupt-client-api</artifactId>
25+
<version>${project.version}</version>
26+
</dependency>
27+
<dependency>
28+
<groupId>com.inrupt.client</groupId>
29+
<artifactId>inrupt-client-openid</artifactId>
30+
<version>${project.version}</version>
31+
</dependency>
32+
<dependency>
33+
<groupId>org.springframework.security</groupId>
34+
<artifactId>spring-security-oauth2-core</artifactId>
35+
<version>${spring.security.version}</version>
36+
<scope>provided</scope>
37+
</dependency>
38+
39+
<!-- test dependencies -->
40+
<dependency>
41+
<groupId>com.inrupt.client</groupId>
42+
<artifactId>inrupt-client-core</artifactId>
43+
<version>${project.version}</version>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.junit.jupiter</groupId>
48+
<artifactId>junit-jupiter-engine</artifactId>
49+
<version>${junit.version}</version>
50+
<scope>test</scope>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.junit.jupiter</groupId>
54+
<artifactId>junit-jupiter-api</artifactId>
55+
<version>${junit.version}</version>
56+
<scope>test</scope>
57+
</dependency>
58+
<dependency>
59+
<groupId>org.slf4j</groupId>
60+
<artifactId>slf4j-simple</artifactId>
61+
<version>${slf4j.version}</version>
62+
<scope>test</scope>
63+
</dependency>
64+
<dependency>
65+
<groupId>org.springframework.security</groupId>
66+
<artifactId>spring-security-test</artifactId>
67+
<version>${spring.security.version}</version>
68+
<scope>test</scope>
69+
</dependency>
70+
<dependency>
71+
<groupId>io.smallrye</groupId>
72+
<artifactId>smallrye-jwt-build</artifactId>
73+
<version>${smallrye.jwt.version}</version>
74+
<scope>test</scope>
75+
</dependency>
76+
<dependency>
77+
<groupId>io.smallrye.config</groupId>
78+
<artifactId>smallrye-config</artifactId>
79+
<version>${smallrye.config.version}</version>
80+
<scope>test</scope>
81+
</dependency>
82+
<dependency>
83+
<groupId>org.glassfish</groupId>
84+
<artifactId>jakarta.json</artifactId>
85+
<version>${glassfish.json.version}</version>
86+
<scope>test</scope>
87+
</dependency>
88+
</dependencies>
89+
90+
<profiles>
91+
<profile>
92+
<id>java-11</id>
93+
<activation>
94+
<jdk>[,17)</jdk>
95+
</activation>
96+
<properties>
97+
<spring.security.version>5.8.8</spring.security.version>
98+
</properties>
99+
</profile>
100+
</profiles>
101+
102+
<build>
103+
<plugins>
104+
<plugin>
105+
<groupId>org.apache.maven.plugins</groupId>
106+
<artifactId>maven-surefire-plugin</artifactId>
107+
<configuration>
108+
<systemPropertyVariables />
109+
</configuration>
110+
</plugin>
111+
<plugin>
112+
<groupId>org.apache.maven.plugins</groupId>
113+
<artifactId>maven-failsafe-plugin</artifactId>
114+
<configuration>
115+
<systemPropertyVariables />
116+
</configuration>
117+
</plugin>
118+
<plugin>
119+
<groupId>org.jacoco</groupId>
120+
<artifactId>jacoco-maven-plugin</artifactId>
121+
</plugin>
122+
</plugins>
123+
</build>
124+
</project>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Inrupt Inc.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to use,
7+
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8+
* Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
package com.inrupt.client.spring;
22+
23+
import com.inrupt.client.auth.Session;
24+
import com.inrupt.client.openid.OpenIdSession;
25+
26+
import java.time.Instant;
27+
import java.util.Optional;
28+
import java.util.function.Function;
29+
30+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
31+
import org.springframework.security.oauth2.core.user.OAuth2User;
32+
33+
/**
34+
* A utility class for converting Spring constructs into session objects for use with the Java Client libraries.
35+
*/
36+
public final class SessionUtils {
37+
38+
/**
39+
* Convert a Spring {@link OAuth2User} to a {@link Session} object.
40+
*
41+
* <p>This method uses the {@link OpenIdSession} library to create a Session
42+
*
43+
* @param user the Spring user object
44+
* @return the session, if present and unexpired
45+
*/
46+
public static Optional<Session> asSession(final OAuth2User user) {
47+
return asSession(user, OpenIdSession::ofIdToken);
48+
}
49+
50+
/**
51+
* Convert a Spring {@link OAuth2User} to a {@link Session} object.
52+
*
53+
* @param user the Spring user object
54+
* @param mapping a mapping function for creating a Session from an ID token
55+
* @return the session, if present and unexpired
56+
*/
57+
public static Optional<Session> asSession(final OAuth2User user, final Function<String, Session> mapping) {
58+
if (user instanceof OidcUser) {
59+
final var oidc = (OidcUser) user;
60+
final var token = oidc.getIdToken();
61+
if (Instant.now().isBefore(token.getExpiresAt())) {
62+
return Optional.ofNullable(mapping.apply(token.getTokenValue()));
63+
}
64+
}
65+
return Optional.empty();
66+
}
67+
68+
private SessionUtils() {
69+
// Prevent instantiation
70+
}
71+
}
72+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright Inrupt Inc.
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal in
6+
* the Software without restriction, including without limitation the rights to use,
7+
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8+
* Software, and to permit persons to whom the Software is furnished to do so,
9+
* subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16+
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17+
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18+
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19+
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20+
*/
21+
package com.inrupt.client.spring;
22+
23+
import static org.junit.jupiter.api.Assertions.*;
24+
25+
import com.inrupt.client.auth.Session;
26+
import com.inrupt.client.openid.OpenIdSession;
27+
28+
import io.smallrye.jwt.build.Jwt;
29+
import io.smallrye.jwt.util.ResourceUtils;
30+
31+
import java.io.IOException;
32+
import java.net.URI;
33+
import java.time.Instant;
34+
import java.util.Optional;
35+
36+
import org.jose4j.jwk.PublicJsonWebKey;
37+
import org.jose4j.lang.JoseException;
38+
import org.jose4j.lang.UncheckedJoseException;
39+
import org.junit.jupiter.api.Test;
40+
import org.springframework.security.core.authority.AuthorityUtils;
41+
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
42+
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
43+
44+
class SessionUtilsTest {
45+
46+
static final String WEBID = "https://example.com/user";
47+
static final String ISSUER = "https://issuer.example";
48+
49+
@Test
50+
void testSession() {
51+
final var token = generateIdToken("user", ISSUER, WEBID, Instant.now().plusSeconds(30));
52+
final var oauthUser = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, token);
53+
final var session = SessionUtils.asSession(oauthUser, OpenIdSession::ofIdToken);
54+
assertTrue(session.isPresent());
55+
session.ifPresent(s ->
56+
assertEquals(Optional.of(URI.create(WEBID)), s.getPrincipal()));
57+
}
58+
59+
@Test
60+
void testExpiredSession() {
61+
final var token = generateIdToken("user", ISSUER, WEBID, Instant.now().minusSeconds(30));
62+
final var oauthUser = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, token);
63+
final var session = SessionUtils.asSession(oauthUser, OpenIdSession::ofIdToken);
64+
assertFalse(session.isPresent());
65+
}
66+
67+
@Test
68+
void testAnonymousSession() {
69+
final var token = generateIdToken("user", ISSUER, WEBID, Instant.now().plusSeconds(30));
70+
final var oauthUser = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, token);
71+
final var session = SessionUtils.asSession(oauthUser, t -> Session.anonymous());
72+
assertTrue(session.isPresent());
73+
session.ifPresent(s ->
74+
assertEquals(Optional.empty(), s.getPrincipal()));
75+
}
76+
77+
@Test
78+
void testSessionNoMapper() {
79+
final var token = generateIdToken("user", ISSUER, WEBID, Instant.now().plusSeconds(30));
80+
final var oauthUser = new DefaultOidcUser(AuthorityUtils.NO_AUTHORITIES, token);
81+
final var session = SessionUtils.asSession(oauthUser);
82+
assertTrue(session.isPresent());
83+
session.ifPresent(s ->
84+
assertEquals(Optional.of(URI.create(WEBID)), s.getPrincipal()));
85+
}
86+
87+
static OidcIdToken generateIdToken(final String sub, final String issuer, final String webid, final Instant exp) {
88+
try {
89+
final var jwk = PublicJsonWebKey.Factory
90+
.newPublicJwk(ResourceUtils.readResource("testKey.json"));
91+
final var issued = Instant.now().isBefore(exp) ? Instant.now() : exp.minusSeconds(30);
92+
final var token = Jwt.claims()
93+
.subject(sub)
94+
.issuer(issuer)
95+
.issuedAt(issued)
96+
.expiresAt(exp)
97+
.claim("webid", webid)
98+
.audience("solid").jws()
99+
.keyId("E1amq-dXv6METCuD2iRwAQ").sign(jwk.getPrivateKey());
100+
return OidcIdToken.withTokenValue(token).expiresAt(Instant.now().plusSeconds(100)).subject(sub)
101+
.issuer(issuer).issuedAt(issued).expiresAt(exp).claim("webid", webid).build();
102+
} catch (final IOException | JoseException ex) {
103+
throw new UncheckedJoseException("Could not build token", ex);
104+
}
105+
}
106+
}
107+

0 commit comments

Comments
 (0)