feature/test (#1)
Reviewed-on: #1 Co-authored-by: Bernhard Müller <postmuller@gmail.com> Co-committed-by: Bernhard Müller <postmuller@gmail.com>
This commit is contained in:
parent
83b6d503cc
commit
95051690d5
7 changed files with 57 additions and 23 deletions
12
Dockerfile
12
Dockerfile
|
|
@ -20,7 +20,11 @@ COPY eeg_frontend/openapi.yaml ./
|
||||||
RUN npx openapi-generator-cli generate -i ./openapi.yaml -g typescript-angular -o ./src/app/api
|
RUN npx openapi-generator-cli generate -i ./openapi.yaml -g typescript-angular -o ./src/app/api
|
||||||
|
|
||||||
COPY eeg_frontend/ ./
|
COPY eeg_frontend/ ./
|
||||||
RUN npm run build -- --configuration production
|
ARG VITE_OR_ANGULAR_BACKEND_URL
|
||||||
|
ENV BACKEND_URL=$VITE_OR_ANGULAR_BACKEND_URL
|
||||||
|
RUN sed -i "s|https://API_URL_PLACEHOLDER|${BACKEND_URL}|g" src/environments/environment.staging.ts
|
||||||
|
|
||||||
|
RUN npm run build -- --configuration staging
|
||||||
|
|
||||||
# ---- Stage 2: Build Spring Boot Backend ----
|
# ---- Stage 2: Build Spring Boot Backend ----
|
||||||
FROM maven:3.9-eclipse-temurin-21 AS backend-build
|
FROM maven:3.9-eclipse-temurin-21 AS backend-build
|
||||||
|
|
@ -65,12 +69,8 @@ USER eeg
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
|
|
||||||
CMD curl -f http://localhost:8080/actuator/health || exit 1
|
|
||||||
|
|
||||||
ENTRYPOINT ["java", \
|
ENTRYPOINT ["java", \
|
||||||
"-XX:+UseContainerSupport", \
|
"-XX:+UseContainerSupport", \
|
||||||
"-XX:MaxRAMPercentage=75.0", \
|
"-XX:MaxRAMPercentage=75.0", \
|
||||||
"-Djava.security.egd=file:/dev/./urandom", \
|
"-Djava.security.egd=file:/dev/./urandom", \
|
||||||
"-jar", "app.jar", \
|
"-jar", "app.jar"]
|
||||||
"--spring.profiles.active=prod"]
|
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,4 @@ foreach ($f in $Files) {
|
||||||
"`n" | Out-File -Append $OutFile -Encoding UTF8
|
"`n" | Out-File -Append $OutFile -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Erfolgreich! Der Kontext für Backend und Frontend wurde in '$OutFile' gespeichert."
|
Write-Host "Erfolgreich! Der Kontext für Backend und Frontend wurde in '$OutFile' gespeichert..."
|
||||||
|
|
@ -3,9 +3,13 @@ package at.mueller.eeg.backend.iam.config;
|
||||||
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
||||||
import com.nimbusds.jose.jwk.source.ImmutableSecret;
|
import com.nimbusds.jose.jwk.source.ImmutableSecret;
|
||||||
import jakarta.servlet.DispatcherType;
|
import jakarta.servlet.DispatcherType;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
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.http.HttpMethod;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
|
@ -23,23 +27,31 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.web.HeaderBearerTokenResolver;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.CorsConfigurationSource;
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity
|
@EnableMethodSecurity
|
||||||
|
@ConfigurationProperties(prefix = "app")
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
@Value("${jwt.secret}")
|
@Value("${jwt.secret}")
|
||||||
private String jwtSecret;
|
private String jwtSecret;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
private List<String> corsOrigins = new ArrayList<>();
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public JwtDecoder jwtDecoder() {
|
public JwtDecoder jwtDecoder() {
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
|
SecretKeySpec secretKey = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
|
||||||
|
|
@ -54,6 +66,7 @@ public class SecurityConfig {
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
|
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
|
||||||
|
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||||
.requestMatchers("/", "/index.html", "/assets/**", "/static/**", "/*.js", "/*.css", "/*.ico").permitAll()
|
.requestMatchers("/", "/index.html", "/assets/**", "/static/**", "/*.js", "/*.css", "/*.ico").permitAll()
|
||||||
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||||
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
|
.requestMatchers("/actuator/health", "/actuator/info").permitAll()
|
||||||
|
|
@ -62,6 +75,7 @@ public class SecurityConfig {
|
||||||
.anyRequest().denyAll()
|
.anyRequest().denyAll()
|
||||||
)
|
)
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
|
.bearerTokenResolver(new HeaderBearerTokenResolver("X-Access-Token"))
|
||||||
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
|
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -76,9 +90,7 @@ public class SecurityConfig {
|
||||||
@Bean
|
@Bean
|
||||||
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
public JwtAuthenticationConverter jwtAuthenticationConverter() {
|
||||||
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||||
// Hier sagst du Spring, wie das Feld in deinem JWT-Payload heißt (z.B. "role" oder "roles")
|
|
||||||
grantedAuthoritiesConverter.setAuthoritiesClaimName("role");
|
grantedAuthoritiesConverter.setAuthoritiesClaimName("role");
|
||||||
// Spring Security erwartet bei Rollen immer das Prefix "ROLE_"
|
|
||||||
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
|
grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
|
||||||
|
|
||||||
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||||
|
|
@ -86,19 +98,26 @@ public class SecurityConfig {
|
||||||
return jwtAuthenticationConverter;
|
return jwtAuthenticationConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Value("${app.cors-origins:http://localhost:4200}")
|
|
||||||
private String corsOrigins;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public CorsConfigurationSource corsConfigurationSource() {
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
CorsConfiguration configuration = new CorsConfiguration();
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
configuration.setAllowedOrigins(Arrays.stream(corsOrigins.split(",")).map(String::trim).toList());
|
List<String> allowedPatterns = corsOrigins.stream()
|
||||||
|
.map(String::trim)
|
||||||
|
.flatMap(origin -> {
|
||||||
|
if (origin.contains("*")) {
|
||||||
|
return Stream.of(origin, origin + ":*");
|
||||||
|
}
|
||||||
|
return Stream.of(origin);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
configuration.setAllowedOriginPatterns(allowedPatterns);
|
||||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||||
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
|
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Access-Token"));
|
||||||
configuration.setAllowCredentials(true);
|
configuration.setAllowCredentials(true);
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
source.registerCorsConfiguration("/**", configuration);
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
System.out.println("LOGGED CORS ORIGINS: " + String.join(",", corsOrigins));
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,7 +131,6 @@ public class SecurityConfig {
|
||||||
return config.getAuthenticationManager();
|
return config.getAuthenticationManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Das Gegenstück zum Decoder: Hiermit erstellen wir die Token!
|
|
||||||
@Bean
|
@Bean
|
||||||
public JwtEncoder jwtEncoder() {
|
public JwtEncoder jwtEncoder() {
|
||||||
SecretKeySpec secretKey = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
|
SecretKeySpec secretKey = new SecretKeySpec(jwtSecret.getBytes(), "HmacSHA256");
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ spring:
|
||||||
jwt:
|
jwt:
|
||||||
secret: dev-only-secret-do-not-use-in-production-2025
|
secret: dev-only-secret-do-not-use-in-production-2025
|
||||||
app:
|
app:
|
||||||
cors-origins: http://localhost:4200
|
cors-origins:
|
||||||
|
- http://localhost:4200
|
||||||
|
- https://eeg.mueller-dev.com
|
||||||
|
- https://*.eeg.mueller-dev.com
|
||||||
eda:
|
eda:
|
||||||
simulation:
|
simulation:
|
||||||
consent-delay-ms: 3000
|
consent-delay-ms: 3000
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,15 @@
|
||||||
],
|
],
|
||||||
"outputHashing": "all"
|
"outputHashing": "all"
|
||||||
},
|
},
|
||||||
|
"staging": {
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.staging.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"optimization": false,
|
"optimization": false,
|
||||||
"extractLicenses": false,
|
"extractLicenses": false,
|
||||||
|
|
@ -62,6 +71,9 @@
|
||||||
"production": {
|
"production": {
|
||||||
"buildTarget": "eeg_frontend:build:production"
|
"buildTarget": "eeg_frontend:build:production"
|
||||||
},
|
},
|
||||||
|
"staging": {
|
||||||
|
"buildTarget": "eeg_frontend:build:staging"
|
||||||
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"buildTarget": "eeg_frontend:build:development"
|
"buildTarget": "eeg_frontend:build:development"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,11 @@ import {HttpInterceptorFn} from '@angular/common/http';
|
||||||
export const jwtInterceptor: HttpInterceptorFn = (req, next) => {
|
export const jwtInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
// If a token exists, clone the request and add the Authorization header
|
|
||||||
if (token) {
|
if (token) {
|
||||||
const clonedRequest = req.clone({
|
const authReq = req.clone({
|
||||||
setHeaders: {
|
headers: req.headers.set('X-Access-Token', token)
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return next(clonedRequest);
|
return next(authReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(req);
|
return next(req);
|
||||||
|
|
|
||||||
4
eeg_frontend/src/environments/environment.staging.ts
Normal file
4
eeg_frontend/src/environments/environment.staging.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const environment = {
|
||||||
|
production: true,
|
||||||
|
apiUrl: 'https://API_URL_PLACEHOLDER'
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue