From fee71d824910489dbf42952ce01f535ee610c9f9 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 10 Sep 2025 15:28:53 +0200 Subject: [PATCH 01/12] add jwt validation and test endpoint --- pom.xml | 4 ++++ .../ase/userservice/controllers/DemoController.java | 13 +++++++++++++ src/main/resources/application.yaml | 7 +++++++ 3 files changed, 24 insertions(+) create mode 100644 src/main/java/com/ase/userservice/controllers/DemoController.java diff --git a/pom.xml b/pom.xml index e26b2f8a..e438d3b9 100644 --- a/pom.xml +++ b/pom.xml @@ -64,5 +64,9 @@ h2 runtime + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + diff --git a/src/main/java/com/ase/userservice/controllers/DemoController.java b/src/main/java/com/ase/userservice/controllers/DemoController.java new file mode 100644 index 00000000..94df8ae3 --- /dev/null +++ b/src/main/java/com/ase/userservice/controllers/DemoController.java @@ -0,0 +1,13 @@ +package com.ase.userservice.controllers; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoController { + + @GetMapping("/demo") + public String demo() { + return "Hello from DemoController!"; + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4b5711b9..b20393fd 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -14,6 +14,13 @@ spring: driverClassName: org.h2.Driver username: sa password: password + security: + oauth2: + resourceserver: + jwt: + issuer-uri: http://localhost:8080/realms/sau + jwk-set-uri: http://localhost:8080/realms/sau/protocol/openid-connect/certs server: + port: 8081 error: include-message: always From 005168b3e65a9e19b2ca89a2633d58fc0c416f7f Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Sep 2025 13:18:36 +0200 Subject: [PATCH 02/12] change issuer uri --- src/main/resources/application.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b20393fd..f7a5442c 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -18,9 +18,9 @@ spring: oauth2: resourceserver: jwt: - issuer-uri: http://localhost:8080/realms/sau - jwk-set-uri: http://localhost:8080/realms/sau/protocol/openid-connect/certs + issuer-uri: https://keycloak.sau-portal.de/realms/sau + jwk-set-uri: https://keycloak.sau-portal.de/realms/sau/protocol/openid-connect/certs server: - port: 8081 + port: 8080 error: include-message: always From 647cf16667f718e70db8af329f2c58d76d917d7e Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Sep 2025 13:46:41 +0200 Subject: [PATCH 03/12] add: provisory dockerfile --- Dockerfile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..27d6d76c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# Use an official OpenJDK runtime as a parent image +FROM debian:latest + +USER root + +# Set the working directory inside the container +WORKDIR /app + +COPY ./ ./ + +RUN apt-get update && apt-get install -y maven openjdk-21-jdk + +RUN mvn clean install +EXPOSE 8080 + +ENTRYPOINT ["mvn", "spring-boot:run"] \ No newline at end of file From 95a08475668a81e4db3dd233233bb8f8a7a05f99 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 11 Sep 2025 14:31:49 +0200 Subject: [PATCH 04/12] try to implement route protection we just copy-pasted the code from the tutorial and hope that it works --- pom.xml | 4 +++ .../controllers/DemoController.java | 1 + .../security/JwtAuthConverter.java | 25 ++++++++++++++ .../userservice/security/SecurityConfig.java | 34 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/main/java/com/ase/userservice/security/JwtAuthConverter.java create mode 100644 src/main/java/com/ase/userservice/security/SecurityConfig.java diff --git a/pom.xml b/pom.xml index e438d3b9..145a1755 100644 --- a/pom.xml +++ b/pom.xml @@ -68,5 +68,9 @@ org.springframework.boot spring-boot-starter-oauth2-resource-server + + org.springframework.boot + spring-boot-starter-security + diff --git a/src/main/java/com/ase/userservice/controllers/DemoController.java b/src/main/java/com/ase/userservice/controllers/DemoController.java index 94df8ae3..8736adb1 100644 --- a/src/main/java/com/ase/userservice/controllers/DemoController.java +++ b/src/main/java/com/ase/userservice/controllers/DemoController.java @@ -1,5 +1,6 @@ package com.ase.userservice.controllers; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java new file mode 100644 index 00000000..318bda1a --- /dev/null +++ b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java @@ -0,0 +1,25 @@ +// based on this tutorial xdd: https://www.javacodegeeks.com/2025/07/spring-boot-keycloak-role-based-authorization.html + +package com.ase.userservice.security; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class JwtAuthConverter implements Converter> { + @Override + public Collection convert(Jwt jwt) { + var realmAccess = jwt.getClaimAsMap("realm_access"); + if (realmAccess == null || realmAccess.isEmpty()) { + return List.of(); + } + var roles = (List) realmAccess.get("roles"); + return roles.stream() + .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/ase/userservice/security/SecurityConfig.java b/src/main/java/com/ase/userservice/security/SecurityConfig.java new file mode 100644 index 00000000..e31c1674 --- /dev/null +++ b/src/main/java/com/ase/userservice/security/SecurityConfig.java @@ -0,0 +1,34 @@ +// // based on this tutorial xdd: https://www.javacodegeeks.com/2025/07/spring-boot-keycloak-role-based-authorization.html + + +package com.ase.userservice.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableMethodSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); + jwtConverter.setJwtGrantedAuthoritiesConverter(new JwtAuthConverter()); + + + http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/demo/**").hasRole("defaultrole") + .requestMatchers("/admin/**").hasRole("admin") + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtConverter)) + ); + return http.build(); + } +} \ No newline at end of file From 8637687f160bbe69909d5bd1cee9f4db0da6952b Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Sep 2025 14:58:17 +0200 Subject: [PATCH 05/12] working mapper broken rbac --- .../com/ase/userservice/controllers/DemoController.java | 1 - .../com/ase/userservice/security/JwtAuthConverter.java | 7 +++---- .../java/com/ase/userservice/security/SecurityConfig.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ase/userservice/controllers/DemoController.java b/src/main/java/com/ase/userservice/controllers/DemoController.java index 8736adb1..94df8ae3 100644 --- a/src/main/java/com/ase/userservice/controllers/DemoController.java +++ b/src/main/java/com/ase/userservice/controllers/DemoController.java @@ -1,6 +1,5 @@ package com.ase.userservice.controllers; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java index 318bda1a..c24d214a 100644 --- a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java +++ b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java @@ -13,11 +13,10 @@ public class JwtAuthConverter implements Converter> { @Override public Collection convert(Jwt jwt) { - var realmAccess = jwt.getClaimAsMap("realm_access"); - if (realmAccess == null || realmAccess.isEmpty()) { - return List.of(); + var roles = jwt.getClaimAsStringList("groups"); + for (String role : roles) { + System.out.println("Role from JWT: " + role); } - var roles = (List) realmAccess.get("roles"); return roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())) .collect(Collectors.toList()); diff --git a/src/main/java/com/ase/userservice/security/SecurityConfig.java b/src/main/java/com/ase/userservice/security/SecurityConfig.java index e31c1674..602f31a9 100644 --- a/src/main/java/com/ase/userservice/security/SecurityConfig.java +++ b/src/main/java/com/ase/userservice/security/SecurityConfig.java @@ -22,7 +22,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/demo/**").hasRole("defaultrole") + .requestMatchers("/demo").hasRole("default-roles-sau") .requestMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated() ) From 00d6b2d2717b1bc492df3a0c658460cef66e5f6d Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Sep 2025 15:11:16 +0200 Subject: [PATCH 06/12] fix for role mapper? --- .../java/com/ase/userservice/security/JwtAuthConverter.java | 2 +- src/main/java/com/ase/userservice/security/SecurityConfig.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java index c24d214a..a406e9ea 100644 --- a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java +++ b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java @@ -18,7 +18,7 @@ public Collection convert(Jwt jwt) { System.out.println("Role from JWT: " + role); } return roles.stream() - .map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase())) + .map(role -> new SimpleGrantedAuthority(role)) .collect(Collectors.toList()); } } \ No newline at end of file diff --git a/src/main/java/com/ase/userservice/security/SecurityConfig.java b/src/main/java/com/ase/userservice/security/SecurityConfig.java index 602f31a9..0eae9a01 100644 --- a/src/main/java/com/ase/userservice/security/SecurityConfig.java +++ b/src/main/java/com/ase/userservice/security/SecurityConfig.java @@ -18,11 +18,10 @@ public class SecurityConfig { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { JwtAuthenticationConverter jwtConverter = new JwtAuthenticationConverter(); jwtConverter.setJwtGrantedAuthoritiesConverter(new JwtAuthConverter()); - http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/demo").hasRole("default-roles-sau") + .requestMatchers("/demo/**").hasRole("default-roles-sau") .requestMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated() ) From c67febd17f48c3f0063526e7e1cc1a3f9c1ee580 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Sep 2025 15:16:19 +0200 Subject: [PATCH 07/12] working role mapper and rbac --- src/main/java/com/ase/userservice/security/SecurityConfig.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ase/userservice/security/SecurityConfig.java b/src/main/java/com/ase/userservice/security/SecurityConfig.java index 602f31a9..f8580127 100644 --- a/src/main/java/com/ase/userservice/security/SecurityConfig.java +++ b/src/main/java/com/ase/userservice/security/SecurityConfig.java @@ -20,9 +20,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { jwtConverter.setJwtGrantedAuthoritiesConverter(new JwtAuthConverter()); + //the role always has to be capatalized http .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/demo").hasRole("default-roles-sau") + .requestMatchers("/demo").hasRole("DEFAULT-ROLES-SAU") .requestMatchers("/admin/**").hasRole("admin") .anyRequest().authenticated() ) From df2066b7844b94076355ea53aa51e7819dc5f0e2 Mon Sep 17 00:00:00 2001 From: erik Date: Thu, 11 Sep 2025 15:20:19 +0200 Subject: [PATCH 08/12] add note in endpoint code --- .../java/com/ase/userservice/controllers/DemoController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/ase/userservice/controllers/DemoController.java b/src/main/java/com/ase/userservice/controllers/DemoController.java index 94df8ae3..917a8097 100644 --- a/src/main/java/com/ase/userservice/controllers/DemoController.java +++ b/src/main/java/com/ase/userservice/controllers/DemoController.java @@ -6,6 +6,8 @@ @RestController public class DemoController { + + // to manage access, add route rules in security/SecurityConfig.java like in the examples @GetMapping("/demo") public String demo() { return "Hello from DemoController!"; From d31412284e528ea8abda4dfc187ceb6b10382929 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 11 Sep 2025 15:29:54 +0200 Subject: [PATCH 09/12] code cleanup --- .../com/ase/userservice/security/JwtAuthConverter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java index c24d214a..6d23c9d4 100644 --- a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java +++ b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java @@ -6,13 +6,16 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; + +import org.springframework.lang.NonNull; + import java.util.Collection; -import java.util.List; import java.util.stream.Collectors; public class JwtAuthConverter implements Converter> { + @Override - public Collection convert(Jwt jwt) { + public Collection convert(@NonNull Jwt jwt) { var roles = jwt.getClaimAsStringList("groups"); for (String role : roles) { System.out.println("Role from JWT: " + role); From d795b0b5388ad6db7ccddd89b6b55aa1a8c64fef Mon Sep 17 00:00:00 2001 From: David Clara Figueiredo Date: Fri, 12 Sep 2025 14:54:33 +0200 Subject: [PATCH 10/12] add user model to extract info from jwt, change claim from which to get roles, need to add jwt mapping to model --- .../com/ase/userservice/entities/User.java | 26 +++++++++++++++++++ .../security/JwtAuthConverter.java | 9 +++++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/ase/userservice/entities/User.java diff --git a/src/main/java/com/ase/userservice/entities/User.java b/src/main/java/com/ase/userservice/entities/User.java new file mode 100644 index 00000000..1e695ec5 --- /dev/null +++ b/src/main/java/com/ase/userservice/entities/User.java @@ -0,0 +1,26 @@ +package com.ase.userservice.entities; + +import java.util.ArrayList; + +public class User { + public int exp; + public int iat; + public int auth_time; + public String jti; + public String iss; + public String aud; + public String sub; + public String typ; + public String azp; + public String sid; + public String at_hash; + public String acr; + public String upn; + public boolean email_verified; + public String name; + public ArrayList groups; + public String preferred_username; + public String given_name; + public String family_name; + public String email; +} diff --git a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java index 6d23c9d4..a2f35720 100644 --- a/src/main/java/com/ase/userservice/security/JwtAuthConverter.java +++ b/src/main/java/com/ase/userservice/security/JwtAuthConverter.java @@ -10,13 +10,18 @@ import org.springframework.lang.NonNull; import java.util.Collection; +import java.util.List; import java.util.stream.Collectors; public class JwtAuthConverter implements Converter> { @Override - public Collection convert(@NonNull Jwt jwt) { - var roles = jwt.getClaimAsStringList("groups"); + public Collection convert(@NonNull Jwt jwt) {; + var claims = jwt.getClaimAsMap("realm_access"); + if (claims == null || claims.isEmpty()) { + return List.of(); + } + var roles = (List) claims.get("groups"); for (String role : roles) { System.out.println("Role from JWT: " + role); } From f98a148e20a017ca3a4e785a61907a7f9b196a51 Mon Sep 17 00:00:00 2001 From: David Clara Figueiredo Date: Wed, 24 Sep 2025 00:25:40 +0200 Subject: [PATCH 11/12] disable csrf && cleanup --- .../com/ase/userservice/entities/User.java | 26 ------------------- .../userservice/security/SecurityConfig.java | 3 ++- 2 files changed, 2 insertions(+), 27 deletions(-) delete mode 100644 src/main/java/com/ase/userservice/entities/User.java diff --git a/src/main/java/com/ase/userservice/entities/User.java b/src/main/java/com/ase/userservice/entities/User.java deleted file mode 100644 index 1e695ec5..00000000 --- a/src/main/java/com/ase/userservice/entities/User.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ase.userservice.entities; - -import java.util.ArrayList; - -public class User { - public int exp; - public int iat; - public int auth_time; - public String jti; - public String iss; - public String aud; - public String sub; - public String typ; - public String azp; - public String sid; - public String at_hash; - public String acr; - public String upn; - public boolean email_verified; - public String name; - public ArrayList groups; - public String preferred_username; - public String given_name; - public String family_name; - public String email; -} diff --git a/src/main/java/com/ase/userservice/security/SecurityConfig.java b/src/main/java/com/ase/userservice/security/SecurityConfig.java index f8580127..284c67de 100644 --- a/src/main/java/com/ase/userservice/security/SecurityConfig.java +++ b/src/main/java/com/ase/userservice/security/SecurityConfig.java @@ -20,8 +20,9 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { jwtConverter.setJwtGrantedAuthoritiesConverter(new JwtAuthConverter()); - //the role always has to be capatalized + //the role always has to be capatilized http + .csrf(csrf -> csrf.disable()) // Disable CSRF for API endpoints isnt needed for our purpose since we are not using cookies for auth .authorizeHttpRequests(authorize -> authorize .requestMatchers("/demo").hasRole("DEFAULT-ROLES-SAU") .requestMatchers("/admin/**").hasRole("admin") From 1dc2f83a545cacbdd928557b4ce66abca942d4db Mon Sep 17 00:00:00 2001 From: David Clara Figueiredo <151277143+davidclarafigueiredo@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:36:07 +0200 Subject: [PATCH 12/12] Update src/main/java/com/ase/userservice/security/SecurityConfig.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/main/java/com/ase/userservice/security/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ase/userservice/security/SecurityConfig.java b/src/main/java/com/ase/userservice/security/SecurityConfig.java index f0789184..33b68222 100644 --- a/src/main/java/com/ase/userservice/security/SecurityConfig.java +++ b/src/main/java/com/ase/userservice/security/SecurityConfig.java @@ -19,7 +19,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { jwtConverter.setJwtGrantedAuthoritiesConverter(new JwtAuthConverter()); - //the role always has to be capatilized + //the role always has to be capitalized http .csrf(csrf -> csrf.disable()) // Disable CSRF for API endpoints isnt needed for our purpose since we are not using cookies for auth .authorizeHttpRequests(authorize -> authorize