Initial commit with basic CRUD functionality:
* GET all properties * GET one property by id * CREATE one property * DELETE one property by id
This commit is contained in:
48
bruno/propify/Authenticate/Auth.bru
Normal file
48
bruno/propify/Authenticate/Auth.bru
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
meta {
|
||||||
|
name: Auth
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{KEYCLOAK_BASE_URL}}/realms/propify/protocol/openid-connect/token
|
||||||
|
body: formUrlEncoded
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
}
|
||||||
|
|
||||||
|
body:form-urlencoded {
|
||||||
|
grant_type: password
|
||||||
|
client_id: {{KEYCLOAK_CLIENT_ID}}
|
||||||
|
username: {{ADMIN_USERNAME}}
|
||||||
|
password: {{ADMIN_PASSWORD}}
|
||||||
|
}
|
||||||
|
|
||||||
|
script:post-response {
|
||||||
|
// Parse die JSON-Antwort
|
||||||
|
const jsonResponse = res.body;
|
||||||
|
|
||||||
|
|
||||||
|
if (jsonResponse.access_token) {
|
||||||
|
// Schreibe den access_token in eine Umgebungsvariable
|
||||||
|
// oder in eine collection-Variable
|
||||||
|
|
||||||
|
// Option 1: In eine Umgebungsvariable schreiben
|
||||||
|
// (z.B. für eine bestimmte Umgebung wie "Development")
|
||||||
|
bru.setEnvVar("BEARER_TOKEN", jsonResponse.access_token);
|
||||||
|
|
||||||
|
// Option 2: In eine Collection-Variable schreiben
|
||||||
|
// (Diese Variable ist global für alle Anfragen in deiner Collection)
|
||||||
|
// bru.setVar("bearerToken", "Bearer " + jsonResponse.access_token);
|
||||||
|
} else {
|
||||||
|
// optional: Error Handling, falls der Token nicht in der Antwort ist
|
||||||
|
console.log("Error: access_token not found in the response.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: false
|
||||||
|
}
|
||||||
8
bruno/propify/Authenticate/folder.bru
Normal file
8
bruno/propify/Authenticate/folder.bru
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
meta {
|
||||||
|
name: Authenticate
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
auth {
|
||||||
|
mode: inherit
|
||||||
|
}
|
||||||
@@ -5,9 +5,13 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{BASE_URL}}/api/{{API_VERSION}}/properties
|
url: {{API_BASE_URL}}/api/{{API_VERSION}}/properties
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{BEARER_TOKEN}}
|
||||||
}
|
}
|
||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete {
|
delete {
|
||||||
url: {{BASE_URL}}/api/{{API_VERSION}}/properties/e64d5e53-cd45-45fb-9237-46078077bf22
|
url: API_BASE_URL}}/api/{{API_VERSION}}/properties/e64d5e53-cd45-45fb-9237-46078077bf22
|
||||||
body: json
|
body: json
|
||||||
auth: inherit
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{BEARER_TOKEN}}
|
||||||
}
|
}
|
||||||
|
|
||||||
body:json {
|
body:json {
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
url: {{BASE_URL}}/api/{{API_VERSION}}/properties
|
url: {{API_BASE_URL}}/api/{{API_VERSION}}/properties
|
||||||
body: none
|
body: none
|
||||||
auth: inherit
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{BEARER_TOKEN}}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -5,9 +5,13 @@ meta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get {
|
get {
|
||||||
url: {{BASE_URL}}/api/{{API_VERSION}}/properties/b6ab79d8-f7eb-4fb7-acde-e3310cb0166c
|
url: {{API_BASE_URL}}/api/{{API_VERSION}}/properties/5dba067e-d7fd-4d79-a08a-ec379834938a
|
||||||
body: none
|
body: none
|
||||||
auth: inherit
|
auth: bearer
|
||||||
|
}
|
||||||
|
|
||||||
|
auth:bearer {
|
||||||
|
token: {{BEARER_TOKEN}}
|
||||||
}
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
vars {
|
vars {
|
||||||
BASE_URL: http://localhost:8080
|
API_BASE_URL: http://localhost:8080
|
||||||
API_VERSION: v1
|
API_VERSION: v1
|
||||||
|
BEARER_TOKEN: -
|
||||||
|
KEYCLOAK_BASE_URL: http://localhost:8280
|
||||||
|
DEV_USERNAME: dev@example.com
|
||||||
|
DEV_PASSWORD: dev
|
||||||
|
ADMIN_USERNAME: admin@example.com
|
||||||
|
ADMIN_PASSWORD: admin
|
||||||
|
KEYCLOAK_CLIENT_ID: propify-app
|
||||||
}
|
}
|
||||||
|
|||||||
9
pom.xml
9
pom.xml
@@ -75,6 +75,15 @@
|
|||||||
<version>2.6.0</version>
|
<version>2.6.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.iwomm.propify_api.controller;
|
|||||||
import de.iwomm.propify_api.entity.Property;
|
import de.iwomm.propify_api.entity.Property;
|
||||||
import de.iwomm.propify_api.service.PropertyService;
|
import de.iwomm.propify_api.service.PropertyService;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -18,24 +19,34 @@ public class PropertyController {
|
|||||||
this.propertyService = propertyService;
|
this.propertyService = propertyService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/info")
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
public String infoEndpoint() {
|
||||||
|
return "Hello, you are authenticated!";
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
|
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
|
||||||
public List<Property> getAllProperties() {
|
public List<Property> getAllProperties() {
|
||||||
return propertyService.findAll();
|
return propertyService.findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
|
||||||
public Property getPropertyById(@PathVariable UUID id) {
|
public Property getPropertyById(@PathVariable UUID id) {
|
||||||
return propertyService.findById(id).orElse(null);
|
return propertyService.findById(id).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@ResponseStatus(HttpStatus.CREATED)
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public Property createProperty(@RequestBody Property property) {
|
public Property createProperty(@RequestBody Property property) {
|
||||||
return propertyService.save(property);
|
return propertyService.save(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{id}")
|
@DeleteMapping("/{id}")
|
||||||
@ResponseStatus(HttpStatus.NO_CONTENT)
|
@ResponseStatus(HttpStatus.NO_CONTENT)
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public void deleteProperty(@PathVariable UUID id) {
|
public void deleteProperty(@PathVariable UUID id) {
|
||||||
Optional<Property> property = propertyService.findById(id);
|
Optional<Property> property = propertyService.findById(id);
|
||||||
if (property.isEmpty()) {
|
if (property.isEmpty()) {
|
||||||
|
|||||||
28
src/main/java/de/iwomm/propify_api/security/CorsConfig.java
Normal file
28
src/main/java/de/iwomm/propify_api/security/CorsConfig.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package de.iwomm.propify_api.security;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class CorsConfig implements WebMvcConfigurer {
|
||||||
|
private final CorsProperties corsProperties;
|
||||||
|
|
||||||
|
public CorsConfig(CorsProperties corsProperties) {
|
||||||
|
this.corsProperties = corsProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**") // Apply rules to all endpoints
|
||||||
|
.allowedOrigins(corsProperties.getAllowedOrigins().toArray(new String[0])) // This targets the frontend app's URLs (you can allow multiple URLs, e.g. "http://localhost:4200,http://example.com"
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||||
|
.allowedHeaders(corsProperties.getAllowedHeaders())
|
||||||
|
.allowCredentials(true); // Allow cookies and authentication headers
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package de.iwomm.propify_api.security;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties(prefix = "cors")
|
||||||
|
public class CorsProperties {
|
||||||
|
private List<String> allowedOrigins;
|
||||||
|
private String allowedHeaders;
|
||||||
|
|
||||||
|
public List<String> getAllowedOrigins() {
|
||||||
|
return allowedOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedOrigins(List<String> allowedOrigins) {
|
||||||
|
this.allowedOrigins = allowedOrigins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAllowedHeaders() {
|
||||||
|
return allowedHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllowedHeaders(String allowedHeaders) {
|
||||||
|
this.allowedHeaders = allowedHeaders;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package de.iwomm.propify_api.security;
|
||||||
|
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class KeycloakRoleConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<GrantedAuthority> convert(Jwt jwt) {
|
||||||
|
// Holen Sie sich das "realm_access" Feld aus dem Token
|
||||||
|
Map<String, Object> realmAccess = (Map<String, Object>) jwt.getClaims().get("realm_access");
|
||||||
|
|
||||||
|
if (realmAccess == null || realmAccess.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holen Sie sich die Liste der Rollen
|
||||||
|
List<String> roles = (List<String>) realmAccess.get("roles");
|
||||||
|
|
||||||
|
// Konvertieren Sie die Rollen in Spring Security GrantedAuthority-Objekte
|
||||||
|
return roles.stream()
|
||||||
|
.map(roleName -> "ROLE_" + roleName.toUpperCase()) // Empfohlene Namenskonvention
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,27 +2,44 @@ package de.iwomm.propify_api.security;
|
|||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity // Wichtig für die Verwendung von @PreAuthorize
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(csrf -> csrf.disable()) // CSRF deaktivieren für APIs
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.cors(cors -> {})
|
||||||
.requestMatchers("/api/**").permitAll() // API ist offen
|
|
||||||
.anyRequest().permitAll() // Alles andere auch offen
|
.authorizeHttpRequests(authorize -> authorize
|
||||||
|
.requestMatchers("/api/**").authenticated()
|
||||||
|
.anyRequest().permitAll()
|
||||||
)
|
)
|
||||||
.httpBasic(httpBasic -> httpBasic.disable()) // Kein Basic Auth
|
|
||||||
.formLogin(form -> form.disable()); // Kein Login-Formular
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
|
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
|
||||||
|
)
|
||||||
|
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
|
||||||
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Konvertiert die Keycloak-Rollen (im JWT) in Spring Security Authorities
|
||||||
|
private JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||||
|
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||||
|
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter());
|
||||||
|
return jwtAuthenticationConverter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,3 +13,20 @@ spring:
|
|||||||
h2:
|
h2:
|
||||||
console:
|
console:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
resourceserver:
|
||||||
|
jwt:
|
||||||
|
issuer-uri: http://localhost:8280/realms/propify
|
||||||
|
jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org:
|
||||||
|
springframework:
|
||||||
|
security: DEBUG
|
||||||
|
|
||||||
|
cors:
|
||||||
|
allowed-origins:
|
||||||
|
- http://localhost:4200
|
||||||
|
- http://localhost:8080
|
||||||
|
allowed-headers: "*"
|
||||||
Reference in New Issue
Block a user