├── .idea ├── .gitignore ├── compiler.xml ├── dataSources.xml ├── encodings.xml ├── jarRepositories.xml ├── libraries │ ├── Maven__antlr_antlr_2_7_7.xml │ ├── Maven__ch_qos_logback_logback_classic_1_2_11.xml │ ├── Maven__ch_qos_logback_logback_core_1_2_11.xml │ ├── Maven__com_fasterxml_classmate_1_5_1.xml │ ├── Maven__com_fasterxml_jackson_core_jackson_annotations_2_13_4.xml │ ├── Maven__com_fasterxml_jackson_core_jackson_core_2_13_4.xml │ ├── Maven__com_fasterxml_jackson_core_jackson_databind_2_13_4_2.xml │ ├── Maven__com_fasterxml_jackson_datatype_jackson_datatype_jdk8_2_13_4.xml │ ├── Maven__com_fasterxml_jackson_datatype_jackson_datatype_jsr310_2_13_4.xml │ ├── Maven__com_fasterxml_jackson_module_jackson_module_parameter_names_2_13_4.xml │ ├── Maven__com_github_stephenc_jcip_jcip_annotations_1_0_1.xml │ ├── Maven__com_jayway_jsonpath_json_path_2_7_0.xml │ ├── Maven__com_nimbusds_nimbus_jose_jwt_9_22.xml │ ├── Maven__com_sun_activation_jakarta_activation_1_2_2.xml │ ├── Maven__com_sun_istack_istack_commons_runtime_3_0_12.xml │ ├── Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml │ ├── Maven__com_zaxxer_HikariCP_4_0_3.xml │ ├── Maven__jakarta_activation_jakarta_activation_api_1_2_2.xml │ ├── Maven__jakarta_annotation_jakarta_annotation_api_1_3_5.xml │ ├── Maven__jakarta_persistence_jakarta_persistence_api_2_2_3.xml │ ├── Maven__jakarta_transaction_jakarta_transaction_api_1_3_3.xml │ ├── Maven__jakarta_validation_jakarta_validation_api_2_0_2.xml │ ├── Maven__jakarta_xml_bind_jakarta_xml_bind_api_2_3_3.xml │ ├── Maven__net_bytebuddy_byte_buddy_1_12_18.xml │ ├── Maven__net_bytebuddy_byte_buddy_agent_1_12_18.xml │ ├── Maven__net_minidev_accessors_smart_2_4_8.xml │ ├── Maven__net_minidev_json_smart_2_4_8.xml │ ├── Maven__org_apache_logging_log4j_log4j_api_2_17_2.xml │ ├── Maven__org_apache_logging_log4j_log4j_to_slf4j_2_17_2.xml │ ├── Maven__org_apache_tomcat_embed_tomcat_embed_core_9_0_68.xml │ ├── Maven__org_apache_tomcat_embed_tomcat_embed_el_9_0_68.xml │ ├── Maven__org_apache_tomcat_embed_tomcat_embed_websocket_9_0_68.xml │ ├── Maven__org_apiguardian_apiguardian_api_1_1_2.xml │ ├── Maven__org_aspectj_aspectjweaver_1_9_7.xml │ ├── Maven__org_assertj_assertj_core_3_22_0.xml │ ├── Maven__org_checkerframework_checker_qual_3_5_0.xml │ ├── Maven__org_glassfish_jaxb_jaxb_runtime_2_3_7.xml │ ├── Maven__org_glassfish_jaxb_txw2_2_3_7.xml │ ├── Maven__org_hamcrest_hamcrest_2_2.xml │ ├── Maven__org_hibernate_common_hibernate_commons_annotations_5_1_2_Final.xml │ ├── Maven__org_hibernate_hibernate_core_5_6_12_Final.xml │ ├── Maven__org_hibernate_validator_hibernate_validator_6_2_5_Final.xml │ ├── Maven__org_jboss_jandex_2_4_2_Final.xml │ ├── Maven__org_jboss_logging_jboss_logging_3_4_3_Final.xml │ ├── Maven__org_junit_jupiter_junit_jupiter_5_8_2.xml │ ├── Maven__org_junit_jupiter_junit_jupiter_api_5_8_2.xml │ ├── Maven__org_junit_jupiter_junit_jupiter_engine_5_8_2.xml │ ├── Maven__org_junit_jupiter_junit_jupiter_params_5_8_2.xml │ ├── Maven__org_junit_platform_junit_platform_commons_1_8_2.xml │ ├── Maven__org_junit_platform_junit_platform_engine_1_8_2.xml │ ├── Maven__org_mockito_mockito_core_4_5_1.xml │ ├── Maven__org_mockito_mockito_junit_jupiter_4_5_1.xml │ ├── Maven__org_objenesis_objenesis_3_2.xml │ ├── Maven__org_opentest4j_opentest4j_1_2_0.xml │ ├── Maven__org_ow2_asm_asm_9_1.xml │ ├── Maven__org_postgresql_postgresql_42_3_7.xml │ ├── Maven__org_projectlombok_lombok_1_18_24.xml │ ├── Maven__org_skyscreamer_jsonassert_1_5_1.xml │ ├── Maven__org_slf4j_jul_to_slf4j_1_7_36.xml │ ├── Maven__org_slf4j_slf4j_api_1_7_36.xml │ ├── Maven__org_springframework_boot_spring_boot_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_autoconfigure_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_aop_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_data_jpa_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_jdbc_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_json_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_logging_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_oauth2_resource_server_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_security_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_test_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_tomcat_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_validation_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_starter_web_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_test_2_7_5.xml │ ├── Maven__org_springframework_boot_spring_boot_test_autoconfigure_2_7_5.xml │ ├── Maven__org_springframework_data_spring_data_commons_2_7_5.xml │ ├── Maven__org_springframework_data_spring_data_jpa_2_7_5.xml │ ├── Maven__org_springframework_security_spring_security_config_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_core_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_crypto_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_oauth2_core_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_oauth2_jose_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_oauth2_resource_server_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_test_5_7_4.xml │ ├── Maven__org_springframework_security_spring_security_web_5_7_4.xml │ ├── Maven__org_springframework_spring_aop_5_3_23.xml │ ├── Maven__org_springframework_spring_aspects_5_3_23.xml │ ├── Maven__org_springframework_spring_beans_5_3_23.xml │ ├── Maven__org_springframework_spring_context_5_3_23.xml │ ├── Maven__org_springframework_spring_core_5_3_23.xml │ ├── Maven__org_springframework_spring_expression_5_3_23.xml │ ├── Maven__org_springframework_spring_jcl_5_3_23.xml │ ├── Maven__org_springframework_spring_jdbc_5_3_23.xml │ ├── Maven__org_springframework_spring_orm_5_3_23.xml │ ├── Maven__org_springframework_spring_test_5_3_23.xml │ ├── Maven__org_springframework_spring_tx_5_3_23.xml │ ├── Maven__org_springframework_spring_web_5_3_23.xml │ ├── Maven__org_springframework_spring_webmvc_5_3_23.xml │ ├── Maven__org_xmlunit_xmlunit_core_2_9_0.xml │ └── Maven__org_yaml_snakeyaml_1_30.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── README.md ├── angular-app ├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── tasks.json ├── README.md ├── angular-app.iml ├── angular.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── proxy.conf.json ├── src │ ├── app │ │ ├── access-denied │ │ │ ├── access-denied.component.css │ │ │ ├── access-denied.component.html │ │ │ ├── access-denied.component.spec.ts │ │ │ └── access-denied.component.ts │ │ ├── api │ │ │ └── web-api.service.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── auth │ │ │ ├── auth.guard.ts │ │ │ └── auth.service.ts │ │ └── user-info │ │ │ ├── user-info.component.css │ │ │ ├── user-info.component.html │ │ │ └── user-info.component.ts │ ├── assets │ │ └── .gitkeep │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ └── utils │ │ └── app-init.ts ├── tsconfig.app.json ├── tsconfig.json └── tsconfig.spec.json ├── docker-compose.yml ├── docs ├── aimg.png ├── aimg_1.png ├── aimg_10.png ├── aimg_11.png ├── aimg_12.png ├── aimg_13.png ├── aimg_14.png ├── aimg_2.png ├── aimg_3.png ├── aimg_4.png ├── aimg_5.png ├── aimg_6.png ├── aimg_7.png ├── aimg_8.png ├── aimg_9.png ├── img.png ├── img_1.png ├── img_10.png ├── img_11.png ├── img_12.png ├── img_13.png ├── img_14.png ├── img_15.png ├── img_16.png ├── img_2.png ├── img_3.png ├── img_4.png ├── img_5.png ├── img_6.png ├── img_7.png ├── img_8.png └── img_9.png ├── spring-app ├── .gitignore ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── springapp │ │ │ ├── SpringAppApplication.java │ │ │ ├── controller │ │ │ └── WebController.java │ │ │ ├── entity │ │ │ └── User.java │ │ │ ├── repository │ │ │ └── UserRepository.java │ │ │ ├── security │ │ │ ├── JwtUserSyncFilter.java │ │ │ └── SecurityConfig.java │ │ │ └── service │ │ │ └── UserService.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── example │ └── springapp │ └── SpringAppApplicationTests.java └── springboot-keycloak-angular.iml /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | postgresql 6 | true 7 | org.postgresql.Driver 8 | jdbc:postgresql://localhost:5432/ewizitingDB 9 | $ProjectFileDir$ 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__antlr_antlr_2_7_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_classic_1_2_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__ch_qos_logback_logback_core_1_2_11.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_classmate_1_5_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_annotations_2_13_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_core_2_13_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_core_jackson_databind_2_13_4_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_datatype_jackson_datatype_jdk8_2_13_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_datatype_jackson_datatype_jsr310_2_13_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_fasterxml_jackson_module_jackson_module_parameter_names_2_13_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_github_stephenc_jcip_jcip_annotations_1_0_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_jayway_jsonpath_json_path_2_7_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_nimbusds_nimbus_jose_jwt_9_22.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_sun_activation_jakarta_activation_1_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_sun_istack_istack_commons_runtime_3_0_12.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_vaadin_external_google_android_json_0_0_20131108_vaadin1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__com_zaxxer_HikariCP_4_0_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jakarta_activation_jakarta_activation_api_1_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jakarta_annotation_jakarta_annotation_api_1_3_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jakarta_persistence_jakarta_persistence_api_2_2_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jakarta_transaction_jakarta_transaction_api_1_3_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jakarta_validation_jakarta_validation_api_2_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__jakarta_xml_bind_jakarta_xml_bind_api_2_3_3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_bytebuddy_byte_buddy_1_12_18.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_bytebuddy_byte_buddy_agent_1_12_18.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_minidev_accessors_smart_2_4_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__net_minidev_json_smart_2_4_8.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_logging_log4j_log4j_api_2_17_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_logging_log4j_log4j_to_slf4j_2_17_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_core_9_0_68.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_el_9_0_68.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apache_tomcat_embed_tomcat_embed_websocket_9_0_68.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_apiguardian_apiguardian_api_1_1_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_aspectj_aspectjweaver_1_9_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_assertj_assertj_core_3_22_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_checkerframework_checker_qual_3_5_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_glassfish_jaxb_jaxb_runtime_2_3_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_glassfish_jaxb_txw2_2_3_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hamcrest_hamcrest_2_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hibernate_common_hibernate_commons_annotations_5_1_2_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hibernate_hibernate_core_5_6_12_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_hibernate_validator_hibernate_validator_6_2_5_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_jboss_jandex_2_4_2_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_jboss_logging_jboss_logging_3_4_3_Final.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_5_8_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_api_5_8_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_engine_5_8_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_jupiter_junit_jupiter_params_5_8_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_platform_junit_platform_commons_1_8_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_junit_platform_junit_platform_engine_1_8_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mockito_mockito_core_4_5_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_mockito_mockito_junit_jupiter_4_5_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_objenesis_objenesis_3_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_opentest4j_opentest4j_1_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_ow2_asm_asm_9_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_postgresql_postgresql_42_3_7.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_projectlombok_lombok_1_18_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_skyscreamer_jsonassert_1_5_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_jul_to_slf4j_1_7_36.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_autoconfigure_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_aop_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_data_jpa_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_jdbc_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_json_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_logging_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_oauth2_resource_server_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_security_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_test_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_tomcat_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_validation_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_starter_web_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_test_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_boot_spring_boot_test_autoconfigure_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_data_spring_data_commons_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_data_spring_data_jpa_2_7_5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_config_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_core_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_crypto_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_oauth2_core_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_oauth2_jose_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_oauth2_resource_server_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_test_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_security_spring_security_web_5_7_4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_aop_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_aspects_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_beans_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_context_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_core_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_expression_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_jcl_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_jdbc_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_orm_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_test_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_tx_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_web_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_springframework_spring_webmvc_5_3_23.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_xmlunit_xmlunit_core_2_9_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/libraries/Maven__org_yaml_snakeyaml_1_30.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Overview 4 | * Keycloak version: 20.0 5 | 6 | ## Useful links 7 | * [Setting up keycloack with spring boot](https://www.baeldung.com/spring-boot-keycloak) 8 | * [Secure Spring Boot + Angular 9 Application using KeyCloak(1/3)](https://medium.com/@kamleshbadgujar00/secure-spring-boot-angular-9-application-using-keycloak-1-3-b00e801ba693) 9 | * [Github - Backend (Spring Boot)](https://github.com/kamleshbadgujar/heroes-backend) 10 | * [Github - Frontend (Angular)](https://github.com/kamleshbadgujar/heroes-app) 11 | 12 | # Tutorial 13 | 14 | ## 1. Deploying Keycloak + Postgres with Docker 15 | > Remember to create database in postgres for Keycloak. By default, docker compose creates database for Spring Boot only. To do this purpose you can use `pgadmin`. 16 | 17 | *docker-compose.yml* 18 | ```yaml 19 | version: "3.8" 20 | services: 21 | 22 | postgresql: 23 | container_name: postgres 24 | image: postgres:14.4 25 | restart: always 26 | ports: 27 | - "5432:5432" # External port access for the development profile 28 | environment: 29 | POSTGRES_PASSWORD: admin 30 | POSTGRES_USER: admin 31 | POSTGRES_DB: ewizitingDB 32 | 33 | pgadmin: 34 | container_name: pgadmin 35 | image: dpage/pgadmin4:latest 36 | restart: always 37 | ports: 38 | - "81:80" 39 | environment: 40 | PGADMIN_DEFAULT_EMAIL: admin@admin.com 41 | PGADMIN_DEFAULT_PASSWORD: admin 42 | 43 | keycloak: 44 | image: quay.io/keycloak/keycloak:20.0.0 45 | environment: 46 | KC_DB: postgres 47 | KC_DB_URL: jdbc:postgresql://postgresql:5432/keycloak 48 | KC_DB_USERNAME: admin 49 | KC_DB_PASSWORD: admin 50 | KEYCLOAK_ADMIN: admin 51 | KEYCLOAK_ADMIN_PASSWORD: admin 52 | # Uncomment the line below if you want to specify JDBC parameters. The parameter below is just an example, and it shouldn't be used in production without knowledge. It is highly recommended that you read the PostgreSQL JDBC driver documentation in order to use it. 53 | #JDBC_PARAMS: "ssl=true" 54 | ports: 55 | - "8081:8081" 56 | command: [ "start-dev", "--http-port=8081" ] 57 | depends_on: 58 | - postgresql 59 | ``` 60 | Start docker with `docker compose up`. 61 | 62 | ## 2. Keycloak - configuration 63 | > Remember to create database in postgres for Keycloak. By default, docker compose creates database for Spring Boot only. To do this purpose you can use `pgadmin`. 64 | 65 | ### 2.1. Creating a realm 66 | Realm is a separate "container" for our application. It contains set of users, clients and global roles.
67 | We can say **realm** represents our application infrastructure, which can have multiple **clients (e.g. backend and frontend)**. 68 | 69 | 1. Go to http://localhost:8081 -> administrator console and login with username and password: `admin` 70 | 2. Click upper top corner to create new realm. By default, you will only see `master` (ignore my `hospital-app` realm). 71 | ![img.png](docs/img.png) 72 | 3. Let's add new realm and call it `test-app`. After clicking the `Create` button, a new realm will be created.
73 | ![img_1.png](docs/aimg_1.png) 74 | 4. **Remember to perform all the operations in this new realm.**
75 | 76 | ### 2.2. Creating a client for backend (Spring Boot) 77 | **Client** is a thing that will access data from our **realm**. For example a client can be **backend - Spring Boot** and/or **frontened - Angular**. 78 | 79 | 1. Go to `Clients -> Create client`
80 | ![img_6.png](docs/aimg_6.png) 81 | 2. As we want to create a client for Spring Boot, populate `Client ID` with `backend` and smash next.
82 | ![img_7.png](docs/aimg_7.png) 83 | 3. Select `Standard flow` and `Direct access grants`, because Spring Boot backend service **will only verify bearer token, it will never initiate the login**. Click save.
84 | ![img_8.png](docs/aimg_8.png) 85 | 4. Now go to `Clients` and select our newly created `backend` client.
86 | Find `Valid redirect URIs` and type `http://localhost:8080/*` (the Spring Boot url - remember to add `*`) and save.
87 | ![img_9.png](docs/aimg_9.png) 88 | 89 | ### 2.3. Creating a client for frontend (Angular) 90 | 1. Go to `Clients -> Create client`
91 | ![img_2.png](docs/img_2.png) 92 | 2. Fill in `Client ID` with `frontend`. Next.
93 | ![img_2.png](docs/aimg_2.png) 94 | 3. Select `Standard flow`, `Direct access grants` and `Implicit flow`.
95 | ![img_3.png](docs/aimg_3.png) 96 | 4. Now go to `Clients` and select our newly created `frontend` client.
97 | Fill in `Valid redirect URIs` and `Valid post logout redirect URIs` with URIs you redirect after login/logout on frontend e.g. I used my local ip address. 98 | Fill in `Web origins` with `*` and save.
99 | ![img_4.png](docs/aimg_4.png) 100 | 101 | ### 2.4. Creating `Roles` 102 | We have 2 types of roles. Global (for every client) called `Realm roles`, and local `Client roles` available only for specific client. 103 | In the tutorial we use only global roles - `Realm roles`. 104 | 105 | 1. Go to `Realm roles -> Create role`
106 | 2. Our role will be `user`
107 | ![img_10.png](docs/aimg_10.png) 108 | 109 | ### 2.5. Enabling Registration 110 | 1. Go to `Realm settings -> Login`
111 | 2. Check `User registration` and `Email as username`
112 | ![img_12.png](docs/aimg_12.png) 113 | 114 | ### 2.6. Making role to be added by default for every registered user 115 | 1. Go to `Realm settings -> User registration`
116 | 2. Click `Assign role`
117 | ![img_13.png](docs/aimg_13.png) 118 | 3. Choose our role `user` and click `Assign` 119 | ![img_14.png](docs/aimg_14.png) 120 | 121 | Now every user registered using our Angular frontend will be automatically assigned to role `user`. 122 | 123 | ### 2.7. Creating 'test' users 124 | I call it `test user`, because this user is created 'artificially', we will add a registration process later. 125 | 126 | 1. Go to `Users -> Add user` 127 | 2. Fill in `Email` and check `Email verified`.
128 | Make sure `Required user actions` field is empty that we can start using this account without any further adjustments (e.g. first time log in password change, email confirmation etc.). Click create.
129 | ![img_15.png](docs/img_15.png) 130 | 3. Go to `Users` and select newly create user. 131 | Go to `Credentials tab` and add password by clicking `Set password`.
132 | Make sure to uncheck `Temporary` and `Save`.
133 | ![img_16.png](docs/img_16.png) 134 | 135 | ### 2.8. Adding roles to user 136 | > This step is just to inform you, how to add role to user. We don't have to do that because our role `user` is automatically assigned for every user. 137 | 138 | 1. Go to `Users` and select newly create user. 139 | 2. Go to `Role mapping` tab 140 | ![img_13.png](docs/img_13.png) 141 | 3. Click `Assign role`, choose your newly created role and hit `Assign` 142 | (here some random created role `teacher`) 143 | ![img_14.png](docs/img_14.png) 144 | 145 | 146 | ## 3. Spring Boot - configuration 147 | 148 | ### 3.1. Maven thing - *pom.xml* 149 | We just need basci stuff (postgres, lombok and spring).

