Public Access
Initial commit
This commit is contained in:
commit
a941000425
8 files changed
+433
No files matched your search
@@ -0,0 +1,12 @@
|
||||
package core_service;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class CoreServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(CoreServiceApplication.class, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package core_service.configuration.security;
|
||||
|
||||
import core_service.shared.security.UserPrincipal;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtService jwtService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain
|
||||
) throws ServletException, IOException {
|
||||
|
||||
final String authHeader = request.getHeader("Authorization");
|
||||
|
||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
final String jwt = authHeader.substring(7);
|
||||
if (!jwtService.isTokenValid(jwt)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final String userEmail = jwtService.extractEmail(jwt);
|
||||
final String userId = jwtService.extractUserId(jwt);
|
||||
|
||||
if (userEmail != null && userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
|
||||
UserPrincipal principal = new UserPrincipal(userId, userEmail);
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||
principal,
|
||||
null,
|
||||
Collections.emptyList()
|
||||
);
|
||||
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("JWT Validation failed: {}", e.getMessage());
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package core_service.configuration.security;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "security.jwt")
|
||||
public class JwtConfig {
|
||||
|
||||
private String secret;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package core_service.configuration.security;
|
||||
|
||||
import core_service.shared.security.UserPrincipal;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
private final JwtConfig jwtConfig;
|
||||
|
||||
public JwtService(JwtConfig jwtConfig) {
|
||||
this.jwtConfig = jwtConfig;
|
||||
}
|
||||
|
||||
public String extractEmail(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload()
|
||||
.getSubject();
|
||||
}
|
||||
|
||||
public String extractUserId(String token) {
|
||||
return Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload()
|
||||
.get("userId", String.class);
|
||||
}
|
||||
|
||||
public boolean isTokenValid(String token) {
|
||||
try {
|
||||
Jwts.parser()
|
||||
.verifyWith(getSigningKey())
|
||||
.build()
|
||||
.parseSignedClaims(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey getSigningKey() {
|
||||
String secretKey = jwtConfig.getSecret();
|
||||
byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package core_service.configuration.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private static final String[] WHITE_LIST_URLS = {
|
||||
"/internal/**", // Allow internal service communication
|
||||
"/actuator/health",
|
||||
"/actuator/prometheus",
|
||||
"/error"
|
||||
};
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||
|
||||
@Value("${app.cors.allowed-origins}")
|
||||
private String[] allowedOrigins;
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList(allowedOrigins));
|
||||
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setAllowCredentials(false);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(WHITE_LIST_URLS).permitAll()
|
||||
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package core_service.shared.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
public record UserPrincipal(
|
||||
String id,
|
||||
String email
|
||||
) implements Principal {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public long getIdAsLong() {
|
||||
return Long.parseLong(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
spring:
|
||||
application:
|
||||
name: tbp-core-service
|
||||
profiles:
|
||||
active: prod
|
||||
datasource:
|
||||
url: jdbc:postgresql://${DB_HOST}:5432/tbp_core
|
||||
username: ${DB_USER}
|
||||
password: ${DB_PASS}
|
||||
jpa:
|
||||
open-in-view: true
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
flyway:
|
||||
enabled: true
|
||||
baseline-on-migrate: true
|
||||
locations: classpath:db/migration
|
||||
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: health, prometheus, metrics
|
||||
|
||||
security:
|
||||
jwt:
|
||||
secret: ${JWT_SECRET}
|
||||
|
||||
app:
|
||||
cors:
|
||||
allowed-origins: "https://tbp.andreas-dahm.eu"
|
||||
|
||||
internal:
|
||||
api-key: ${INTERNAL_API_KEY}
|
||||
Reference in new issue
Block a user