package com.aet.timesheets.common.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.List;

@Configuration
@EnableWebSecurity
public class SecurityFilterChainConfig {
@Autowired
private JwtFilter jwtFilter;

@Value("#{'${security.exclude.paths}'.split(',')}")
private List<String> excludedPaths;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
excludedPaths = excludedPaths.stream().map(String::trim).toList();
http
.csrf(AbstractHttpConfigurer::disable)
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth.requestMatchers(excludedPaths.toArray(new String[0])).permitAll()
.anyRequest().authenticated())
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
//config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedOrigins(List.of(
"https://app.dev.timemate-nonprod.aeturnum.net",
"https://app.qa.timemate-nonprod.aeturnum.net",
"https://app.stg.timemate-nonprod.aeturnum.net",
"https://app.timemate.aeturnum.com",
"http://localhost:3000"
));

config.setAllowedMethods(List.of("GET", "POST", "DELETE", "PUT", "PATCH", "HEAD", "OPTIONS"));

config.setAllowedHeaders(List.of(
"Access-Control-Allow-Headers", "Origin", "Accept", "X-Requested-With",
"Content-Type", "Access-Control-Request-Method", "Access-Control-Request-Headers",
"Access-Control-Allow-Headers", "Authorization", "TENANT_ID"));

config.setAllowCredentials(true); // Optional: enable if cookies are used

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}


package com.aet.timesheets.common.security.config;

import com.aet.timesheets.common.dto.response.CommonErrorResponseDTO;
import com.aet.timesheets.common.exception.UnauthorizedException;
import com.aet.timesheets.common.security.service.dto.AuthUserDetails;
import com.aet.timesheets.common.security.service.impl.InternalServiceImpl;
import com.aet.timesheets.common.security.service.impl.JwtManagerServiceImpl;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;
import java.util.UUID;

import static com.aet.timesheets.common.utils.Constant.*;

@Component
@Slf4j
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtManagerServiceImpl jwtManagerService;

@Autowired
private InternalServiceImpl internalService;

@Value("#{'${security.exclude.paths}'.split(',')}")
private List<String> excludedPaths;

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
request.setAttribute(USE_LOGIN_USER_TOKEN, TRUE);
request.setAttribute(TENANT_ID, request.getHeader(TENANT_ID));
request.setAttribute(X_REQUEST_ID, request.getHeader(X_REQUEST_ID));
request.setAttribute(APP_ID, UUID.randomUUID().toString());

String path = request.getServletPath();
boolean shouldSkip = excludedPaths.stream()
.anyMatch(excludedPath -> new AntPathMatcher().match(excludedPath.trim(), path));

log.debug("JwtFilter - shouldNotFilter for path '{}': {}", path, shouldSkip);
return shouldSkip;
}

@Override
protected void doFilterInternal(
final @NotNull HttpServletRequest request,
final @NotNull HttpServletResponse response,
final @NotNull FilterChain filterChain) throws IOException {

try {
String token = extractJwt(request);
String tenantId = request.getHeader(TENANT_ID);

final AuthUserDetails authUserDetails = jwtManagerService.validateToken(token);
final UserDetails userDetails = internalService.handleAuthUserDetails(authUserDetails, tenantId);

final UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, token, userDetails.getAuthorities());

request.setAttribute(TENANT_ID, tenantId);
request.setAttribute(TOKEN, token);

authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);

log.info("Request: method=JwtFilter.doFilterInternal, requestId={}, appId={}, user={}",
request.getAttribute(X_REQUEST_ID),
request.getAttribute(APP_ID),
authUserDetails.getEmail());
filterChain.doFilter(request, response);

} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json");
CommonErrorResponseDTO repose = new CommonErrorResponseDTO();
repose.setTimestamp(System.currentTimeMillis());
repose.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
repose.setErrors(List.of("User Not Authorized.Error: " + e));
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(repose));
}
}

private String extractJwt(final HttpServletRequest request) {
final String authHeader = request.getHeader(AUTHORIZATION);
String jwt = null;

if (authHeader != null && authHeader.startsWith(BEARER)) {
jwt = authHeader.replaceFirst(BEARER, "").trim();
}

if (jwt == null || jwt.isBlank()) {
throw new UnauthorizedException("Auth Token Not Found");
}
return jwt;
}
}

..

package com.aet.timesheets.common.security.config;

import com.aet.timesheets.common.exception.InternalErrorException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