150 | *pom.xml* 151 | ```xml 152 | 153 | 155 | 4.0.0 156 | 157 | org.springframework.boot 158 | spring-boot-starter-parent 159 | 2.7.5 160 | 161 | 162 | com.example 163 | spring-app 164 | 0.0.1-SNAPSHOT 165 | spring-app 166 | spring-app 167 | 168 | 11 169 | 170 | 171 | 172 | org.springframework.boot 173 | spring-boot-starter-data-jpa 174 | 175 | 176 | org.springframework.boot 177 | spring-boot-starter-security 178 | 179 | 180 | org.springframework.boot 181 | spring-boot-starter-validation 182 | 183 | 184 | 185 | org.postgresql 186 | postgresql 187 | runtime 188 | 189 | 190 | org.projectlombok 191 | lombok 192 | true 193 | 194 | 195 | org.springframework.boot 196 | spring-boot-starter-test 197 | test 198 | 199 | 200 | org.springframework.security 201 | spring-security-test 202 | test 203 | 204 | 205 | org.springframework.boot 206 | spring-boot-starter-web 207 | 208 | 209 | 210 | 211 | org.springframework.boot 212 | spring-boot-starter-oauth2-resource-server 213 | 214 | 215 | 216 | 217 | 218 | 219 | org.springframework.boot 220 | spring-boot-maven-plugin 221 | 222 | 223 | 224 | org.projectlombok 225 | lombok 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | ``` 235 | 236 | ### 3.2. *application.properties* 237 | *application.properties* 238 | ```yaml 239 | # -- Spring Boot -- 240 | server.port=8080 241 | 242 | # -- Postgresql database -- 243 | spring.datasource.url=jdbc:postgresql://localhost:5432/ewizitingDB 244 | spring.datasource.username=admin 245 | spring.datasource.password=admin 246 | spring.jpa.hibernate.ddl-auto=create 247 | 248 | # -- Keycloak -- 249 | spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/test-app 250 | ``` 251 | 252 | ### 3.3. SecurityConfig.java 253 | 254 | Basic spring security configuration.
255 | - `jwtAuthenticationConverterForKeycloak` - maps Keycloak roles (REALM and CLIENT level) to get them all with user info. By default, spring doesn't retrieve realm? level roles. 256 | - `http.anonymous().disable();` - disable anonymous requests (not logged in users). 257 | - `http.authorizeRequests().anyRequest().hasRole("user");` - allow to use site only for logged users with role `user`. 258 | - `jwtAuthUserFilterBean()`, `http.addFilterAfter(jwtAuthUserFilterBean(), SwitchUserFilter.class);` - in my case I need a 'brief' user database to be able to (create relation) link user with other table in Spring Boot e.g. users are teachers and I link them with classes. To be able to do that, i need `Users` table. For that matter, filter synchronizes logged user with Spring Boot user table. 259 | 260 | *SecurityConfig.java* 261 | ```java 262 | package com.example.springapp.security; 263 | 264 | import lombok.RequiredArgsConstructor; 265 | import lombok.extern.slf4j.Slf4j; 266 | import org.springframework.context.annotation.Bean; 267 | import org.springframework.context.annotation.Configuration; 268 | import org.springframework.core.convert.converter.Converter; 269 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 270 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 271 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 272 | import org.springframework.security.config.http.SessionCreationPolicy; 273 | import org.springframework.security.core.GrantedAuthority; 274 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 275 | import org.springframework.security.oauth2.jwt.Jwt; 276 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; 277 | import org.springframework.security.web.SecurityFilterChain; 278 | import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; 279 | 280 | import java.util.Collection; 281 | import java.util.Map; 282 | import java.util.stream.Collectors; 283 | 284 | @Configuration 285 | @EnableWebSecurity 286 | @EnableGlobalMethodSecurity(prePostEnabled = true) 287 | @RequiredArgsConstructor 288 | @Slf4j 289 | public class SecurityConfig { 290 | 291 | /** 292 | * Map Keycloak roles (REALM and CLIENT level) to get them all 293 | * */ 294 | @Bean 295 | public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() { 296 | 297 | log.warn("CONVERT"); 298 | 299 | Converter> jwtGrantedAuthoritiesConverter = jwt -> { 300 | Map> realmAccess = jwt.getClaim("realm_access"); 301 | Collection roles = realmAccess.get("roles"); 302 | return roles.stream() 303 | .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) 304 | .collect(Collectors.toList()); 305 | }; 306 | 307 | var jwtAuthenticationConverter = new JwtAuthenticationConverter(); 308 | jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); 309 | 310 | return jwtAuthenticationConverter; 311 | } 312 | 313 | /** 314 | * Sync local minimal user database with Keycloak db 315 | * */ 316 | @Bean 317 | public JwtUserSyncFilter jwtAuthUserFilterBean() { 318 | return new JwtUserSyncFilter(); 319 | } 320 | 321 | /** 322 | * Configure security 323 | * */ 324 | @Bean 325 | public SecurityFilterChain configure(HttpSecurity http) throws Exception { 326 | http.csrf().disable(); 327 | http.cors().disable(); 328 | http.anonymous().disable(); 329 | 330 | http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 331 | http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak()); 332 | 333 | http.authorizeRequests() 334 | .anyRequest() 335 | .hasRole("user"); 336 | 337 | http.addFilterAfter(jwtAuthUserFilterBean(), SwitchUserFilter.class); 338 | 339 | return http.build(); 340 | } 341 | 342 | } 343 | ``` 344 | 345 | ### 3.4. User.java - `User` table 346 | Stores basic user info retrieved from token.
347 | *User.java* 348 | ```java 349 | package com.example.springapp.entity; 350 | 351 | import lombok.*; 352 | import javax.persistence.*; 353 | 354 | @Entity 355 | @Table(name = "USER_") 356 | @Setter @Getter 357 | @Builder 358 | @AllArgsConstructor 359 | @NoArgsConstructor 360 | public class User { 361 | 362 | @Id 363 | @Column 364 | private String email; 365 | 366 | @Column 367 | private String firstname; 368 | 369 | @Column 370 | private String lastname; 371 | 372 | @Column 373 | @Enumerated(EnumType.STRING) 374 | private Gender gender; 375 | 376 | public enum Gender { 377 | MALE, FEMALE 378 | } 379 | 380 | } 381 | ``` 382 | 383 | ### 3.5. UserService.java 384 | Manages `User` table.
385 | 386 | *UserService.java* 387 | ```java 388 | package com.example.springapp.service; 389 | 390 | import com.example.springapp.entity.User; 391 | import com.example.springapp.repository.UserRepository; 392 | import lombok.RequiredArgsConstructor; 393 | import org.springframework.security.core.context.SecurityContextHolder; 394 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 395 | import org.springframework.stereotype.Service; 396 | 397 | import javax.persistence.EntityNotFoundException; 398 | import java.util.Optional; 399 | 400 | @Service 401 | @RequiredArgsConstructor 402 | public class UserService { 403 | 404 | private final UserRepository userRepository; 405 | 406 | public User getLoggedUser() { 407 | JwtAuthenticationToken token = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); 408 | 409 | String email = String.valueOf(token.getTokenAttributes().get("email")); 410 | User user = userRepository.findByEmail(email).orElseThrow(() -> new EntityNotFoundException("Error while fetching user")); 411 | 412 | return user; 413 | } 414 | 415 | public void syncUser(User user) { 416 | if (user == null) { 417 | throw new EntityNotFoundException("Error while user sync"); 418 | } 419 | 420 | User saveUser = user; 421 | Optional optionalUser = userRepository.findByEmail(user.getEmail()); 422 | 423 | if (optionalUser.isPresent()) { 424 | saveUser = optionalUser.get(); 425 | saveUser.setFirstname(user.getFirstname()); 426 | saveUser.setLastname(user.getLastname()); 427 | } 428 | 429 | userRepository.save(saveUser); 430 | } 431 | 432 | } 433 | ``` 434 | 435 | ### 3.6. JwtUserSyncFilter.java 436 | Retrieve data from token and sync local user table.
437 | 438 | *JwtUserSyncFilter.java* 439 | ```java 440 | package com.example.springapp.security; 441 | 442 | import com.example.springapp.entity.User; 443 | import com.example.springapp.service.UserService; 444 | import org.springframework.beans.factory.annotation.Autowired; 445 | import org.springframework.security.core.context.SecurityContextHolder; 446 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 447 | import org.springframework.web.filter.OncePerRequestFilter; 448 | 449 | import javax.servlet.FilterChain; 450 | import javax.servlet.ServletException; 451 | import javax.servlet.http.HttpServletRequest; 452 | import javax.servlet.http.HttpServletResponse; 453 | import java.io.IOException; 454 | 455 | 456 | public class JwtUserSyncFilter extends OncePerRequestFilter { 457 | 458 | @Autowired 459 | private UserService userService; 460 | 461 | @Override 462 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 463 | 464 | try { 465 | JwtAuthenticationToken token = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); 466 | 467 | String firstname = String.valueOf(token.getTokenAttributes().get("given_name")); 468 | String lastname = String.valueOf(token.getTokenAttributes().get("family_name")); 469 | String email = String.valueOf(token.getTokenAttributes().get("email")); 470 | User.Gender gender = token.getTokenAttributes().get("gender") == null ? null : 471 | User.Gender.valueOf(String.valueOf(token.getTokenAttributes().get("gender")).toUpperCase()); 472 | 473 | User user = User.builder() 474 | .firstname(firstname) 475 | .lastname(lastname) 476 | .email(email) 477 | .gender(gender) 478 | .build(); 479 | 480 | userService.syncUser(user); 481 | } catch (Exception e) { 482 | throw new IllegalArgumentException("Unable to auth user"); 483 | } 484 | 485 | filterChain.doFilter(request, response); 486 | } 487 | 488 | } 489 | ``` 490 | 491 | ### 3.7. WebController.java 492 | Controller for demonstration purposes. 493 | - `getUserInfo1()` - gets user info from synced `User` Spring Boot database 494 | - `getUserInfo2()` - gets user info directly from the token 495 | > We need to add `@CrossOrigin(origins = "http://localhost:4200")` to be able to use endpoint from Angular frontend. 496 | 497 | *WebController.java* 498 | ```java 499 | package com.example.springapp.controller; 500 | 501 | import com.example.springapp.entity.User; 502 | import com.example.springapp.service.UserService; 503 | import lombok.RequiredArgsConstructor; 504 | import org.slf4j.Logger; 505 | import org.slf4j.LoggerFactory; 506 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 507 | import org.springframework.web.bind.annotation.CrossOrigin; 508 | import org.springframework.web.bind.annotation.GetMapping; 509 | import org.springframework.web.bind.annotation.RestController; 510 | 511 | 512 | @RestController 513 | @CrossOrigin(origins = "http://localhost:4200") 514 | @RequiredArgsConstructor 515 | public class WebController { 516 | 517 | private static final Logger logger = LoggerFactory.getLogger(WebController.class); 518 | 519 | private final UserService userService; 520 | 521 | @GetMapping(path = "/userInfo1") 522 | public String getUserInfo1() { 523 | User user = userService.getLoggedUser(); 524 | 525 | return "UserInfo1: " + user.getFirstname() + " " + user.getLastname() + ", " + user.getEmail(); 526 | } 527 | 528 | @GetMapping("/userInfo2") 529 | public String getUserInfo2(JwtAuthenticationToken auth) { 530 | String firstname = auth.getTokenAttributes().get("given_name").toString(); 531 | String lastname = auth.getTokenAttributes().get("family_name").toString(); 532 | String email = auth.getTokenAttributes().get("email").toString(); 533 | String authorities = auth.getAuthorities().toString(); 534 | 535 | return "UserInfo2: " + firstname + " " + lastname + ", " + email + ", " + authorities ; 536 | } 537 | 538 | } 539 | ``` 540 | 541 | ## 4. Angular - configuration 542 | 543 | ### 4.0. Open your Angular project or create new 544 | Create new project using: 545 | 546 | ``` 547 | ng new angular-app 548 | ``` 549 | 550 | While creating project make sure you use routing. 551 | 552 | ### 4.1. Install dependencies 553 | ``` 554 | npm install keycloak-angular --save 555 | npm install keycloak-js --save 556 | ``` 557 | 558 | ### 4.2. Configure proxy to avoid any CORS policy 559 | Every call made to `http://localhost:4200/api`, will be redirected to our Spring Boot application `http:localhost:8080`. 560 | 561 | 1. Create file `proxy.conf.json` inside project directory 562 | ```json 563 | { 564 | "/api": { 565 | "target": "http://localhost:8080", 566 | "secure": false, 567 | "pathRewrite": {"^/api" : ""} 568 | } 569 | } 570 | ``` 571 | 572 | 2. Edit `package.json` (or start script using this command by hand) 573 | ``` 574 | "start": "ng serve --proxy-config proxy.conf.json", 575 | ``` 576 | 577 | ### 4.3. Config Keycloak using `enviroment` 578 | Update enviroment file. 579 | 580 | *src/enviroments/enviroment.ts* 581 | ```typescript 582 | export const environment = { 583 | production: false, 584 | apiUrl: '/api', 585 | keycloak: { 586 | // Keycloak url 587 | issuer: 'http://localhost:8081', 588 | // Realm 589 | realm: 'test-app', 590 | clientId: 'frontend' 591 | }, 592 | }; 593 | ``` 594 | 595 | ### 4.4. Initialize Keycloak service 596 | In order to make sure Keycloak is initialized you will have to add an APP_INITIALIZER provider to your AppModule. 597 | This provider will call the initializeKeycloak function shown below which will set up the Keycloak service. 598 | 599 | 1. Create folder and file *utils/app-init.ts* 600 | 601 | *utils/app-init.ts* 602 | ```typescript 603 | import { KeycloakService } from "keycloak-angular"; 604 | import { environment } from "../environments/environment"; 605 | 606 | export function initializer(keycloak: KeycloakService): () => Promise { 607 | return (): Promise => { 608 | return new Promise(async (resolve, reject) => { 609 | try { 610 | await keycloak.init({ 611 | config: { 612 | url: environment.keycloak.issuer, 613 | realm: environment.keycloak.realm, 614 | clientId: environment.keycloak.clientId, 615 | }, 616 | // If set a false you cannot get any information about the user (e.g. username) 617 | loadUserProfileAtStartUp: true, 618 | initOptions: { 619 | onLoad: 'login-required', 620 | checkLoginIframe: true, 621 | // silentCheckSsoRedirectUri: window.location.origin + '/assets/silent-check-sso.html', 622 | }, 623 | // By default, the keycloak-angular library add 'Authorization: Bearer TOKEN' header to all http requests 624 | // Add here if u want to exclude urls (to not have that header) 625 | bearerExcludedUrls: ['/assets'] 626 | }); 627 | 628 | resolve(resolve); 629 | } catch (err) { 630 | reject(err); 631 | } 632 | }); 633 | }; 634 | 635 | } 636 | ``` 637 | 638 | 2. Update *src/app/app.module.ts* 639 | 640 | *app.module.ts* 641 | ```typescript 642 | import {APP_INITIALIZER, NgModule} from '@angular/core'; 643 | import { BrowserModule } from '@angular/platform-browser'; 644 | 645 | import { AppComponent } from './app.component'; 646 | import {initializer} from "../utils/app-init"; 647 | import {KeycloakAngularModule, KeycloakService} from "keycloak-angular"; 648 | import { AppRoutingModule } from './app-routing.module'; 649 | import { AccessDeniedComponent } from './access-denied/access-denied.component'; 650 | import { UserInfoComponent } from './user-info/user-info.component'; 651 | import {HttpClientModule} from "@angular/common/http"; 652 | 653 | @NgModule({ 654 | declarations: [ 655 | AppComponent, 656 | AccessDeniedComponent, 657 | UserInfoComponent 658 | ], 659 | imports: [ 660 | BrowserModule, 661 | AppRoutingModule, 662 | KeycloakAngularModule, 663 | HttpClientModule 664 | ], 665 | providers: [ 666 | { 667 | provide: APP_INITIALIZER, 668 | useFactory: initializer, 669 | deps: [ KeycloakService ], 670 | multi: true 671 | } 672 | ], 673 | bootstrap: [AppComponent] 674 | }) 675 | export class AppModule { } 676 | ``` 677 | 678 | ### 4.5. Protect pages using guard 679 | In order to make some pages available for logged users with enough roles, we will create guard and service for authorization purposes.
680 | Create folder *src/app/auth*. 681 | 682 | *src/app/auth/auth.guard.ts* 683 | ```typescript 684 | import {Injectable} from "@angular/core"; 685 | import {ActivatedRouteSnapshot, Router, RouterStateSnapshot} from "@angular/router"; 686 | import {KeycloakAuthGuard, KeycloakService} from "keycloak-angular"; 687 | import Keycloak from "keycloak-js"; 688 | 689 | @Injectable({ 690 | providedIn: 'root' 691 | }) 692 | export class AuthGuard extends KeycloakAuthGuard { 693 | 694 | constructor(protected override readonly router: Router, 695 | protected readonly keycloak: KeycloakService 696 | ) { 697 | super(router, keycloak); 698 | } 699 | 700 | public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 701 | 702 | // If not logged, redirect to login page 703 | if (!this.authenticated) { 704 | await this.keycloak.login({ 705 | redirectUri: window.location.origin + state.url 706 | }); 707 | } 708 | 709 | // Get the roles from keycloak 710 | const keycloakRoles = this.roles; 711 | 712 | // Get the roles from app.routing.module.ts 713 | const requiredRoles = route.data['roles']; 714 | 715 | // If page doesn't need any extra roles 716 | if (!(requiredRoles instanceof Array) || requiredRoles.length == 0) { 717 | return true; 718 | } 719 | 720 | // Check whether user has role to visit page (check keycloak roles against app.routing.module.ts config) 721 | if (requiredRoles.every(role => keycloakRoles.includes(role))) { 722 | return true; 723 | } 724 | 725 | // If user doesn't have necessary roles, redirect to error page 726 | this.router.navigate(['access-denied']); 727 | return false; 728 | } 729 | 730 | } 731 | ``` 732 | 733 | *src/app/auth/auth.service.ts* 734 | ```typescript 735 | import {Injectable} from "@angular/core"; 736 | import {KeycloakService} from "keycloak-angular"; 737 | 738 | @Injectable({ 739 | providedIn: "root" 740 | }) 741 | export class AuthService { 742 | 743 | constructor(private keycloakService: KeycloakService) { } 744 | 745 | public getUsername(): string { 746 | return this.keycloakService.getUsername(); 747 | } 748 | 749 | public logout(): void { 750 | this.keycloakService.logout().then(() => this.keycloakService.clearToken()); 751 | } 752 | 753 | } 754 | ``` 755 | 756 | ### 4.6. Create and configure pages (components) 757 | 1. Create page for `access-denied` purposes. 758 | ``` 759 | ng g c access-denied 760 | ```` 761 | 762 | 2. Create page for `user-info`. 763 | ``` 764 | ng g c user-info 765 | ```` 766 | 767 | 3. Update *src/app/user-info/user-info.component.ts* 768 | 769 | *user-info.component.ts* 770 | ```typescript 771 | import { Component, OnInit } from '@angular/core'; 772 | import {HttpClient} from "@angular/common/http"; 773 | import {AuthService} from "../auth/auth.service"; 774 | import {WebApiService} from "../api/web-api.service"; 775 | 776 | @Component({ 777 | selector: 'app-user-info', 778 | templateUrl: './user-info.component.html', 779 | styleUrls: ['./user-info.component.css'] 780 | }) 781 | export class UserInfoComponent implements OnInit { 782 | 783 | message: string = 'null'; 784 | 785 | constructor(private authService: AuthService, private webApiService: WebApiService, private http: HttpClient) { } 786 | 787 | ngOnInit(): void { 788 | this.message = this.authService.getUsername(); 789 | 790 | this.webApiService.getUserInfo().subscribe({ 791 | next: data => { 792 | this.message += ", " + data; 793 | }, error: err => { 794 | console.log(err); 795 | } 796 | }); 797 | 798 | } 799 | 800 | } 801 | ``` 802 | 4. Update *src/app/user-info/user-info.component.html* 803 | 804 | *user-info.component.html* 805 | ```html 806 |

