+
history.push("/logout")}>
+
+
+
+
+
+
+ {props.menuItems.map((menuItem, index) => (
+
+ history.push(menuItem.link)}>
+
+
+
+ ))}
+
+
+
+
+
+ {props.children}
+
+
+ )
+}
+
+export default SidebarNavigation;
diff --git a/reacthookspring-frontend/src/app/features/home/Home.jsx b/reacthookspring-frontend/src/app/features/home/Home.jsx
new file mode 100644
index 0000000..de5c110
--- /dev/null
+++ b/reacthookspring-frontend/src/app/features/home/Home.jsx
@@ -0,0 +1,18 @@
+import {Container, Typography} from "@mui/material";
+import React from "react";
+import {useTranslation} from "react-i18next";
+
+function Home() {
+ const {t} = useTranslation();
+
+ return (
+
+
+ {t("home.title")}
+
+ {t("home.welcome")}
+
+ );
+}
+
+export default Home;
diff --git a/reacthookspring-frontend/src/app/modifiers/HomeModifier.js b/reacthookspring-frontend/src/app/modifiers/HomeModifier.js
new file mode 100644
index 0000000..0b587ed
--- /dev/null
+++ b/reacthookspring-frontend/src/app/modifiers/HomeModifier.js
@@ -0,0 +1,40 @@
+import {produce} from "immer";
+
+const RegexConfig = {
+ prop1: /^[A-Z][a-zA-Z0-9]{1,100}$/
+};
+
+const newHome =
+ {id: null, prop1: null};
+
+function toDatabase(home) {
+ const data = {};
+ data.id = home.id;
+ data.prop1 = home.prop1;
+ return data;
+}
+
+function updateHome(home, data) {
+ return produce(home, draft => {
+ draft.id = data.id;
+ draft.prop1 = data.prop1;
+ draft.isValid = isValid(draft);
+ });
+}
+
+function updateProp1(home, prop1) {
+ return produce(home, draft => {
+ draft.prop1 = prop1;
+ });
+}
+
+function isValid(data) {
+ return RegexConfig.prop1.test(data.prop1);
+}
+
+export {
+ newHome,
+ updateHome,
+ updateProp1,
+ isValid,
+ toDatabase};
diff --git a/reacthookspring-frontend/src/app/services/CrudRest.js b/reacthookspring-frontend/src/app/services/CrudRest.js
new file mode 100755
index 0000000..fa07f14
--- /dev/null
+++ b/reacthookspring-frontend/src/app/services/CrudRest.js
@@ -0,0 +1,29 @@
+import axios from "axios";
+
+class CrudRest {
+ constructor(baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ create = entity => {
+ return axios.post(this.baseUrl, entity);
+ };
+
+ update = entity => {
+ return axios.put(this.baseUrl, entity);
+ };
+
+ delete = entityId => {
+ return axios.delete(this.baseUrl + "/" + entityId);
+ };
+
+ findAll = () => {
+ return axios.get(this.baseUrl);
+ };
+
+ findById = entityId => {
+ return axios.get(this.baseUrl + "/" + entityId);
+ };
+}
+
+export default CrudRest;
diff --git a/reacthookspring-frontend/src/app/services/VersionRest.js b/reacthookspring-frontend/src/app/services/VersionRest.js
new file mode 100755
index 0000000..feeaff6
--- /dev/null
+++ b/reacthookspring-frontend/src/app/services/VersionRest.js
@@ -0,0 +1,13 @@
+import axios from "axios";
+
+class VersionRest {
+ constructor() {
+ this.baseUrl = window._env_.API_URL + + "/monitoring";
+ }
+
+ info = () => {
+ return axios.get(this.baseUrl + "/info");
+ };
+}
+
+export default VersionRest;
diff --git a/reacthookspring-frontend/src/index.css b/reacthookspring-frontend/src/index.css
new file mode 100755
index 0000000..bec5506
--- /dev/null
+++ b/reacthookspring-frontend/src/index.css
@@ -0,0 +1,18 @@
+body {
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body, html, #root {
+ height: 100%
+}
+
+code {
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
+}
diff --git a/reacthookspring-frontend/src/index.js b/reacthookspring-frontend/src/index.js
new file mode 100755
index 0000000..9730d79
--- /dev/null
+++ b/reacthookspring-frontend/src/index.js
@@ -0,0 +1,42 @@
+import React from "react";
+import ReactDOM from "react-dom";
+import "./index.css";
+import App from "./app/App";
+import * as serviceWorker from "./serviceWorker";
+import {HashRouter as Router} from "react-router-dom";
+import "./localization/i18n";
+import {SnackbarProvider} from "notistack";
+import MainTheme from "./app/assets/themes/MainTheme";
+import BearerProvider from "./commons/BearerProvider/BearerProvider";
+import {AuthProvider} from "oidc-react";
+
+ReactDOM.render((
+
+
+ {
+ console.log("on signin");
+ }}
+ >
+
+
+
+
+
+
+
+
+ ),
+ document.getElementById("root")
+);
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: http://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/reacthookspring-frontend/src/localization/i18n.js b/reacthookspring-frontend/src/localization/i18n.js
new file mode 100755
index 0000000..75b63e0
--- /dev/null
+++ b/reacthookspring-frontend/src/localization/i18n.js
@@ -0,0 +1,27 @@
+import i18n from "i18next";
+import {initReactI18next} from "react-i18next";
+import LanguageDetector from "i18next-browser-languagedetector";
+import translationEnEN from "./translation-en-EN";
+
+const resources = {
+ "en-US": {translation: translationEnEN}
+};
+
+const lngDetectinOptions = {
+ order: ["navigator", "cookie", "localStorage", "querystring", "htmlTag", "path", "subdomain"]
+};
+
+i18n
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ resources,
+ detection: lngDetectinOptions,
+ fallbackLng: ["en-US"],
+ keySeparator: false,
+ interpolation: {
+ escapeValue: false
+ }
+ }, null).then(null, null);
+
+export default i18n;
diff --git a/reacthookspring-frontend/src/localization/translation-en-EN.js b/reacthookspring-frontend/src/localization/translation-en-EN.js
new file mode 100644
index 0000000..08ace0d
--- /dev/null
+++ b/reacthookspring-frontend/src/localization/translation-en-EN.js
@@ -0,0 +1,6 @@
+const translationEnEN = {
+ "app.baseName": "ReactHook",
+ "home.title": "Welcome",
+ "home.welcome": "Welcome to ProjectBuilder"
+};
+export default translationEnEN;
diff --git a/reacthookspring-frontend/src/serviceWorker.js b/reacthookspring-frontend/src/serviceWorker.js
new file mode 100755
index 0000000..e05fdeb
--- /dev/null
+++ b/reacthookspring-frontend/src/serviceWorker.js
@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read http://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === "localhost" ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === "[::1]" ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(
+ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+ )
+);
+
+export function register(config) {
+ if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener("load", () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ "This web app is being served cache-first by a service " +
+ "worker. To learn more, visit http://bit.ly/CRA-PWA"
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW(swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === "installed") {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log(
+ "New content is available and will be used when all " +
+ "tabs for this page are closed. See http://bit.ly/CRA-PWA."
+ );
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log("Content is cached for offline use.");
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error("Error during service worker registration:", error);
+ });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get("content-type");
+ if (
+ response.status === 404 ||
+ (contentType != null && contentType.indexOf("javascript") === -1)
+ ) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log(
+ "No internet connection found. App is running in offline mode."
+ );
+ });
+}
+
+export function unregister() {
+ if ("serviceWorker" in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+}
diff --git a/reacthookspring/README.MD b/reacthookspring/README.MD
index ad11541..cdcabf7 100755
--- a/reacthookspring/README.MD
+++ b/reacthookspring/README.MD
@@ -1,6 +1,6 @@
### Prerequisites
-* Java JDK 14 or later
+* Java JDK 17 or later
* Maven 3
* NodeJs (16.9.1) and NPM (8.3.2) - [NodeJS Install](https://nodejs.org/en/download/package-manager/)
* mariaDB 10.6 (available for development via docker-compose scripts)
diff --git a/reacthookspring/application/pom.xml b/reacthookspring/application/pom.xml
index 40b1f7e..ff6f3df 100644
--- a/reacthookspring/application/pom.xml
+++ b/reacthookspring/application/pom.xml
@@ -19,10 +19,6 @@
de.starwit
persistence
-
- org.keycloak
- keycloak-spring-boot-starter
-
org.springframework.boot
spring-boot-starter-actuator
@@ -37,11 +33,15 @@
org.springdoc
- springdoc-openapi-ui
+ springdoc-openapi-starter-webmvc-ui
org.springdoc
- springdoc-openapi-security
+ springdoc-openapi-starter-common
+
+
+ org.projectlombok
+ lombok
diff --git a/reacthookspring/application/src/main/java/de/starwit/application/config/DevelopmentSecurityConfig.java b/reacthookspring/application/src/main/java/de/starwit/application/config/DevelopmentSecurityConfig.java
new file mode 100644
index 0000000..5ce22fb
--- /dev/null
+++ b/reacthookspring/application/src/main/java/de/starwit/application/config/DevelopmentSecurityConfig.java
@@ -0,0 +1,24 @@
+package de.starwit.application.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+
+@Profile("dev")
+@Configuration
+@EnableWebSecurity
+public class DevelopmentSecurityConfig {
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .httpBasic(AbstractHttpConfigurer::disable)
+ .csrf(AbstractHttpConfigurer::disable);
+
+ return http.build();
+ }
+}
\ No newline at end of file
diff --git a/reacthookspring/application/src/main/java/de/starwit/application/config/KeycloakConfig.java b/reacthookspring/application/src/main/java/de/starwit/application/config/KeycloakConfig.java
deleted file mode 100644
index 76350a7..0000000
--- a/reacthookspring/application/src/main/java/de/starwit/application/config/KeycloakConfig.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package de.starwit.application.config;
-
-import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
-import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Profile;
-import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
-import org.springframework.security.core.session.SessionRegistryImpl;
-import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
-import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
-import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
-
-@Profile("!dev")
-@EnableWebSecurity
-public class KeycloakConfig extends KeycloakWebSecurityConfigurerAdapter {
-
- private static final String[] AUTH_WHITELIST = {
- // -- Swagger UI v2
- "/v2/api-docs",
- "/swagger-resources",
- "/swagger-resources/**",
- "/configuration/ui",
- "/configuration/security",
- "/swagger-ui.html",
- "/webjars/**",
- // -- Swagger UI v3 (OpenAPI)
- "/v3/api-docs/**",
- "/swagger-ui/**"
- // other public endpoints of your API may be appended to this array
- };
-
- @Autowired
- public void configureGlobal(AuthenticationManagerBuilder auth) {
- SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
- grantedAuthorityMapper.setPrefix("ROLE_");
-
- KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
- keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);
- auth.authenticationProvider(keycloakAuthenticationProvider);
- }
-
- @Bean
- @Override
- protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
- return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
- }
-
- @Override
- public void configure(AuthenticationManagerBuilder auth) {
- KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
- auth.authenticationProvider(keycloakAuthenticationProvider);
- }
-
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- super.configure(http);
-
- http
- // .csrf().disable()
- .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
- .and()
- .authorizeRequests()
- .antMatchers(AUTH_WHITELIST).permitAll()
- .antMatchers("/logout.html").permitAll()
- .antMatchers("/api/user/logout").permitAll()
- .antMatchers("/**").hasAnyRole("admin", "user", "reader")
- .anyRequest().authenticated()
- .and()
- .logout().logoutSuccessUrl("/myLogout.html")
- .and().exceptionHandling().accessDeniedPage("/accessDenied.html");
- }
-}
diff --git a/reacthookspring/application/src/main/java/de/starwit/application/config/KeycloakResolverConfig.java b/reacthookspring/application/src/main/java/de/starwit/application/config/KeycloakResolverConfig.java
deleted file mode 100644
index 4b121a7..0000000
--- a/reacthookspring/application/src/main/java/de/starwit/application/config/KeycloakResolverConfig.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package de.starwit.application.config;
-
-import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-@Configuration
-public class KeycloakResolverConfig {
-
- @Bean
- public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
- return new KeycloakSpringBootConfigResolver();
- }
-}
diff --git a/reacthookspring/application/src/main/java/de/starwit/application/config/MethodSecurityConfig.java b/reacthookspring/application/src/main/java/de/starwit/application/config/MethodSecurityConfig.java
index da595d1..e69de29 100644
--- a/reacthookspring/application/src/main/java/de/starwit/application/config/MethodSecurityConfig.java
+++ b/reacthookspring/application/src/main/java/de/starwit/application/config/MethodSecurityConfig.java
@@ -1,16 +0,0 @@
-package de.starwit.application.config;
-
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
-import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
-
-@Profile("!dev")
-@Configuration
-@EnableGlobalMethodSecurity(
- prePostEnabled = true,
- securedEnabled = true,
- jsr250Enabled = true)
-public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
-
-}
diff --git a/reacthookspring/application/src/main/java/de/starwit/application/config/SecurityConfig.java b/reacthookspring/application/src/main/java/de/starwit/application/config/SecurityConfig.java
new file mode 100644
index 0000000..843ae73
--- /dev/null
+++ b/reacthookspring/application/src/main/java/de/starwit/application/config/SecurityConfig.java
@@ -0,0 +1,174 @@
+package de.starwit.application.config;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
+import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+import org.springframework.security.web.csrf.*;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.function.Supplier;
+
+
+@Profile("!dev")
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ static final Logger LOG = LoggerFactory.getLogger(SecurityConfig.class);
+
+ @Autowired
+ private ClientRegistrationRepository clientRegistrationRepository;
+
+ LogoutSuccessHandler oidcLogoutSuccessHandler() {
+ OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
+ new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
+
+ // Sets the location that the End-User's User Agent will be redirected to
+ // after the logout has been performed at the Provider
+ // oidcLogoutSuccessHandler.setPostLogoutRedirectUri(contextPath+"/");
+
+ return oidcLogoutSuccessHandler;
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .cors(cors -> cors.configurationSource(request -> {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(List.of("http://localhost:8081"));
+ configuration.setAllowedHeaders(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
+ configuration.setAllowedMethods(List.of("*"));
+ return configuration;
+ }))
+ .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer
+ .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
+ .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
+ )
+ .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
+ .authorizeHttpRequests(authorize -> authorize
+ .requestMatchers("/**").hasAnyRole("admin", "user", "reader")
+ .anyRequest().authenticated()
+ )
+ .logout((logout) -> logout
+ .logoutSuccessHandler(oidcLogoutSuccessHandler())
+ .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
+ )
+ // Maybe https://stackoverflow.com/questions/74939220/classnotfoundexception-org-springframework-security-oauth2-server-resource-web
+ .oauth2Login(Customizer.withDefaults());
+ return http.build();
+ }
+
+
+ // Taken from https://stackoverflow.com/questions/74939220/classnotfoundexception-org-springframework-security-oauth2-server-resource-web
+ @Component
+ @RequiredArgsConstructor
+ static class GrantedAuthoritiesMapperImpl implements GrantedAuthoritiesMapper {
+
+ @Override
+ public Collection extends GrantedAuthority> mapAuthorities(Collection extends GrantedAuthority> authorities) {
+ Set
mappedAuthorities = new HashSet<>();
+
+ authorities.forEach(authority -> {
+ if (authority instanceof SimpleGrantedAuthority) {
+ mappedAuthorities.add(authority);
+ }
+ if (authority instanceof OidcUserAuthority) {
+ try {
+ final var oidcUserAuthority = (OidcUserAuthority) authority;
+ final List roles = oidcUserAuthority.getUserInfo().getClaimAsStringList("roles");
+
+ mappedAuthorities.addAll(roles.stream().map(SimpleGrantedAuthority::new).toList());
+ } catch (NullPointerException e) {
+ LOG.error("Could not read the roles from claims.realm_access.roles -- Is the Mapper set up correctly?");
+ }
+ } else if (authority instanceof OAuth2UserAuthority oauth2UserAuthority) {
+ final var userAttributes = oauth2UserAuthority.getAttributes();
+ final Map> realmAccess = (Map>) userAttributes.get("realm_access");
+ final List roles = realmAccess.get("roles");
+
+ mappedAuthorities.addAll(roles.stream().map(SimpleGrantedAuthority::new).toList());
+ }
+ });
+
+ return mappedAuthorities;
+ }
+
+ }
+
+
+}
+
+// Taken from https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-integration-javascript-spa
+final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
+
+ private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
+
+ @Override
+ public void handle(HttpServletRequest request, HttpServletResponse response, Supplier csrfToken) {
+ /*
+ * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
+ * the CsrfToken when it is rendered in the response body.
+ */
+ this.delegate.handle(request, response, csrfToken);
+ }
+
+ @Override
+ public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
+ /*
+ * If the request contains a request header, use CsrfTokenRequestAttributeHandler
+ * to resolve the CsrfToken. This applies when a single-page application includes
+ * the header value automatically, which was obtained via a cookie containing the
+ * raw CsrfToken.
+ */
+ if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
+ return super.resolveCsrfTokenValue(request, csrfToken);
+ }
+ /*
+ * In all other cases (e.g. if the request contains a request parameter), use
+ * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
+ * when a server-side rendered form includes the _csrf request parameter as a
+ * hidden input.
+ */
+ return this.delegate.resolveCsrfTokenValue(request, csrfToken);
+ }
+}
+
+final class CsrfCookieFilter extends OncePerRequestFilter {
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+ throws ServletException, IOException {
+ CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
+ // Render the token value to a cookie by causing the deferred token to be loaded
+ csrfToken.getToken();
+
+ filterChain.doFilter(request, response);
+ }
+}
diff --git a/reacthookspring/application/src/main/resources/application.properties b/reacthookspring/application/src/main/resources/application.properties
index 56a1535..0ee17c0 100644
--- a/reacthookspring/application/src/main/resources/application.properties
+++ b/reacthookspring/application/src/main/resources/application.properties
@@ -19,6 +19,7 @@ spring.datasource.url=jdbc:mariadb://localhost:3306/reacthookspring?useLegacyDat
spring.jpa.hibernate.naming.physical-strategy=de.starwit.persistence.config.DatabasePhysicalNamingStrategy
spring.datasource.username=reacthookspring
spring.datasource.password=reacthookspring
+spring.data.rest.detection-strategy=annotated
# Flyway
spring.flyway.user=${spring.datasource.username}
@@ -29,29 +30,14 @@ spring.flyway.locations=classpath:db/migration
spring.flyway.encoding=UTF-8
spring.flyway.placeholder-replacement=false
-springdoc.swagger-ui.disable-swagger-default-url=true
-#spring.profiles.active=dev
-springdoc.api-docs.path=/api-docs
-springdoc.swagger-ui.path=/swagger-ui.html
-springdoc.swagger-ui.csrf.enabled=true
-#logging.level.org.springframework=DEBUG
-#spring.jpa.generate-ddl=true
-#spring.jpa.show-sql=true
-#spring.jpa.hibernate.ddl-auto=create-drop
-#spring.jpa.properties.javax.persistence.schema-generation.create-source=metadata
-#spring.jpa.properties.javax.persistence.schema-generation.scripts.action=create
-#spring.jpa.properties.javax.persistence.schema-generation.scripts.create-target=/home/anett/create.sql
-
-#logging.level.org.springframework.security=DEBUG
-
-keycloak.auth-server-url=http://localhost:8080/auth
-keycloak.realm=reacthookspring
-keycloak.resource=reacthookspring
-keycloak.principal-attribute=preferred_username
-keycloak.public-client=true
-keycloak.enabled=true
-
-
-
+# Authentication
+spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/auth/realms/reacthookspring
+spring.security.oauth2.client.registration.keycloak.client-id=reacthookspring
+spring.security.oauth2.client.registration.keycloak.client-secret=reacthookspring
+spring.security.oauth2.client.registration.keycloak.scope=openid
+# OpenApi
+springdoc.swagger-ui.csrf.enabled=true
+# logging.level.org.springframework.security=DEBUG
+# logging.level.org.springframework.web=DEBUG
diff --git a/reacthookspring/deployment/keycloak/imports/realm.json b/reacthookspring/deployment/keycloak/imports/realm.json
index b63daf2..65e579a 100644
--- a/reacthookspring/deployment/keycloak/imports/realm.json
+++ b/reacthookspring/deployment/keycloak/imports/realm.json
@@ -1,123 +1,716 @@
{
- "id": "reacthookspring",
"realm": "reacthookspring",
+ "notBefore": 0,
+ "defaultSignatureAlgorithm": "RS256",
+ "revokeRefreshToken": false,
+ "refreshTokenMaxReuse": 0,
+ "accessTokenLifespan": 300,
+ "accessTokenLifespanForImplicitFlow": 900,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 0,
+ "ssoSessionMaxLifespanRememberMe": 0,
+ "offlineSessionIdleTimeout": 2592000,
+ "offlineSessionMaxLifespanEnabled": false,
+ "offlineSessionMaxLifespan": 5184000,
+ "clientSessionIdleTimeout": 0,
+ "clientSessionMaxLifespan": 0,
+ "clientOfflineSessionIdleTimeout": 0,
+ "clientOfflineSessionMaxLifespan": 0,
+ "accessCodeLifespan": 60,
+ "accessCodeLifespanUserAction": 300,
+ "accessCodeLifespanLogin": 1800,
+ "actionTokenGeneratedByAdminLifespan": 43200,
+ "actionTokenGeneratedByUserLifespan": 300,
+ "oauth2DeviceCodeLifespan": 600,
+ "oauth2DevicePollingInterval": 5,
"enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
"roles": {
"realm": [
{
- "name": "ROLE_user",
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "attributes": {}
+ },
+ {
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
"composite": false,
"clientRole": false,
- "containerId": "reacthookspring",
+ "attributes": {}
+ },
+ {
+ "name": "default-roles-reacthookspring",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "composites": {
+ "realm": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "client": {
+ "account": [
+ "manage-account",
+ "view-profile"
+ ]
+ }
+ },
+ "clientRole": false,
"attributes": {}
},
{
"name": "ROLE_admin",
"composite": false,
"clientRole": false,
- "containerId": "reacthookspring",
"attributes": {}
},
{
"name": "ROLE_reader",
"composite": false,
"clientRole": false,
- "containerId": "reacthookspring",
+ "attributes": {}
+ },
+ {
+ "name": "ROLE_user",
+ "composite": false,
+ "clientRole": false,
"attributes": {}
}
],
"client": {
+ "realm-management": [
+ {
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-realms",
+ "view-clients",
+ "view-realm",
+ "manage-clients",
+ "impersonation",
+ "view-users",
+ "manage-users",
+ "query-clients",
+ "query-groups",
+ "query-users",
+ "view-events",
+ "manage-identity-providers",
+ "manage-authorization",
+ "manage-events",
+ "view-authorization",
+ "create-client",
+ "view-identity-providers",
+ "manage-realm"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-groups",
+ "query-users"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ }
+ ],
+ "security-admin-console": [],
+ "admin-cli": [],
+ "account-console": [],
+ "broker": [
+ {
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ }
+ ],
+ "account": [
+ {
+ "name": "delete-account",
+ "description": "${role_delete-account}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-applications",
+ "description": "${role_view-applications}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-consent",
+ "description": "${role_view-consent}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-account",
+ "description": "${role_manage-account}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "manage-account-links"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-profile",
+ "description": "${role_view-profile}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-account-links",
+ "description": "${role_manage-account-links}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "view-groups",
+ "description": "${role_view-groups}",
+ "composite": false,
+ "clientRole": true,
+ "attributes": {}
+ },
+ {
+ "name": "manage-consent",
+ "description": "${role_manage-consent}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "view-consent"
+ ]
+ }
+ },
+ "clientRole": true,
+ "attributes": {}
+ }
+ ],
"reacthookspring": []
}
},
- "groups": [
+ "groups": [],
+ "defaultRole": {
+ "name": "default-roles-reacthookspring",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false
+ },
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpPolicyCodeReusable": false,
+ "otpSupportedApplications": [
+ "totpAppFreeOTPName",
+ "totpAppGoogleName"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "scopeMappings": [
{
- "name": "public",
- "path": "/public",
- "attributes": {},
- "realmRoles": [
- "ROLE_reader"
- ],
- "clientRoles": {},
- "subGroups": []
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
}
],
- "defaultGroups": [
- "/public"
- ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account",
+ "view-groups"
+ ]
+ }
+ ]
+ },
"clients": [
+ {
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/reacthookspring/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/reacthookspring/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ],
+ "secret": null
+ },
+ {
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/reacthookspring/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/realms/reacthookspring/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+",
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ],
+ "secret": null
+ },
+ {
+ "clientId": "admin-cli",
+ "name": "${client_admin-cli}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": false,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ],
+ "secret": null,
+ "rootUrl": null
+ },
+ {
+ "clientId": "broker",
+ "name": "${client_broker}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ],
+ "secret": null,
+ "rootUrl": null
+ },
{
"clientId": "reacthookspring",
"name": "reacthookspring",
- "rootUrl": "",
+ "description": "",
+ "adminUrl": "",
"baseUrl": "",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
+ "secret": "reacthookspring",
"redirectUris": [
"*"
],
"webOrigins": [
- "+"
+ "*"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
- "directAccessGrantsEnabled": false,
+ "directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
- "publicClient": true,
- "frontchannelLogout": false,
+ "publicClient": false,
+ "frontchannelLogout": true,
"protocol": "openid-connect",
"attributes": {
- "id.token.as.detached.signature": "false",
- "saml.assertion.signature": "false",
- "saml.force.post.binding": "false",
- "saml.multivalued.roles": "false",
- "saml.encrypt": "false",
- "login_theme": "keycloak",
- "oauth2.device.authorization.grant.enabled": "false",
- "backchannel.logout.revoke.offline.tokens": "false",
- "saml.server.signature": "false",
- "saml.server.signature.keyinfo.ext": "false",
- "use.refresh.tokens": "true",
- "exclude.session.state.from.auth.response": "false",
"oidc.ciba.grant.enabled": "false",
- "saml.artifact.binding": "false",
+ "client.secret.creation.time": "1689243300",
"backchannel.logout.session.required": "true",
- "client_credentials.use_refresh_token": "false",
- "saml_force_name_id_format": "false",
- "require.pushed.authorization.requests": "false",
- "saml.client.signature": "false",
- "tls.client.certificate.bound.access.tokens": "false",
- "saml.authnstatement": "false",
"display.on.consent.screen": "false",
- "saml.onetimeuse.condition": "false"
+ "oauth2.device.authorization.grant.enabled": "false",
+ "backchannel.logout.revoke.offline.tokens": "false"
},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ],
"protocolMappers": [
{
+ "id": "8741eb9f-9d31-4f0d-86c9-a5b9c66b4b5f",
"name": "groups",
"protocol": "openid-connect",
- "protocolMapper": "oidc-group-membership-mapper",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
- "full.path": "false",
- "id.token.claim": "false",
+ "multivalued": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "groups",
- "userinfo.token.claim": "true"
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "6db650d3-b452-4d98-b3eb-11288006100a",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "roles",
+ "jsonType.label": "String"
}
}
- ],
+ ]
+ },
+ {
+ "clientId": "realm-management",
+ "name": "${client_realm-management}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {},
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
"defaultClientScopes": [
"web-origins",
- "roles",
+ "acr",
"profile",
+ "roles",
"email"
],
"optionalClientScopes": [
@@ -125,7 +718,1453 @@
"phone",
"offline_access",
"microprofile-jwt"
- ]
+ ],
+ "secret": null,
+ "rootUrl": null
+ },
+ {
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/reacthookspring/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "redirectUris": [
+ "/admin/reacthookspring/console/*"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+",
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "acr",
+ "profile",
+ "roles",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ],
+ "secret": null
+ }
+ ],
+ "clientScopes": [
+ {
+ "name": "profile",
+ "description": "OpenID Connect built-in scope: profile",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${profileScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "long"
+ }
+ },
+ {
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "name": "web-origins",
+ "description": "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false",
+ "consent.screen.text": ""
+ },
+ "protocolMappers": [
+ {
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ },
+ {
+ "name": "email",
+ "description": "OpenID Connect built-in scope: email",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${emailScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ },
+ {
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "name": "microprofile-jwt",
+ "description": "Microprofile - JWT built-in scope",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "name": "phone",
+ "description": "OpenID Connect built-in scope: phone",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${phoneScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ },
+ {
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "name": "offline_access",
+ "description": "OpenID Connect built-in scope: offline_access",
+ "protocol": "openid-connect",
+ "attributes": {
+ "consent.screen.text": "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen": "true"
+ }
+ },
+ {
+ "name": "address",
+ "description": "OpenID Connect built-in scope: address",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${addressScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "user.attribute.postal_code": "postal_code",
+ "userinfo.token.claim": "true",
+ "user.attribute.street": "street",
+ "id.token.claim": "true",
+ "user.attribute.region": "region",
+ "access.token.claim": "true",
+ "user.attribute.locality": "locality"
+ }
+ }
+ ]
+ },
+ {
+ "name": "roles",
+ "description": "OpenID Connect scope for add user roles to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${rolesScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "name": "acr",
+ "description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "name": "acr loa level",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-acr-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true"
+ }
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "role_list",
+ "profile",
+ "email",
+ "roles",
+ "web-origins",
+ "acr"
+ ],
+ "defaultOptionalClientScopes": [
+ "offline_access",
+ "address",
+ "phone",
+ "microprofile-jwt"
+ ],
+ "browserSecurityHeaders": {
+ "contentSecurityPolicyReportOnly": "",
+ "xContentTypeOptions": "nosniff",
+ "xRobotsTag": "none",
+ "xFrameOptions": "SAMEORIGIN",
+ "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection": "1; mode=block",
+ "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer": {
+ "password": "",
+ "port": "",
+ "host": "",
+ "from": "",
+ "fromDisplayName": "",
+ "user": "",
+ "ssl": "",
+ "replyToDisplayName": "",
+ "starttls": ""
+ },
+ "eventsEnabled": false,
+ "eventsListeners": [
+ "jboss-logging"
+ ],
+ "enabledEventTypes": [],
+ "adminEventsEnabled": false,
+ "adminEventsDetailsEnabled": false,
+ "identityProviders": [],
+ "identityProviderMappers": [],
+ "components": {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+ {
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-address-mapper",
+ "oidc-full-name-mapper",
+ "oidc-usermodel-property-mapper",
+ "saml-user-attribute-mapper",
+ "saml-role-list-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "saml-user-property-mapper"
+ ]
+ }
+ },
+ {
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-usermodel-property-mapper",
+ "saml-user-attribute-mapper",
+ "saml-user-property-mapper",
+ "oidc-full-name-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "oidc-address-mapper",
+ "saml-role-list-mapper"
+ ]
+ }
+ }
+ ],
+ "org.keycloak.userprofile.UserProfileProvider": [
+ {
+ "providerId": "declarative-user-profile",
+ "subComponents": {},
+ "config": {}
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "alias": "Account verification options",
+ "description": "Method with which to verity the existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-email-verification",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "Verify Existing Account by Re-authentication",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "Authentication Options",
+ "description": "Authentication options.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "basic-auth",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "basic-auth-otp",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 30,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "Browser - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "Direct Grant - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "direct-grant-validate-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "First broker login - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "Handle Existing Account",
+ "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-confirm-link",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "Account verification options",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "Reset - Conditional OTP",
+ "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "reset-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "User creation or linking",
+ "description": "Flow for the existing/non-existing user alternatives",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "create unique user config",
+ "authenticator": "idp-create-user-if-unique",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "Handle Existing Account",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "Verify Existing Account by Re-authentication",
+ "description": "Reauthentication of existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "First broker login - Conditional OTP",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "browser",
+ "description": "browser based authentication",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-cookie",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "identity-provider-redirector",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 25,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "autheticatorFlow": true,
+ "flowAlias": "forms",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "clients",
+ "description": "Base authentication for clients",
+ "providerId": "client-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "client-secret",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "client-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "client-secret-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "client-x509",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 40,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "direct grant",
+ "description": "OpenID Connect Resource Owner Grant",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "direct-grant-validate-username",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "direct-grant-validate-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 30,
+ "autheticatorFlow": true,
+ "flowAlias": "Direct Grant - Conditional OTP",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "docker auth",
+ "description": "Used by Docker clients to authenticate against the IDP",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "docker-http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "first broker login",
+ "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "review profile config",
+ "authenticator": "idp-review-profile",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "User creation or linking",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "forms",
+ "description": "Username, password, otp and other auth forms.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "Browser - Conditional OTP",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "http challenge",
+ "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "no-cookie-redirect",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": true,
+ "flowAlias": "Authentication Options",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "registration",
+ "description": "registration flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-page-form",
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": true,
+ "flowAlias": "registration form",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "registration form",
+ "description": "registration form",
+ "providerId": "form-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-user-creation",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "registration-profile-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "registration-password-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 50,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "registration-recaptcha-action",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 60,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "reset credentials",
+ "description": "Reset credentials for a user if they forgot their password or something",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "reset-credentials-choose-user",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "reset-credential-email",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticator": "reset-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 30,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 40,
+ "autheticatorFlow": true,
+ "flowAlias": "Reset - Conditional OTP",
+ "userSetupAllowed": false
+ }
+ ]
+ },
+ {
+ "alias": "saml ecp",
+ "description": "SAML ECP Profile Authentication Flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "autheticatorFlow": false,
+ "userSetupAllowed": false
+ }
+ ]
+ }
+ ],
+ "authenticatorConfig": [
+ {
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "alias": "review profile config",
+ "config": {
+ "update.profile.on.first.login": "missing"
+ }
+ }
+ ],
+ "requiredActions": [
+ {
+ "alias": "CONFIGURE_TOTP",
+ "name": "Configure OTP",
+ "providerId": "CONFIGURE_TOTP",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 10,
+ "config": {}
+ },
+ {
+ "alias": "terms_and_conditions",
+ "name": "Terms and Conditions",
+ "providerId": "terms_and_conditions",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 20,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PASSWORD",
+ "name": "Update Password",
+ "providerId": "UPDATE_PASSWORD",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 30,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PROFILE",
+ "name": "Update Profile",
+ "providerId": "UPDATE_PROFILE",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 40,
+ "config": {}
+ },
+ {
+ "alias": "VERIFY_EMAIL",
+ "name": "Verify Email",
+ "providerId": "VERIFY_EMAIL",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 50,
+ "config": {}
+ },
+ {
+ "alias": "delete_account",
+ "name": "Delete Account",
+ "providerId": "delete_account",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 60,
+ "config": {}
+ },
+ {
+ "alias": "webauthn-register",
+ "name": "Webauthn Register",
+ "providerId": "webauthn-register",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 70,
+ "config": {}
+ },
+ {
+ "alias": "webauthn-register-passwordless",
+ "name": "Webauthn Register Passwordless",
+ "providerId": "webauthn-register-passwordless",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 80,
+ "config": {}
+ },
+ {
+ "alias": "update_user_locale",
+ "name": "Update User Locale",
+ "providerId": "update_user_locale",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 1000,
+ "config": {}
+ }
+ ],
+ "browserFlow": "browser",
+ "registrationFlow": "registration",
+ "directGrantFlow": "direct grant",
+ "resetCredentialsFlow": "reset credentials",
+ "clientAuthenticationFlow": "clients",
+ "dockerAuthenticationFlow": "docker auth",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "oauth2DevicePollingInterval": "5",
+ "clientOfflineSessionMaxLifespan": "0",
+ "clientSessionIdleTimeout": "0",
+ "parRequestUriLifespan": "60",
+ "clientSessionMaxLifespan": "0",
+ "clientOfflineSessionIdleTimeout": "0",
+ "cibaInterval": "5",
+ "realmReusableOtpCode": "false"
+ },
+ "keycloakVersion": "20.0.2",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ },
+ "users": [
+ {
+ "createdTimestamp": 1689189284738,
+ "username": "admin",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": false,
+ "credentials": [
+ {
+ "id": "1ec1abd6-bb09-4a90-b874-bbb8e1e64290",
+ "type": "password",
+ "createdDate": 1689189284762,
+ "secretData": "{\"value\":\"bdTG67Kc8WkmG5O9FidYPVms6la4EVyA7eRGm4aKmbpuwXIO4PGHZxYzLWeLAaRstqz8xatqtostvs4LKWd/vw==\",\"salt\":\"y99TTu8C8VZFt8j64Lf2hw==\",\"additionalParameters\":{}}",
+ "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "default-roles-reacthookspring",
+ "ROLE_admin"
+ ],
+ "notBefore": 0
+ },
+ {
+ "createdTimestamp": 1689189284782,
+ "username": "reader",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": false,
+ "credentials": [
+ {
+ "id": "f7724b15-c2f0-4625-8528-cebbb89268f1",
+ "type": "password",
+ "createdDate": 1689189284783,
+ "secretData": "{\"value\":\"o2sxyw56EK8jKnh0CQSV2FiN9plXTSzwOWNlt7SiIUd/Ox+huiDWYGlCL4gqSM0R/3Q+PONjG0Dl7dQrvtfU2A==\",\"salt\":\"m63ldw2Y1VagMqRIEhQZOg==\",\"additionalParameters\":{}}",
+ "credentialData": "{\"hashIterations\":100000,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "default-roles-reacthookspring",
+ "ROLE_reader"
+ ],
+ "notBefore": 0
+ },
+ {
+ "createdTimestamp": 1689189284771,
+ "username": "user",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": false,
+ "credentials": [
+ {
+ "id": "6de67af4-01e6-4092-b184-45e64ba67ad4",
+ "type": "password",
+ "createdDate": 1689189284774,
+ "secretData": "{\"value\":\"o2sxyw56EK8jKnh0CQSV2FiN9plXTSzwOWNlt7SiIUd/Ox+huiDWYGlCL4gqSM0R/3Q+PONjG0Dl7dQrvtfU2A==\",\"salt\":\"m63ldw2Y1VagMqRIEhQZOg==\",\"additionalParameters\":{}}",
+ "credentialData": "{\"hashIterations\":100000,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "default-roles-reacthookspring",
+ "ROLE_user"
+ ],
+ "notBefore": 0
}
]
}
\ No newline at end of file
diff --git a/reacthookspring/deployment/localenv-docker-compose.yml b/reacthookspring/deployment/localenv-docker-compose.yml
index 44777b2..4a94cb1 100644
--- a/reacthookspring/deployment/localenv-docker-compose.yml
+++ b/reacthookspring/deployment/localenv-docker-compose.yml
@@ -44,23 +44,22 @@ services:
- backend
reacthookspring-keycloak:
- image: jboss/keycloak
+ image: quay.io/keycloak/keycloak
volumes:
- - ./keycloak/imports:/opt/jboss/keycloak/imports
- - ./keycloak/local-test-users.json:/opt/jboss/keycloak/standalone/configuration/keycloak-add-user.json
+ - ./keycloak/imports:/opt/keycloak/data/import
depends_on:
reacthookspring-db-keycloak:
condition: service_healthy
restart: on-failure
environment:
- KEYCLOAK_IMPORT: /opt/jboss/keycloak/imports/realm.json
- DB_VENDOR: mariadb
- DB_ADDR: reacthookspring-db-keycloak
- DB_PORT: 3307
- DB_USER: 'keycloak'
- DB_PASSWORD: 'keycloak'
- PROXY_ADDRESS_FORWARDING: 'true'
- KEYCLOAK_FRONTEND_URL: 'http://localhost:8080/auth'
+ KC_DB_URL: jdbc:mariadb://reacthookspring-db-keycloak:3307/keycloak
+ KC_DB: mariadb
+ KC_DB_USERNAME: keycloak
+ KC_DB_PASSWORD: keycloak
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: admin
+ KC_HTTP_RELATIVE_PATH: /auth/
+ command: start-dev --import-realm
ports:
# : < MySQL Port running inside container>
- '8080:8080'
diff --git a/reacthookspring/persistence/pom.xml b/reacthookspring/persistence/pom.xml
index c7061bb..dd3ff78 100644
--- a/reacthookspring/persistence/pom.xml
+++ b/reacthookspring/persistence/pom.xml
@@ -53,8 +53,8 @@
jackson-annotations
- org.springdoc
- springdoc-openapi-security
+ com.fasterxml.jackson.core
+ jackson-databind
diff --git a/reacthookspring/persistence/src/main/java/de/starwit/persistence/converter/ListToStringConverter.java b/reacthookspring/persistence/src/main/java/de/starwit/persistence/converter/ListToStringConverter.java
index 41aab98..4501c7c 100644
--- a/reacthookspring/persistence/src/main/java/de/starwit/persistence/converter/ListToStringConverter.java
+++ b/reacthookspring/persistence/src/main/java/de/starwit/persistence/converter/ListToStringConverter.java
@@ -4,7 +4,7 @@
import java.util.Collections;
import java.util.List;
-import javax.persistence.AttributeConverter;
+import jakarta.persistence.AttributeConverter;
public class ListToStringConverter implements AttributeConverter, String> {
@Override
diff --git a/reacthookspring/persistence/src/main/java/de/starwit/persistence/entity/AbstractEntity.java b/reacthookspring/persistence/src/main/java/de/starwit/persistence/entity/AbstractEntity.java
index 4af94a8..7c545a7 100755
--- a/reacthookspring/persistence/src/main/java/de/starwit/persistence/entity/AbstractEntity.java
+++ b/reacthookspring/persistence/src/main/java/de/starwit/persistence/entity/AbstractEntity.java
@@ -2,11 +2,11 @@
import java.io.Serializable;
-import javax.persistence.Column;
-import javax.persistence.GeneratedValue;
-import javax.persistence.GenerationType;
-import javax.persistence.Id;
-import javax.persistence.MappedSuperclass;
+import jakarta.persistence.Column;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class AbstractEntity {
diff --git a/reacthookspring/pom.xml b/reacthookspring/pom.xml
index 2ce2bc2..7dc64cb 100644
--- a/reacthookspring/pom.xml
+++ b/reacthookspring/pom.xml
@@ -1,12 +1,10 @@
-
+
4.0.0
org.springframework.boot
spring-boot-starter-parent
- 2.6.6
+ 3.1.1
de.starwit
@@ -18,10 +16,9 @@
UTF-8
UTF-8
- 2.6.6
- 5.3.27
- 2.17.2
- 1.6.7
+ 3.1.1
+ 6.0.1
+ 2.1.0
2.7.4
@@ -40,13 +37,6 @@
spring-core
${spring-version}
-
- org.keycloak.bom
- keycloak-adapter-bom
- 15.0.2
- pom
- import
-
org.springframework.boot
spring-boot-starter
@@ -141,14 +131,19 @@
spring-boot-starter-log4j2
${spring-boot-version}
+
+ org.springframework.data
+ spring-data-jpa
+ ${spring-boot-version}
+
org.springdoc
- springdoc-openapi-ui
+ springdoc-openapi-starter-webmvc-ui
${openapi-version}
org.springdoc
- springdoc-openapi-security
+ springdoc-openapi-starter-common
${openapi-version}
diff --git a/reacthookspring/rest/pom.xml b/reacthookspring/rest/pom.xml
index 8edae42..cf90d9b 100644
--- a/reacthookspring/rest/pom.xml
+++ b/reacthookspring/rest/pom.xml
@@ -15,10 +15,6 @@
org.springframework.security
spring-security-web
-
- org.keycloak
- keycloak-spring-security-adapter
-
org.springframework.boot
spring-boot-starter-validation
@@ -37,7 +33,7 @@
org.springdoc
- springdoc-openapi-ui
+ springdoc-openapi-starter-webmvc-ui
org.springframework.boot
diff --git a/reacthookspring/rest/src/main/java/de/starwit/rest/controller/UserController.java b/reacthookspring/rest/src/main/java/de/starwit/rest/controller/UserController.java
index ffd8d66..6824664 100644
--- a/reacthookspring/rest/src/main/java/de/starwit/rest/controller/UserController.java
+++ b/reacthookspring/rest/src/main/java/de/starwit/rest/controller/UserController.java
@@ -2,9 +2,9 @@
import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/reacthookspring/rest/src/main/java/de/starwit/rest/exception/ControllerExceptionHandler.java b/reacthookspring/rest/src/main/java/de/starwit/rest/exception/ControllerExceptionHandler.java
index 2540bc0..be56c6b 100644
--- a/reacthookspring/rest/src/main/java/de/starwit/rest/exception/ControllerExceptionHandler.java
+++ b/reacthookspring/rest/src/main/java/de/starwit/rest/exception/ControllerExceptionHandler.java
@@ -4,7 +4,7 @@
import java.util.HashMap;
import java.util.Map;
-import javax.persistence.EntityNotFoundException;
+import jakarta.persistence.EntityNotFoundException;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
@@ -16,6 +16,7 @@
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.security.access.AccessDeniedException;
@@ -132,7 +133,7 @@ public ResponseEntity
org.springdoc
- springdoc-openapi-ui
+ springdoc-openapi-starter-webmvc-ui
com.h2database
diff --git a/reacthookspring/service/src/main/java/de/starwit/service/impl/ServiceInterface.java b/reacthookspring/service/src/main/java/de/starwit/service/impl/ServiceInterface.java
index f8a0807..bcbd4f5 100644
--- a/reacthookspring/service/src/main/java/de/starwit/service/impl/ServiceInterface.java
+++ b/reacthookspring/service/src/main/java/de/starwit/service/impl/ServiceInterface.java
@@ -2,7 +2,7 @@
import java.util.List;
-import javax.persistence.EntityNotFoundException;
+import jakarta.persistence.EntityNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/reacthookspring/webclient/app/.gitignore b/reacthookspring/webclient/app/.gitignore
index 4d29575..d3ff5fc 100755
--- a/reacthookspring/webclient/app/.gitignore
+++ b/reacthookspring/webclient/app/.gitignore
@@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
+
+package-lock.json
\ No newline at end of file
diff --git a/reacthookspring/webclient/app/src/app/App.jsx b/reacthookspring/webclient/app/src/app/App.jsx
index f315477..6b5b286 100644
--- a/reacthookspring/webclient/app/src/app/App.jsx
+++ b/reacthookspring/webclient/app/src/app/App.jsx
@@ -2,7 +2,6 @@ import React from "react";
import MainContentRouter from "./MainContentRouter";
import {CssBaseline} from "@mui/material";
import {ErrorHandler} from "@starwit/react-starwit";
-import AppHeader from "./commons/appHeader/AppHeader";
import {useTranslation} from "react-i18next";
import {appItems} from "./AppConfig";
import Navigation from "./commons/navigation/Navigation";
diff --git a/reacthookspring/webclient/app/src/app/commons/navigation/appHeader/AppHeader.jsx b/reacthookspring/webclient/app/src/app/commons/navigation/appHeader/AppHeader.jsx
index 056383e..85b16ee 100644
--- a/reacthookspring/webclient/app/src/app/commons/navigation/appHeader/AppHeader.jsx
+++ b/reacthookspring/webclient/app/src/app/commons/navigation/appHeader/AppHeader.jsx
@@ -26,7 +26,7 @@ function AppHeader(props) {
onClick={() => history.push(item.link)}>{t(item.title)}
))}
history.push("/logout")}>
+ onClick={() => window.location.href = window.location.origin + window.location.pathname + "logout"}>
diff --git a/template-config-api-postgres.json b/template-config-api-postgres.json
new file mode 100755
index 0000000..3aba1e4
--- /dev/null
+++ b/template-config-api-postgres.json
@@ -0,0 +1,175 @@
+{
+ "templateName": "reacthookspring",
+ "packagePlaceholder": "starwit",
+ "imageUrl": "https://raw.githubusercontent.com/starwit/reacthook-spring-template/main/screenshot.png",
+ "templateFiles": [
+ {
+ "fileName": "pom.xml",
+ "templatePath": "api-postgres/pom.ftl",
+ "targetPath": "/",
+ "category": "BUILD"
+ },
+ {
+ "fileName": "pom.xml",
+ "templatePath": "api-postgres/pom-persistence.ftl",
+ "targetPath": "persistence/",
+ "category": "BUILD"
+ },
+ {
+ "fileName": "README.MD",
+ "templatePath": "api-postgres/readme.ftl",
+ "targetPath": "/",
+ "category": "DOC"
+ },
+ {
+ "fileName": "localenv-docker-compose.yml",
+ "templatePath": "api-postgres/deployment/localenv-docker-compose.ftl",
+ "targetPath": "deployment/",
+ "category": "DEPLOYMENT"
+ },
+ {
+ "fileName": "postgreslocal-docker-compose.yml",
+ "templatePath": "api-postgres/deployment/postgreslocal-docker-compose.ftl",
+ "targetPath": "deployment/",
+ "category": "DEPLOYMENT"
+ },
+ {
+ "fileName": "app-docker-compose.yml",
+ "templatePath": "api-postgres/deployment/app-docker-compose.ftl",
+ "targetPath": "deployment/https/",
+ "category": "DEPLOYMENT"
+ },
+ {
+ "fileName": "env-docker-compose.yml",
+ "templatePath": "api-postgres/deployment/env-docker-compose.ftl",
+ "targetPath": "deployment/https/",
+ "category": "DEPLOYMENT"
+ },
+ {
+ "fileName": "application.properties",
+ "templatePath": "api-postgres/application/properties.ftl",
+ "targetPath": "application/src/main/resources/",
+ "category": "APP"
+ },
+ {
+ "fileName": "application.properties",
+ "templatePath": "api-postgres/application/propertiestest.ftl",
+ "targetPath": "rest/src/test/resources/",
+ "category": "APP"
+ },
+ {
+ "fileName": "application.properties",
+ "templatePath": "api-postgres/application/propertiestestpersistence.ftl",
+ "targetPath": "persistence/src/test/resources/",
+ "category": "APP"
+ },
+ {
+ "fileName": "${entity.name}Entity.java",
+ "templatePath": "persistence/Entity-lowercaseIdentifier.ftl",
+ "targetPath": "persistence/src/main/java/de/${app.packageName?lower_case}/persistence/entity/",
+ "category": "ENTITY"
+ },
+ {
+ "fileName": "${enumDef.name}.java",
+ "templatePath": "persistence/Enum.ftl",
+ "targetPath": "persistence/src/main/java/de/${app.packageName?lower_case}/persistence/entity/",
+ "category": "ENTITY"
+ },
+ {
+ "fileName": "${entity.name}Repository.java",
+ "templatePath": "persistence/EntityRepository.ftl",
+ "targetPath": "persistence/src/main/java/de/${app.packageName?lower_case}/persistence/repository/",
+ "category": "ENTITY"
+ },
+ {
+ "fileName": "${entity.name}RepositoryTest.java",
+ "templatePath": "persistence/EntityRepositoryTest.ftl",
+ "targetPath": "persistence/src/test/java/de/${app.packageName?lower_case}/persistence/repository/",
+ "category": "ENTITY"
+ },
+ {
+ "fileName": "V1_0__init.sql",
+ "templatePath": "api-postgres/flyway/V1_0__init-postgres.ftl",
+ "targetPath": "persistence/src/main/resources/db/migration/",
+ "category": "ENTITY"
+ },
+ {
+ "fileName": "V1_0__init.sql",
+ "templatePath": "api-postgres/flyway/V1_0__init-postgres.ftl",
+ "targetPath": "persistence/src/test/resources/db/test/",
+ "category": "ENTITY"
+ },
+ {
+ "fileName": "${entity.name}Service.java",
+ "templatePath": "service/Service.ftl",
+ "targetPath": "service/src/main/java/de/${app.packageName?lower_case}/service/impl/",
+ "category": "SERVICE"
+ },
+ {
+ "fileName": "${entity.name}Controller.java",
+ "templatePath": "rest/Controller.ftl",
+ "targetPath": "rest/src/main/java/de/${app.packageName?lower_case}/rest/controller/",
+ "category": "REST"
+ },
+ {
+ "fileName": "${entity.name}ControllerAcceptanceTest.java",
+ "templatePath": "rest/ControllerAcceptanceTest.ftl",
+ "targetPath": "rest/src/test/java/de/${app.packageName?lower_case}/rest/acceptance/",
+ "category": "REST"
+ },
+ {
+ "fileName": "${entity.name}ControllerIntegrationTest.java",
+ "templatePath": "rest/ControllerIntegrationTest.ftl",
+ "targetPath": "rest/src/test/java/de/${app.packageName?lower_case}/rest/integration/",
+ "category": "REST"
+ },
+ {
+ "fileName": "AppConfig.js",
+ "templatePath": "webclient/AppConfig.ftl",
+ "targetPath": "webclient/app/src/app/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "translation-en-EN.js",
+ "templatePath": "webclient/translationEnEN.ftl",
+ "targetPath": "webclient/app/src/localization/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "${entity.name}Rest.js",
+ "templatePath": "webclient/EntityRest.ftl",
+ "targetPath": "webclient/app/src/app/services/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "${entity.name}Modifier.js",
+ "templatePath": "webclient/EntityModifier.ftl",
+ "targetPath": "webclient/app/src/app/modifiers/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "MainContentRouter.jsx",
+ "templatePath": "webclient/MainContentRouter.ftl",
+ "targetPath": "webclient/app/src/app/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "${entity.name}Main.jsx",
+ "templatePath": "webclient/EntityMain.ftl",
+ "targetPath": "webclient/app/src/app/features/${entity.name?uncap_first}/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "${entity.name}Detail.jsx",
+ "templatePath": "webclient/EntityDetail.ftl",
+ "targetPath": "webclient/app/src/app/features/${entity.name?uncap_first}/",
+ "category": "FRONTEND"
+ },
+ {
+ "fileName": "${entity.name}Overview.jsx",
+ "templatePath": "webclient/EntityOverview.ftl",
+ "targetPath": "webclient/app/src/app/features/${entity.name?uncap_first}/",
+ "category": "FRONTEND"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/template-config.json b/template-config.json
index 8203c38..ce712bc 100755
--- a/template-config.json
+++ b/template-config.json
@@ -128,6 +128,12 @@
"templatePath": "webclient/EntityOverview.ftl",
"targetPath": "webclient/app/src/app/features/${entity.name?uncap_first}/",
"category": "FRONTEND"
+ },
+ {
+ "fileName": ".public-env",
+ "templatePath": "webclient/.public-env.ftl",
+ "targetPath": "webclient/.public-env",
+ "category": "FRONTEND"
}
]
}
\ No newline at end of file