@Configuration
public class JwtManagementSecurityConfig {
private static final String INTERNAL_SIGNING_KEY_ID = "gateway-signing-key";

@Value("${jwt.signing.key.private}")
private String privateKeyPem;

@Value("${jwt.signing.key.public}")
private String publicKeyPem;

/**
* Provides access to the internal signing key for JWT processor configuration.
*
* @return The internal RSA signing key
*/
@Bean("internalSigningKey")
public RSAKey internalSigningKey() {
// Load from PEM format
final RSAPrivateKey privateKey = loadPrivateKeyFromPem(privateKeyPem);
final RSAPublicKey publicKey = loadPublicKeyFromPem(publicKeyPem);

return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(INTERNAL_SIGNING_KEY_ID)
.build();
}


/**
* In the core module extra security flow has been handled.
* Currently, this bean is used only in core module.
*
* @return Configured JWT processor for internal tokens
*/
@Bean
public ConfigurableJWTProcessor<SecurityContext> configurableJWTProcessor(
final RSAKey internalSigningKey) {
try {
final JWKSource<SecurityContext> keySource =
new ImmutableJWKSet<>(new JWKSet(internalSigningKey));

final JWSKeySelector<SecurityContext> keySelector =
new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, keySource);

final ConfigurableJWTProcessor<SecurityContext> jwtProcessor =
new DefaultJWTProcessor<>();
jwtProcessor.setJWSKeySelector(keySelector);

return jwtProcessor;
} catch (Exception e) {
throw new InternalErrorException("Failed to configure JWT processor", e);
}
}

private RSAPrivateKey loadPrivateKeyFromPem(String privateKeyPem) {
try {
final String normalizedPrivateKey = privateKeyPem.replace("\\n", "\n");

final String cleanKey = normalizedPrivateKey
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("-----BEGIN RSA PRIVATE KEY-----", "")
.replace("-----END RSA PRIVATE KEY-----", "")
.replaceAll("\\s", "");

final byte[] keyBytes = Base64.getDecoder().decode(cleanKey);
final PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InternalErrorException("Failed to load private key from PEM", e);
}
}


private RSAPublicKey loadPublicKeyFromPem(String publicKeyPem) {
try {
final String normalizedPublicKey = publicKeyPem.replace("\\n", "\n");

final String cleanKey = normalizedPublicKey
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replace("-----BEGIN RSA PUBLIC KEY-----", "")
.replace("-----END RSA PUBLIC KEY-----", "")
.replaceAll("\\s", "");

final byte[] keyBytes = Base64.getDecoder().decode(cleanKey);
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
final KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return (RSAPublicKey) keyFactory.generatePublic(keySpec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new InternalErrorException("Failed to load public key from PEM", e);
}
}
}


package com.aet.timesheets.common.security.service.dto;

import com.aet.timesheets.common.utils.enums.Region;
import com.aet.timesheets.common.utils.enums.Status;
import com.aet.timesheets.common.utils.enums.UserRole;
import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;
import java.util.List;