Username: {{ message }}

807 | ``` 808 | 809 | ### 4.7. Configure which pages should be protected 810 | Update *src/app/app-routing.module.ts*.
811 | 812 | *app-routing.module.ts* 813 | ```typescript 814 | import { NgModule } from '@angular/core'; 815 | import { CommonModule } from '@angular/common'; 816 | import {RouterModule, Routes} from "@angular/router"; 817 | import {AccessDeniedComponent} from "./access-denied/access-denied.component"; 818 | import {AuthGuard} from "./auth/auth.guard"; 819 | import {UserInfoComponent} from "./user-info/user-info.component"; 820 | 821 | const routes: Routes = [ 822 | { 823 | path: 'access-denied', 824 | component: AccessDeniedComponent, 825 | canActivate: [AuthGuard] 826 | }, 827 | { 828 | path: 'user-info', 829 | component: UserInfoComponent, 830 | canActivate: [AuthGuard], 831 | // The user need to have these roles to access page 832 | data: { roles: ['user'] } 833 | } 834 | ]; 835 | 836 | @NgModule({ 837 | imports: [RouterModule.forRoot(routes)], 838 | exports: [RouterModule] 839 | }) 840 | export class AppRoutingModule { } 841 | ``` 842 | 843 | ### 4.8. Update main page to show load components 844 | Update *src/app/app.component.ts* and *src/app/app.component.html*.
845 | 846 | *app.component.ts* 847 | ```typescript 848 | import { Component } from '@angular/core'; 849 | import {AuthService} from "./auth/auth.service"; 850 | 851 | @Component({ 852 | selector: 'app-root', 853 | templateUrl: './app.component.html', 854 | styleUrls: ['./app.component.css'] 855 | }) 856 | export class AppComponent { 857 | title = 'angular-app'; 858 | 859 | constructor(private authService: AuthService) { 860 | } 861 | 862 | public logout() { 863 | this.authService.logout(); 864 | } 865 | 866 | } 867 | ``` 868 | 869 | *app.component.html* 870 | ```html 871 | 872 | 873 | 874 | ``` 875 | 876 | ## 5. That's it 877 | Start Spring Boot and Angular. Go to http://localhost:4200 and you sould see Keycloak login/register page. 878 | -------------------------------------------------------------------------------- /angular-app/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | -------------------------------------------------------------------------------- /angular-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /angular-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /angular-app/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /angular-app/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "pwa-chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /angular-app/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /angular-app/README.md: -------------------------------------------------------------------------------- 1 | # AngularApp 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 14.2.9. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /angular-app/angular-app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /angular-app/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-app": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/angular-app", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "budgets": [ 33 | { 34 | "type": "initial", 35 | "maximumWarning": "500kb", 36 | "maximumError": "1mb" 37 | }, 38 | { 39 | "type": "anyComponentStyle", 40 | "maximumWarning": "2kb", 41 | "maximumError": "4kb" 42 | } 43 | ], 44 | "fileReplacements": [ 45 | { 46 | "replace": "src/environments/environment.ts", 47 | "with": "src/environments/environment.prod.ts" 48 | } 49 | ], 50 | "outputHashing": "all" 51 | }, 52 | "development": { 53 | "buildOptimizer": false, 54 | "optimization": false, 55 | "vendorChunk": true, 56 | "extractLicenses": false, 57 | "sourceMap": true, 58 | "namedChunks": true 59 | } 60 | }, 61 | "defaultConfiguration": "production" 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "configurations": { 66 | "production": { 67 | "browserTarget": "angular-app:build:production" 68 | }, 69 | "development": { 70 | "browserTarget": "angular-app:build:development" 71 | } 72 | }, 73 | "defaultConfiguration": "development" 74 | }, 75 | "extract-i18n": { 76 | "builder": "@angular-devkit/build-angular:extract-i18n", 77 | "options": { 78 | "browserTarget": "angular-app:build" 79 | } 80 | }, 81 | "test": { 82 | "builder": "@angular-devkit/build-angular:karma", 83 | "options": { 84 | "main": "src/test.ts", 85 | "polyfills": "src/polyfills.ts", 86 | "tsConfig": "tsconfig.spec.json", 87 | "karmaConfig": "karma.conf.js", 88 | "assets": [ 89 | "src/favicon.ico", 90 | "src/assets" 91 | ], 92 | "styles": [ 93 | "src/styles.css" 94 | ], 95 | "scripts": [] 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /angular-app/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/angular-app'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /angular-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-app", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve --proxy-config proxy.conf.json", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "-": "^0.0.1", 14 | "@angular/animations": "^14.2.0", 15 | "@angular/common": "^14.2.0", 16 | "@angular/compiler": "^14.2.0", 17 | "@angular/core": "^14.2.0", 18 | "@angular/forms": "^14.2.0", 19 | "@angular/platform-browser": "^14.2.0", 20 | "@angular/platform-browser-dynamic": "^14.2.0", 21 | "@angular/router": "^14.2.0", 22 | "keycloak-angular": "^12.1.0", 23 | "keycloak-js": "^20.0.1", 24 | "rxjs": "~7.5.0", 25 | "save": "^2.9.0", 26 | "tslib": "^2.3.0", 27 | "zone.js": "~0.11.4" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^14.2.9", 31 | "@angular/cli": "~14.2.9", 32 | "@angular/compiler-cli": "^14.2.0", 33 | "@types/jasmine": "~4.0.0", 34 | "jasmine-core": "~4.3.0", 35 | "karma": "~6.4.0", 36 | "karma-chrome-launcher": "~3.1.0", 37 | "karma-coverage": "~2.2.0", 38 | "karma-jasmine": "~5.1.0", 39 | "karma-jasmine-html-reporter": "~2.0.0", 40 | "typescript": "~4.7.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /angular-app/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:8080", 4 | "secure": false, 5 | "pathRewrite": {"^/api" : ""} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /angular-app/src/app/access-denied/access-denied.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/angular-app/src/app/access-denied/access-denied.component.css -------------------------------------------------------------------------------- /angular-app/src/app/access-denied/access-denied.component.html: -------------------------------------------------------------------------------- 1 |

access-denied works!

2 | -------------------------------------------------------------------------------- /angular-app/src/app/access-denied/access-denied.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AccessDeniedComponent } from './access-denied.component'; 4 | 5 | describe('AccessDeniedComponent', () => { 6 | let component: AccessDeniedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AccessDeniedComponent ] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AccessDeniedComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /angular-app/src/app/access-denied/access-denied.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-access-denied', 5 | templateUrl: './access-denied.component.html', 6 | styleUrls: ['./access-denied.component.css'] 7 | }) 8 | export class AccessDeniedComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /angular-app/src/app/api/web-api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import {HttpClient} from "@angular/common/http"; 3 | import {environment} from "../../environments/environment"; 4 | import {Observable} from "rxjs"; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class WebApiService { 10 | 11 | constructor(private http: HttpClient) { } 12 | 13 | public getUserInfo(): Observable { 14 | return this.http.get(`${environment.apiUrl}/userInfo1`, {responseType:'text'}); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /angular-app/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import {RouterModule, Routes} from "@angular/router"; 4 | import {AccessDeniedComponent} from "./access-denied/access-denied.component"; 5 | import {AuthGuard} from "./auth/auth.guard"; 6 | import {UserInfoComponent} from "./user-info/user-info.component"; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: 'access-denied', 11 | component: AccessDeniedComponent, 12 | canActivate: [AuthGuard] 13 | }, 14 | { 15 | path: 'user-info', 16 | component: UserInfoComponent, 17 | canActivate: [AuthGuard], 18 | // The user need to have these roles to access page 19 | data: { roles: ['user'] } 20 | } 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [RouterModule.forRoot(routes)], 25 | exports: [RouterModule] 26 | }) 27 | export class AppRoutingModule { } 28 | -------------------------------------------------------------------------------- /angular-app/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/angular-app/src/app/app.component.css -------------------------------------------------------------------------------- /angular-app/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /angular-app/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | }); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'angular-app'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app.title).toEqual('angular-app'); 23 | }); 24 | 25 | it('should render title', () => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.nativeElement as HTMLElement; 29 | expect(compiled.querySelector('.content span')?.textContent).toContain('angular-app app is running!'); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /angular-app/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import {AuthService} from "./auth/auth.service"; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent { 10 | title = 'angular-app'; 11 | 12 | constructor(private authService: AuthService) { 13 | } 14 | 15 | public logout() { 16 | this.authService.logout(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /angular-app/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {APP_INITIALIZER, NgModule} from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppComponent } from './app.component'; 5 | import {initializer} from "../utils/app-init"; 6 | import {KeycloakAngularModule, KeycloakService} from "keycloak-angular"; 7 | import { AppRoutingModule } from './app-routing.module'; 8 | import { AccessDeniedComponent } from './access-denied/access-denied.component'; 9 | import { UserInfoComponent } from './user-info/user-info.component'; 10 | import {HttpClientModule} from "@angular/common/http"; 11 | 12 | @NgModule({ 13 | declarations: [ 14 | AppComponent, 15 | AccessDeniedComponent, 16 | UserInfoComponent 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | AppRoutingModule, 21 | KeycloakAngularModule, 22 | HttpClientModule 23 | ], 24 | providers: [ 25 | { 26 | provide: APP_INITIALIZER, 27 | useFactory: initializer, 28 | deps: [ KeycloakService ], 29 | multi: true 30 | } 31 | ], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class AppModule { } 35 | -------------------------------------------------------------------------------- /angular-app/src/app/auth/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@angular/core"; 2 | import {ActivatedRouteSnapshot, Router, RouterStateSnapshot} from "@angular/router"; 3 | import {KeycloakAuthGuard, KeycloakService} from "keycloak-angular"; 4 | import Keycloak from "keycloak-js"; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class AuthGuard extends KeycloakAuthGuard { 10 | 11 | constructor(protected override readonly router: Router, 12 | protected readonly keycloak: KeycloakService 13 | ) { 14 | super(router, keycloak); 15 | } 16 | 17 | public async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 18 | 19 | // If not logged, redirect to login page 20 | if (!this.authenticated) { 21 | await this.keycloak.login({ 22 | redirectUri: window.location.origin + state.url 23 | }); 24 | } 25 | 26 | // Get the roles from keycloak 27 | const keycloakRoles = this.roles; 28 | 29 | // Get the roles from app.routing.module.ts 30 | const requiredRoles = route.data['roles']; 31 | 32 | // If page doesn't need any extra roles 33 | if (!(requiredRoles instanceof Array) || requiredRoles.length == 0) { 34 | return true; 35 | } 36 | 37 | // Check whether user has role to visit page (check keycloak roles against app.routing.module.ts config) 38 | if (requiredRoles.every(role => keycloakRoles.includes(role))) { 39 | return true; 40 | } 41 | 42 | // If user doesn't have necessary roles, redirect to error page 43 | this.router.navigate(['access-denied']); 44 | return false; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /angular-app/src/app/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@angular/core"; 2 | import {KeycloakService} from "keycloak-angular"; 3 | 4 | @Injectable({ 5 | providedIn: "root" 6 | }) 7 | export class AuthService { 8 | 9 | constructor(private keycloakService: KeycloakService) { } 10 | 11 | public getUsername(): string { 12 | return this.keycloakService.getUsername(); 13 | } 14 | 15 | public logout(): void { 16 | this.keycloakService.logout().then(() => this.keycloakService.clearToken()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /angular-app/src/app/user-info/user-info.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/angular-app/src/app/user-info/user-info.component.css -------------------------------------------------------------------------------- /angular-app/src/app/user-info/user-info.component.html: -------------------------------------------------------------------------------- 1 |

Username: {{ message }}

2 | -------------------------------------------------------------------------------- /angular-app/src/app/user-info/user-info.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {HttpClient} from "@angular/common/http"; 3 | import {AuthService} from "../auth/auth.service"; 4 | import {WebApiService} from "../api/web-api.service"; 5 | 6 | @Component({ 7 | selector: 'app-user-info', 8 | templateUrl: './user-info.component.html', 9 | styleUrls: ['./user-info.component.css'] 10 | }) 11 | export class UserInfoComponent implements OnInit { 12 | 13 | message: string = 'null'; 14 | 15 | constructor(private authService: AuthService, private webApiService: WebApiService, private http: HttpClient) { } 16 | 17 | ngOnInit(): void { 18 | this.message = this.authService.getUsername(); 19 | 20 | this.webApiService.getUserInfo().subscribe({ 21 | next: data => { 22 | this.message += ", " + data; 23 | }, error: err => { 24 | console.log(err); 25 | } 26 | }); 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /angular-app/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/angular-app/src/assets/.gitkeep -------------------------------------------------------------------------------- /angular-app/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /angular-app/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | apiUrl: '/api', 8 | keycloak: { 9 | // Url of the Identity Provider 10 | issuer: 'http://localhost:8081', 11 | // Realm 12 | realm: 'test-app', 13 | clientId: 'frontend' 14 | }, 15 | }; 16 | 17 | /* 18 | * For easier debugging in development mode, you can import the following file 19 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 20 | * 21 | * This import should be commented out in production mode because it will have a negative impact 22 | * on performance if an error is thrown. 23 | */ 24 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 25 | -------------------------------------------------------------------------------- /angular-app/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/angular-app/src/favicon.ico -------------------------------------------------------------------------------- /angular-app/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularApp 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /angular-app/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /angular-app/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes recent versions of Safari, Chrome (including 12 | * Opera), Edge on the desktop, and iOS and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /angular-app/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /angular-app/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context(path: string, deep?: boolean, filter?: RegExp): { 12 | (id: string): T; 13 | keys(): string[]; 14 | }; 15 | }; 16 | 17 | // First, initialize the Angular testing environment. 18 | getTestBed().initTestEnvironment( 19 | BrowserDynamicTestingModule, 20 | platformBrowserDynamicTesting(), 21 | ); 22 | 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().forEach(context); 27 | -------------------------------------------------------------------------------- /angular-app/src/utils/app-init.ts: -------------------------------------------------------------------------------- 1 | import { KeycloakService } from "keycloak-angular"; 2 | import { environment } from "../environments/environment"; 3 | 4 | export function initializer(keycloak: KeycloakService): () => Promise { 5 | return (): Promise => { 6 | return new Promise(async (resolve, reject) => { 7 | try { 8 | await keycloak.init({ 9 | config: { 10 | url: environment.keycloak.issuer, 11 | realm: environment.keycloak.realm, 12 | clientId: environment.keycloak.clientId, 13 | }, 14 | // If set a false you cannot get any information about the user (e.g. username) 15 | loadUserProfileAtStartUp: true, 16 | initOptions: { 17 | // This is an action we specified on keycloak load 18 | // We have two options : 'login-required'|'check-sso' 19 | // If is set to 'login-required' this means your browser will do a full redirect to the Keycloak server and back to your application. 20 | // If is set to 'check-sso' instead this action will be performed in a hidden iframe, so your application resources only need to be loaded and parsed once by the browser. 21 | // Then you will need to add the silentCheckSsoRedirectUri and create a html file silent-check-sso.html with this content 22 | // 23 | // 24 | // 27 | // 28 | // 29 | onLoad: 'login-required', 30 | checkLoginIframe: true, 31 | // silentCheckSsoRedirectUri: window.location.origin + '/assets/silent-check-sso.html', 32 | }, 33 | // By default, the keycloak-angular library add 'Authorization: Bearer TOKEN' header to all http requests 34 | // Add here if u want to exclude urls (to not have that header) 35 | bearerExcludedUrls: ['/assets'] 36 | }); 37 | 38 | resolve(resolve); 39 | } catch (err) { 40 | reject(err); 41 | } 42 | }); 43 | }; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /angular-app/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /angular-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "target": "es2020", 20 | "module": "es2020", 21 | "lib": [ 22 | "es2020", 23 | "dom" 24 | ] 25 | }, 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /angular-app/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | 4 | postgresql: 5 | container_name: postgres 6 | image: postgres:14.4 7 | restart: always 8 | ports: 9 | - "5432:5432" # External port access for the development profile 10 | environment: 11 | POSTGRES_PASSWORD: admin 12 | POSTGRES_USER: admin 13 | POSTGRES_DB: keycloak 14 | 15 | pgadmin: 16 | container_name: pgadmin 17 | image: dpage/pgadmin4:latest 18 | restart: always 19 | ports: 20 | - "81:80" 21 | environment: 22 | PGADMIN_DEFAULT_EMAIL: admin@admin.com 23 | PGADMIN_DEFAULT_PASSWORD: admin 24 | 25 | keycloak: 26 | image: quay.io/keycloak/keycloak:20.0.0 27 | environment: 28 | KC_DB: postgres 29 | KC_DB_URL: jdbc:postgresql://postgresql:5432/keycloak 30 | KC_DB_USERNAME: admin 31 | KC_DB_PASSWORD: admin 32 | KEYCLOAK_ADMIN: admin 33 | KEYCLOAK_ADMIN_PASSWORD: admin 34 | # Uncomment the line below if you want to specify JDBC parameters. The parameter below is just an example, and it shouldn't be used in production without knowledge. It is highly recommended that you read the PostgreSQL JDBC driver documentation in order to use it. 35 | #JDBC_PARAMS: "ssl=true" 36 | ports: 37 | - "8081:8081" 38 | command: [ "start-dev", "--http-port=8081" ] 39 | depends_on: 40 | - postgres 41 | -------------------------------------------------------------------------------- /docs/aimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg.png -------------------------------------------------------------------------------- /docs/aimg_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_1.png -------------------------------------------------------------------------------- /docs/aimg_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_10.png -------------------------------------------------------------------------------- /docs/aimg_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_11.png -------------------------------------------------------------------------------- /docs/aimg_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_12.png -------------------------------------------------------------------------------- /docs/aimg_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_13.png -------------------------------------------------------------------------------- /docs/aimg_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_14.png -------------------------------------------------------------------------------- /docs/aimg_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_2.png -------------------------------------------------------------------------------- /docs/aimg_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_3.png -------------------------------------------------------------------------------- /docs/aimg_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_4.png -------------------------------------------------------------------------------- /docs/aimg_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_5.png -------------------------------------------------------------------------------- /docs/aimg_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_6.png -------------------------------------------------------------------------------- /docs/aimg_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_7.png -------------------------------------------------------------------------------- /docs/aimg_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_8.png -------------------------------------------------------------------------------- /docs/aimg_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/aimg_9.png -------------------------------------------------------------------------------- /docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img.png -------------------------------------------------------------------------------- /docs/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_1.png -------------------------------------------------------------------------------- /docs/img_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_10.png -------------------------------------------------------------------------------- /docs/img_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_11.png -------------------------------------------------------------------------------- /docs/img_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_12.png -------------------------------------------------------------------------------- /docs/img_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_13.png -------------------------------------------------------------------------------- /docs/img_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_14.png -------------------------------------------------------------------------------- /docs/img_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_15.png -------------------------------------------------------------------------------- /docs/img_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_16.png -------------------------------------------------------------------------------- /docs/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_2.png -------------------------------------------------------------------------------- /docs/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_3.png -------------------------------------------------------------------------------- /docs/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_4.png -------------------------------------------------------------------------------- /docs/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_5.png -------------------------------------------------------------------------------- /docs/img_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_6.png -------------------------------------------------------------------------------- /docs/img_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_7.png -------------------------------------------------------------------------------- /docs/img_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_8.png -------------------------------------------------------------------------------- /docs/img_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/docs/img_9.png -------------------------------------------------------------------------------- /spring-app/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /spring-app/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/heytechv/springboot-keycloak-angular/0299b01008eafc43b952c9b6d12f27962ab2ea02/spring-app/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /spring-app/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar 3 | -------------------------------------------------------------------------------- /spring-app/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /spring-app/mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /spring-app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.5 9 | 10 | 11 | com.example 12 | spring-app 13 | 0.0.1-SNAPSHOT 14 | spring-app 15 | spring-app 16 | 17 | 11 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-data-jpa 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-security 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-validation 31 | 32 | 33 | 34 | org.postgresql 35 | postgresql 36 | runtime 37 | 38 | 39 | org.projectlombok 40 | lombok 41 | true 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-starter-test 46 | test 47 | 48 | 49 | org.springframework.security 50 | spring-security-test 51 | test 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-web 56 | 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-oauth2-resource-server 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-maven-plugin 70 | 71 | 72 | 73 | org.projectlombok 74 | lombok 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/SpringAppApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringAppApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringAppApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/controller/WebController.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp.controller; 2 | 3 | import com.example.springapp.entity.User; 4 | import com.example.springapp.service.UserService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 9 | import org.springframework.web.bind.annotation.CrossOrigin; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | 14 | @RestController 15 | @CrossOrigin(origins = "http://localhost:4200") 16 | @RequiredArgsConstructor 17 | public class WebController { 18 | 19 | private static final Logger logger = LoggerFactory.getLogger(WebController.class); 20 | 21 | private final UserService userService; 22 | 23 | @GetMapping(path = "/userInfo1") 24 | public String getUserInfo1() { 25 | User user = userService.getLoggedUser(); 26 | 27 | return "UserInfo1: " + user.getFirstname() + " " + user.getLastname() + ", " + user.getEmail(); 28 | } 29 | 30 | @GetMapping("/userInfo2") 31 | public String getUserInfo2(JwtAuthenticationToken auth) { 32 | String firstname = auth.getTokenAttributes().get("given_name").toString(); 33 | String lastname = auth.getTokenAttributes().get("family_name").toString(); 34 | String email = auth.getTokenAttributes().get("email").toString(); 35 | String authorities = auth.getAuthorities().toString(); 36 | 37 | return "UserInfo2: " + firstname + " " + lastname + ", " + email + ", " + authorities ; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Entity 8 | @Table(name = "USER_") 9 | @Setter @Getter 10 | @Builder 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class User { 14 | 15 | @Id 16 | @Column 17 | private String email; 18 | 19 | @Column 20 | private String firstname; 21 | 22 | @Column 23 | private String lastname; 24 | 25 | @Column 26 | @Enumerated(EnumType.STRING) 27 | private Gender gender; 28 | 29 | 30 | public enum Gender { 31 | MALE, FEMALE 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp.repository; 2 | 3 | import com.example.springapp.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface UserRepository extends JpaRepository { 11 | 12 | Optional findByEmail(String email); 13 | boolean existsByEmail(String email); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/security/JwtUserSyncFilter.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp.security; 2 | 3 | import com.example.springapp.entity.User; 4 | import com.example.springapp.service.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 8 | import org.springframework.web.filter.OncePerRequestFilter; 9 | 10 | import javax.servlet.FilterChain; 11 | import javax.servlet.ServletException; 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | 16 | 17 | public class JwtUserSyncFilter extends OncePerRequestFilter { 18 | 19 | @Autowired 20 | private UserService userService; 21 | 22 | @Override 23 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { 24 | 25 | try { 26 | JwtAuthenticationToken token = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); 27 | 28 | String firstname = String.valueOf(token.getTokenAttributes().get("given_name")); 29 | String lastname = String.valueOf(token.getTokenAttributes().get("family_name")); 30 | String email = String.valueOf(token.getTokenAttributes().get("email")); 31 | User.Gender gender = token.getTokenAttributes().get("gender") == null ? null : 32 | User.Gender.valueOf(String.valueOf(token.getTokenAttributes().get("gender")).toUpperCase()); 33 | 34 | User user = User.builder() 35 | .firstname(firstname) 36 | .lastname(lastname) 37 | .email(email) 38 | .gender(gender) 39 | .build(); 40 | 41 | userService.syncUser(user); 42 | } catch (Exception e) { 43 | throw new IllegalArgumentException("Unable to auth user"); 44 | } 45 | 46 | filterChain.doFilter(request, response); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp.security; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.convert.converter.Converter; 8 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 9 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 10 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 11 | import org.springframework.security.config.http.SessionCreationPolicy; 12 | import org.springframework.security.core.GrantedAuthority; 13 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 14 | import org.springframework.security.oauth2.jwt.Jwt; 15 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; 16 | import org.springframework.security.web.SecurityFilterChain; 17 | import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; 18 | 19 | import java.util.Collection; 20 | import java.util.Map; 21 | import java.util.stream.Collectors; 22 | 23 | @Configuration 24 | @EnableWebSecurity 25 | @EnableGlobalMethodSecurity(prePostEnabled = true) 26 | @RequiredArgsConstructor 27 | @Slf4j 28 | public class SecurityConfig { 29 | 30 | /** 31 | * Map Keycloak roles (REALM and CLIENT level) to get them all 32 | * */ 33 | @Bean 34 | public JwtAuthenticationConverter jwtAuthenticationConverterForKeycloak() { 35 | 36 | log.warn("CONVERT"); 37 | 38 | Converter> jwtGrantedAuthoritiesConverter = jwt -> { 39 | Map> realmAccess = jwt.getClaim("realm_access"); 40 | Collection roles = realmAccess.get("roles"); 41 | return roles.stream() 42 | .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) 43 | .collect(Collectors.toList()); 44 | }; 45 | 46 | var jwtAuthenticationConverter = new JwtAuthenticationConverter(); 47 | jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); 48 | 49 | return jwtAuthenticationConverter; 50 | } 51 | 52 | /** 53 | * Sync local minimal user database with Keycloak db 54 | * */ 55 | @Bean 56 | public JwtUserSyncFilter jwtAuthUserFilterBean() { 57 | return new JwtUserSyncFilter(); 58 | } 59 | 60 | /** 61 | * Configure security 62 | * */ 63 | @Bean 64 | public SecurityFilterChain configure(HttpSecurity http) throws Exception { 65 | http.csrf().disable(); 66 | http.cors().disable(); 67 | http.anonymous().disable(); 68 | 69 | http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); 70 | http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverterForKeycloak()); 71 | 72 | http.authorizeRequests() 73 | .anyRequest() 74 | .hasRole("user"); 75 | 76 | http.addFilterAfter(jwtAuthUserFilterBean(), SwitchUserFilter.class); 77 | 78 | return http.build(); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /spring-app/src/main/java/com/example/springapp/service/UserService.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp.service; 2 | 3 | import com.example.springapp.entity.User; 4 | import com.example.springapp.repository.UserRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 8 | import org.springframework.stereotype.Service; 9 | 10 | import javax.persistence.EntityNotFoundException; 11 | import java.util.Optional; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class UserService { 16 | 17 | private final UserRepository userRepository; 18 | 19 | public User getLoggedUser() { 20 | // nie uzywa sie UserDetails 21 | // https://stackoverflow.com/questions/64514346/spring-boot-oauth2-resource-server-userdetailsservice 22 | // wiec lepiej chyba to z hasId 23 | // https://stackoverflow.com/questions/62662036/how-do-i-configure-preauthorize-to-recognize-the-id-of-my-logged-in-user 24 | 25 | JwtAuthenticationToken token = (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); 26 | 27 | String email = String.valueOf(token.getTokenAttributes().get("email")); 28 | User user = userRepository.findByEmail(email).orElseThrow(() -> new EntityNotFoundException("Error while fetching user")); 29 | 30 | return user; 31 | } 32 | 33 | public void syncUser(User user) { 34 | if (user == null) { 35 | throw new EntityNotFoundException("Error while user sync"); 36 | } 37 | 38 | User saveUser = user; 39 | Optional optionalUser = userRepository.findByEmail(user.getEmail()); 40 | 41 | if (optionalUser.isPresent()) { 42 | saveUser = optionalUser.get(); 43 | saveUser.setFirstname(user.getFirstname()); 44 | saveUser.setLastname(user.getLastname()); 45 | } 46 | 47 | userRepository.save(saveUser); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /spring-app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # -- Spring Boot -- 2 | server.port=8080 3 | 4 | # -- Postgresql database -- 5 | spring.datasource.url=jdbc:postgresql://localhost:5432/ewizitingDB 6 | spring.datasource.username=admin 7 | spring.datasource.password=admin 8 | spring.jpa.hibernate.ddl-auto=create 9 | 10 | # -- Keycloak -- 11 | spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/test-app 12 | -------------------------------------------------------------------------------- /spring-app/src/test/java/com/example/springapp/SpringAppApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.springapp; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringAppApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /springboot-keycloak-angular.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | --------------------------------------------------------------------------------