Skip to content

Commit 2da19cc

Browse files
authored
Add Spring example with authentication (#1255)
1 parent 27e9047 commit 2da19cc

File tree

17 files changed

+805
-1
lines changed

17 files changed

+805
-1
lines changed

examples/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<modules>
4444
<module>cli</module>
4545
<module>springboot</module>
46+
<module>spring-web</module>
4647
<module>webapp</module>
4748
</modules>
4849
</profile>

examples/spring-web/pom.xml

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
5+
<parent>
6+
<groupId>com.inrupt.client</groupId>
7+
<artifactId>inrupt-client-examples-parent</artifactId>
8+
<version>1.2.0-SNAPSHOT</version>
9+
</parent>
10+
11+
<artifactId>inrupt-client-examples-spring-web</artifactId>
12+
<version>1.2.0-SNAPSHOT</version>
13+
<name>Inrupt Java Client Libraries - SpringBoot WebApp Example</name>
14+
<description>
15+
Sample SpringBoot Web Application.
16+
</description>
17+
18+
<properties>
19+
<maven.compiler.target>17</maven.compiler.target>
20+
<maven.compiler.source>17</maven.compiler.source>
21+
<springboot.version>3.2.5</springboot.version>
22+
</properties>
23+
24+
<dependencyManagement>
25+
<dependencies>
26+
<dependency>
27+
<groupId>org.springframework.boot</groupId>
28+
<artifactId>spring-boot-dependencies</artifactId>
29+
<version>${springboot.version}</version>
30+
<type>pom</type>
31+
<scope>import</scope>
32+
</dependency>
33+
</dependencies>
34+
</dependencyManagement>
35+
36+
<dependencies>
37+
<dependency>
38+
<groupId>com.inrupt.client</groupId>
39+
<artifactId>inrupt-client-runtime</artifactId>
40+
<version>${project.version}</version>
41+
</dependency>
42+
<dependency>
43+
<groupId>com.inrupt.client</groupId>
44+
<artifactId>inrupt-client-spring</artifactId>
45+
<version>${project.version}</version>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.springframework.boot</groupId>
49+
<artifactId>spring-boot-starter-web</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.springframework.boot</groupId>
53+
<artifactId>spring-boot-starter-thymeleaf</artifactId>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.springframework.boot</groupId>
57+
<artifactId>spring-boot-starter-oauth2-client</artifactId>
58+
</dependency>
59+
60+
<!-- transitive dependencies -->
61+
<!-- addresses CVE-2023-52428 via Spring Boot 3.2.x -->
62+
<dependency>
63+
<groupId>com.nimbusds</groupId>
64+
<artifactId>nimbus-jose-jwt</artifactId>
65+
<version>9.37.3</version>
66+
</dependency>
67+
68+
<!-- testing -->
69+
<dependency>
70+
<groupId>org.springframework.boot</groupId>
71+
<artifactId>spring-boot-starter-test</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
<dependency>
75+
<groupId>org.springframework.security</groupId>
76+
<artifactId>spring-security-test</artifactId>
77+
<scope>test</scope>
78+
</dependency>
79+
<dependency>
80+
<groupId>io.smallrye</groupId>
81+
<artifactId>smallrye-jwt-build</artifactId>
82+
<version>${smallrye.jwt.version}</version>
83+
<scope>test</scope>
84+
</dependency>
85+
<dependency>
86+
<groupId>io.smallrye.config</groupId>
87+
<artifactId>smallrye-config</artifactId>
88+
<version>${smallrye.config.version}</version>
89+
<scope>test</scope>
90+
</dependency>
91+
<dependency>
92+
<groupId>org.wiremock</groupId>
93+
<artifactId>wiremock-standalone</artifactId>
94+
<version>${wiremock.version}</version>
95+
<scope>test</scope>
96+
</dependency>
97+
</dependencies>
98+
99+
<build>
100+
<plugins>
101+
<plugin>
102+
<!-- for building a jar -->
103+
<groupId>org.springframework.boot</groupId>
104+
<artifactId>spring-boot-maven-plugin</artifactId>
105+
<version>${springboot.version}</version>
106+
<executions>
107+
<execution>
108+
<goals>
109+
<goal>repackage</goal>
110+
</goals>
111+
</execution>
112+
</executions>
113+
</plugin>
114+
</plugins>
115+
</build>
116+
</project>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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.examples.spring.web;
22+
23+
import org.springframework.beans.factory.annotation.Autowired;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.security.config.Customizer;
27+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
28+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
29+
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
30+
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
31+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
32+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
33+
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
34+
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
35+
import org.springframework.security.web.SecurityFilterChain;
36+
37+
@Configuration
38+
@EnableWebSecurity
39+
public class SecurityConfiguration {
40+
41+
@Autowired
42+
ClientRegistrationRepository clientRegistrationRepository;
43+
44+
@Bean
45+
public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws Exception {
46+
http
47+
.logout(logout -> logout
48+
.logoutSuccessHandler(oidcLogoutSuccessHandler())
49+
)
50+
.oauth2Login(Customizer.withDefaults());
51+
return http.build();
52+
}
53+
54+
@Bean
55+
public JwtDecoderFactory<ClientRegistration> idTokenDecoderFactory() {
56+
final var decoder = new OidcIdTokenDecoderFactory();
57+
decoder.setJwsAlgorithmResolver(clientRegistration -> SignatureAlgorithm.ES256);
58+
return decoder;
59+
}
60+
61+
OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() {
62+
final var successHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
63+
// This URL should be one of the `post_logout_redirect_urls`
64+
successHandler.setPostLogoutRedirectUri("http://localhost:8080/");
65+
return successHandler;
66+
}
67+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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.examples.spring.web;
22+
23+
import org.springframework.boot.SpringApplication;
24+
import org.springframework.boot.autoconfigure.SpringBootApplication;
25+
26+
// Spring Boot's class requirements conflict with the checkstyle rules in this case
27+
@SuppressWarnings("checkstyle:HideUtilityClassConstructor")
28+
@SpringBootApplication
29+
public class WebApplication {
30+
public static void main(final String[] args) {
31+
SpringApplication.run(WebApplication.class, args);
32+
}
33+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.examples.spring.web.controller;
22+
23+
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
24+
import static org.springframework.security.oauth2.core.OAuth2ErrorCodes.INVALID_TOKEN;
25+
26+
import com.inrupt.client.auth.Session;
27+
import com.inrupt.client.examples.spring.web.model.*;
28+
import com.inrupt.client.openid.OpenIdException;
29+
import com.inrupt.client.solid.SolidClientException;
30+
import com.inrupt.client.solid.SolidRDFSource;
31+
import com.inrupt.client.solid.SolidSyncClient;
32+
import com.inrupt.client.spring.SessionUtils;
33+
import com.inrupt.client.webid.WebIdProfile;
34+
35+
import jakarta.servlet.http.HttpServletResponse;
36+
import jakarta.servlet.http.HttpSession;
37+
38+
import java.io.IOException;
39+
import java.net.URI;
40+
import java.util.*;
41+
42+
import org.springframework.beans.factory.annotation.Autowired;
43+
import org.springframework.boot.autoconfigure.SpringBootApplication;
44+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
45+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
46+
import org.springframework.security.oauth2.core.user.OAuth2User;
47+
import org.springframework.web.bind.annotation.*;
48+
49+
@SpringBootApplication
50+
@RestController
51+
@RequestMapping("/api")
52+
public class SolidController {
53+
54+
private static final SolidSyncClient client = SolidSyncClient.getClient();
55+
56+
@Autowired
57+
HttpSession httpSession;
58+
59+
@GetMapping(value = "/resource", produces = "text/turtle")
60+
public Resource getResource(final @AuthenticationPrincipal OAuth2User user, final @RequestParam URI uri) {
61+
final var session = SessionUtils.asSession(user)
62+
.orElseThrow(() -> new OAuth2AuthenticationException(INVALID_TOKEN));
63+
try (final var resource = client.session(session).read(uri, SolidRDFSource.class)) {
64+
return new Resource(resource);
65+
}
66+
}
67+
68+
@GetMapping(value = "/webid", produces = APPLICATION_JSON_VALUE)
69+
public WebId getUser(final @AuthenticationPrincipal OAuth2User user) {
70+
final var webid = SessionUtils.asSession(user).flatMap(Session::getPrincipal)
71+
.orElseThrow(() -> new OAuth2AuthenticationException(INVALID_TOKEN));
72+
return new WebId(webid);
73+
}
74+
75+
@GetMapping(value = "/profile", produces = APPLICATION_JSON_VALUE)
76+
public Profile getProfile(final @AuthenticationPrincipal OAuth2User user) {
77+
final var session = SessionUtils.asSession(user)
78+
.orElseThrow(() -> new OAuth2AuthenticationException(INVALID_TOKEN));
79+
final var webid = session.getPrincipal().orElseThrow(() -> new OAuth2AuthenticationException(INVALID_TOKEN));
80+
try (final var profile = client.session(session).read(webid, WebIdProfile.class)) {
81+
return Profile.of(profile);
82+
}
83+
}
84+
85+
@ExceptionHandler(SolidClientException.class)
86+
public void clientException(final HttpServletResponse response) throws IOException {
87+
httpSession.invalidate();
88+
response.sendError(400);
89+
}
90+
91+
@ExceptionHandler({OAuth2AuthenticationException.class, OpenIdException.class})
92+
public void sessionException(final HttpServletResponse response) throws IOException {
93+
httpSession.invalidate();
94+
response.sendError(401);
95+
}
96+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.examples.spring.web.model;
22+
23+
import com.inrupt.client.webid.WebIdProfile;
24+
25+
import java.net.URI;
26+
import java.util.Set;
27+
28+
public record Profile(URI id, Set<URI> storages, Set<URI> issuers) {
29+
public static Profile of(final WebIdProfile profile) {
30+
return new Profile(profile.getIdentifier(), profile.getStorages(), profile.getOidcIssuers());
31+
}
32+
}

0 commit comments

Comments
 (0)