@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@Builder
@ToString
public class AuthUserDetails implements UserDetails {
private String username;
private String password;
private String subId;
private String id; // time mate ID
private String email;
private String firstName;
private String lastName;
private String designation;
private UserRole role;
private Status status;
private Region region;
private String azureADId;
private String tenantId;
private String tenantName;
private boolean isInternalIssuer;
// private List<String> cognitoGroups;

public AuthUserDetails(String username, String password, UserRole role) {
this.username = username;
this.password = password;
this.role = role;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(role);
}

@Override
public String getPassword() {
return password;
}

@Override
public String getUsername() {
return username;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}
package com.aet.timesheets.common.security.service.impl;


import com.aet.timesheets.common.exception.InternalErrorException;
import com.aet.timesheets.common.exception.UnauthorizedException;
import com.aet.timesheets.common.security.idp.broker.IdentityBrokerAzureAD;
import com.aet.timesheets.common.security.idp.broker.IdentityBrokerCognito;
import com.aet.timesheets.common.security.service.dto.AuthUserDetails;
import com.aet.timesheets.common.security.service.mapper.JwtTmDataMapper;
import com.aet.timesheets.common.utils.enums.UserRole;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import com.aet.timesheets.common.utils.enums.JwtValidationApproach;

import java.util.UUID;

import static com.aet.timesheets.common.utils.Constant.APP_USER_SUB_ID;

@Service
@Slf4j
public class JwtManagerServiceImpl {
private static final String INTERNAL_ISSUER = "gateway-internal";
private static final String INTERNAL_CLIENT_ID = "gateway-internal-client";
private static final AuthUserDetails APP_USER = AuthUserDetails.builder()
.subId(APP_USER_SUB_ID)
.email("app-user@aeturnum.com")
.designation("AppUser-Designation")
.firstName("AppUser-FirstName")
.lastName("AppUser-LastName")
.tenantName("AETURNUM")
.tenantId(UUID.randomUUID().toString())
.role(UserRole.ADMIN)
.build();

private String internalUserJwt = null;

@Value("${jwt.signing.key.private}")
private String privateKeyPem;

@Value("${jwt.signing.key.public}")
private String publicKeyPem;

@Value("${jwt.gatewayInternalUser.token.valid.duration}")
private int userTokenValidDuration;

@Value("${jwt.gatewayInternalAppUser.token.valid.duration}")
private int appUserTokenValidDuration;

@Autowired
private IdentityBrokerCognito identityBrokerCognito;

@Autowired
private JwtTmDataMapper jwtTmDataMapper;

@Autowired
@Qualifier("internalSigningKey")
private RSAKey internalSigningKey;

@Autowired
private IdentityBrokerAzureAD identityBrokerAzureAD;


public AuthUserDetails validateToken(final String token) {
JwtValidationApproach approach = determineJwtValidationApproach(token);

return switch (approach) {
case AZURE_AD -> identityBrokerAzureAD.validateToken(token);
case INTERNAL -> processInternalTokenAuthorization(token);
default -> identityBrokerCognito.validateToken(token);
};
}

private AuthUserDetails processInternalTokenAuthorization(final String token) {
try {
final JWKSource<SecurityContext> keySource
= new ImmutableJWKSet<>(new JWKSet(this.internalSigningKey));

final ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();

final JWSKeySelector<SecurityContext> keySelector
= new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, keySource);

jwtProcessor.setJWSKeySelector(keySelector);

final JWTClaimsSet claimsSet = jwtProcessor.process(token, null);
return jwtTmDataMapper.buildAuthUserDetailsFromJWTClaimsSet(claimsSet);
} catch (Exception e) {
throw new UnauthorizedException("Invalid internal token: " + e.getMessage());
}
}

private JwtValidationApproach determineJwtValidationApproach(String token) {
try {
final SignedJWT signedJWT = SignedJWT.parse(token);
final JWTClaimsSet claims = signedJWT.getJWTClaimsSet();

if (isAzureADToken(claims)) {
return JwtValidationApproach.AZURE_AD;
}

if (INTERNAL_ISSUER.equals(claims.getIssuer())) {
return JwtValidationApproach.INTERNAL;
}

return JwtValidationApproach.TENANT_BASED;

} catch (Exception e) {
return JwtValidationApproach.TENANT_BASED;
}
}

private boolean isAzureADToken(JWTClaimsSet claims) {
String issuer = claims.getIssuer();
return issuer != null && issuer.startsWith("https://login.microsoftonline.com/") && issuer.endsWith("/v2.0");
}

private boolean isInternalToken(String token) {
try {
final SignedJWT signedJWT = SignedJWT.parse(token);
final String issuer = signedJWT.getJWTClaimsSet().getIssuer();
return INTERNAL_ISSUER.equals(issuer);
} catch (Exception e) {
return false;
}
}

public String generateApplicationUserToken() {

if (!isAppUserTokenValid(internalUserJwt)) {
internalUserJwt = generateGatewayToken(APP_USER, true);
}
return internalUserJwt;
}

private boolean isAppUserTokenValid(final String token) {
try {
if (StringUtils.isBlank(token)) {
return false;
}
processInternalTokenAuthorization(token);
return true;
} catch (Exception e) {
return false;
}
}

public String generateGatewayToken(final AuthUserDetails authUserDetails, final boolean isAppUser) {
try {
int tokenExpireDuration = isAppUser ?
appUserTokenValidDuration : userTokenValidDuration;

final RSAKey rsaKey = this.internalSigningKey;
final JWTClaimsSet claimsSet = jwtTmDataMapper.buildJWTClaimsSetForInternalUser(
authUserDetails, INTERNAL_ISSUER, INTERNAL_CLIENT_ID,
authUserDetails.getEmail(), tokenExpireDuration);

final SignedJWT signedJWT = createSignedJWT(claimsSet, rsaKey);
log.debug("Gateway generated JWT token for user email: {}", authUserDetails.getEmail());
return signedJWT.serialize();

} catch (Exception e) {
log.error("Failed to generate JWT token by gateway for user: {}", authUserDetails.getEmail(), e);
throw new InternalErrorException("JWT token generation failed", e);
}
}

private SignedJWT createSignedJWT(final JWTClaimsSet claimsSet, final RSAKey rsaKey) throws JOSEException {
final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
.keyID(rsaKey.getKeyID())
.build();

final SignedJWT signedJWT = new SignedJWT(header, claimsSet);
final RSASSASigner signer = new RSASSASigner(rsaKey);
signedJWT.sign(signer);

return signedJWT;
}

/**
* Token valid till 24 hours, But token getting refresh 20 hours
*/
@Scheduled(fixedRate = 20 * 60 * 60000)
public void refreshApplicationUserToken() {
internalUserJwt = generateGatewayToken(APP_USER, true);
}
}
package com.aet.timesheets.common.security.service.mapper;


import com.aet.timesheets.common.security.service.dto.AuthUserDetails;
import com.aet.timesheets.common.utils.enums.UserRole;
import com.nimbusds.jwt.JWTClaimsSet;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.UUID;

import static com.aet.timesheets.common.utils.Constant.*;

@Component
public class JwtTmDataMapper {
public JWTClaimsSet buildJWTClaimsSetForInternalUser(
final AuthUserDetails authUserDetails, final String issuer, final String audience,
final String subject, final int tokenValidDuration) {

final Instant now = Instant.now();
final Instant expiration = now.plus(tokenValidDuration, ChronoUnit.MINUTES);

final JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder()
.issuer(issuer)
.audience(audience)
.subject(subject)
.jwtID(UUID.randomUUID().toString())
.issueTime(Date.from(now))
.expirationTime(Date.from(expiration))
.claim("token_use", "id")
.claim(EMAIL, authUserDetails.getEmail());

// Add optional claims if present
addValueToClaims(claimsBuilder, SUB_ID, authUserDetails.getSubId());
addValueToClaims(claimsBuilder, DESIGNATION, authUserDetails.getDesignation());
addValueToClaims(claimsBuilder, GIVEN_NAME, authUserDetails.getFirstName());
addValueToClaims(claimsBuilder, FAMILY_NAME, authUserDetails.getLastName());
addValueToClaims(claimsBuilder, TENANT_ID_PROPERTY, authUserDetails.getTenantId());
addValueToClaims(claimsBuilder, TENANT_NAME, authUserDetails.getTenantName());
addValueToClaims(claimsBuilder, ROLE, authUserDetails.getRole() != null ? authUserDetails.getRole().name() : null);

return claimsBuilder.build();
}

public AuthUserDetails buildAuthUserDetailsFromJWTClaimsSet(
final JWTClaimsSet claimsSet) {

return AuthUserDetails.builder()
.subId(getClaimsData(claimsSet, SUB_ID))
.email(getClaimsData(claimsSet, EMAIL))
.firstName(getClaimsData(claimsSet, GIVEN_NAME))
.lastName(getClaimsData(claimsSet, FAMILY_NAME))
.role(UserRole.valueOf(getClaimsData(claimsSet, ROLE)))
.tenantId(getClaimsData(claimsSet, TENANT_ID_PROPERTY))
.tenantName(getClaimsData(claimsSet, TENANT_NAME))
.isInternalIssuer(true)
.build();
}

private void addValueToClaims(
final JWTClaimsSet.Builder builder, final String claimName, final String claimValue) {
if (claimValue != null && !claimValue.isBlank()) {
builder.claim(claimName, claimValue);
}
}

private String getClaimsData(final JWTClaimsSet claimsSet, final String claimKey) {
return ObjectUtils.isNotEmpty(claimsSet.getClaims().get(claimKey))
? claimsSet.getClaims().get(claimKey).toString()
: null;
}

}


package com.aet.timesheets.common.dto.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Accessors(chain = true)
@ToString
@EqualsAndHashCode
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CommonErrorResponseDTO implements Serializable {

private static final long serialVersionUID = -4237729511495857819L;

private long timestamp;

private int status;

private List<String> errors;
}
key:
private: |
-----BEGIN PRIVATE KEY-----
private_key
-----END PRIVATE KEY-----
public: |
-----BEGIN PUBLIC KEY-----
public_key
-----END PUBLIC KEY-----
public class SecurityUtils {
public static AuthUserDetails getCurrentUser(final Principal principal) {
if (principal == null) {
return null;
}
if (principal instanceof UsernamePasswordAuthenticationToken token &&
token.getPrincipal() instanceof AuthUserDetails userDetails) {
return userDetails;
}
throw new IllegalStateException("Invalid Principal type or not authenticated");
}
}


package com.aet.timesheets.common.restclient;

import com.aet.timesheets.common.context.RequestContextBridge;
import com.aet.timesheets.common.security.service.impl.JwtManagerServiceImpl;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Map;

import static com.aet.timesheets.common.utils.Constant.*;

@Service
@Getter
public final class ApiService {

@Autowired
private JwtManagerServiceImpl jwtManagerService;

private final WebClient webClient;

@Value("${timeMate.userService.basePath}")
private String userBasePath;

@Value("${timeMate.tenantService.basePath}")
private String tenantBasePath;

@Value("${timeMate.coreService.basePath}")
private String coreBasePath;

@Value("${timeMate.reportService.basePath}")
private String reportBasePath;

public ApiService() {
this.webClient = WebClient.builder().build();
}

private HttpHeaders createHeaders(Map<String, String> headerValues, boolean isInternal) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);


if (headerValues != null) {
headerValues.forEach(headers::set);
}

if (isInternal) {
final boolean useLogInUserToken = getUseLoginUserTokenFromContext();
final String token = useLogInUserToken ?
getTokenFromContext() : jwtManagerService.generateApplicationUserToken();

headers.set(AUTHORIZATION, BEARER + token);
headers.set(TENANT_ID, getTenantIdFromContext());
headers.set(X_REQUEST_ID, getRequestIdFromContext());
}

return headers;
}

private String getTokenFromContext() {
// Try WebFlux context bridge first
String token = RequestContextBridge.getToken();
if (token != null) {
return token;
}

// Fallback to MVC context
return getMvcAttribute(TOKEN);
}

private String getTenantIdFromContext() {
String tenantId = RequestContextBridge.getTenantId();
if (tenantId != null) {
return tenantId;
}

return getMvcAttribute(TENANT_ID);
}

private String getRequestIdFromContext() {
String requestId = RequestContextBridge.getRequestId();
if (requestId != null) {
return requestId;
}

return getMvcAttribute(X_REQUEST_ID);
}

private boolean getUseLoginUserTokenFromContext() {
return TRUE.equals(getMvcAttribute(USE_LOGIN_USER_TOKEN));
}

private String getMvcAttribute(String attributeName) {
try {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes()).getRequest();
return (String) request.getAttribute(attributeName);
} catch (Exception e) {
return null;
}
}

public <T> T doGet(String url, Map<String, String> headerValues, ParameterizedTypeReference<T> responseType, boolean isInternal) {
final HttpHeaders headers = createHeaders(headerValues, isInternal);
return webClient.get()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.retrieve()
.bodyToMono(responseType)
.block();
}

public <T> T doPost(String url, Map<String, String> headerValues, Object requestBody, ParameterizedTypeReference<T> responseType, boolean isInternal) {
final HttpHeaders headers = createHeaders(headerValues, isInternal);

if (requestBody != null) {
return webClient.post()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
//.headers(httpHeaders -> headerValues.forEach(headers::set))
.bodyValue(requestBody)
.retrieve()
.bodyToMono(responseType)
.block();
} else {
return webClient.post()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
//.headers(httpHeaders -> headerValues.forEach(headers::set))
.retrieve()
.bodyToMono(responseType)
.block();
}

}

public <T> T doPut(String url, Map<String, String> headerValues, Object requestBody, ParameterizedTypeReference<T> responseType, boolean isInternal) {
final HttpHeaders headers = createHeaders(headerValues, isInternal);

if (requestBody != null) {
return webClient.put()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.bodyValue(requestBody)
.retrieve()
.bodyToMono(responseType)
.block();
} else {
return webClient.put()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.retrieve()
.bodyToMono(responseType)
.block();
}
}

public <T> T doPatch(String url, Map<String, String> headerValues, Object requestBody, ParameterizedTypeReference<T> responseType, boolean isInternal) {
final HttpHeaders headers = createHeaders(headerValues, isInternal);

if (requestBody != null) {
return webClient.patch()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.bodyValue(requestBody)
.retrieve()
.bodyToMono(responseType)
.block();
} else {
return webClient.patch()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.retrieve()
.bodyToMono(responseType)
.block();
}
}

public void doDelete(String url, Map<String, String> headerValues, boolean isInternal) {
final HttpHeaders headers = createHeaders(headerValues, isInternal);
webClient.delete()
.uri(url)
.headers(httpHeaders -> httpHeaders.addAll(headers))
.retrieve()
.toBodilessEntity()
.block();
}
}




,,



Comments