diff --git a/productservice/pom.xml b/productservice/pom.xml index 7a9d64d..8525467 100644 --- a/productservice/pom.xml +++ b/productservice/pom.xml @@ -179,6 +179,15 @@ + + com.fasterxml.jackson.core + jackson-databind + + + io.github.openfeign + feign-jackson + + diff --git a/productservice/src/main/java/com/springbootmicroservices/productservice/client/UserServiceClient.java b/productservice/src/main/java/com/springbootmicroservices/productservice/client/UserServiceClient.java index bce85b8..67e6eb5 100644 --- a/productservice/src/main/java/com/springbootmicroservices/productservice/client/UserServiceClient.java +++ b/productservice/src/main/java/com/springbootmicroservices/productservice/client/UserServiceClient.java @@ -1,12 +1,13 @@ package com.springbootmicroservices.productservice.client; +import com.springbootmicroservices.productservice.config.FeignClientConfig; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; -@FeignClient(name = "userservice", path = "/api/v1/users") +@FeignClient(name = "userservice", path = "/api/v1/users", configuration = FeignClientConfig.class) public interface UserServiceClient { @PostMapping("/validate-token") diff --git a/productservice/src/main/java/com/springbootmicroservices/productservice/config/FeignClientConfig.java b/productservice/src/main/java/com/springbootmicroservices/productservice/config/FeignClientConfig.java new file mode 100644 index 0000000..27f7e00 --- /dev/null +++ b/productservice/src/main/java/com/springbootmicroservices/productservice/config/FeignClientConfig.java @@ -0,0 +1,93 @@ +package com.springbootmicroservices.productservice.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import feign.FeignException; +import feign.Request; +import feign.Response; +import feign.codec.Decoder; +import feign.codec.ErrorDecoder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Map; + +@Slf4j +@Configuration +public class FeignClientConfig { + + @Bean + public Decoder feignDecoder(ObjectMapper objectMapper) { + return new CustomDecoder(objectMapper); + } + + @Bean + public ErrorDecoder errorDecoder() { + return new CustomErrorDecoder(); + } + + private static class CustomDecoder implements Decoder { + + private final ObjectMapper objectMapper; + + public CustomDecoder(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public Object decode(Response response, Type type) throws IOException { + // Handle specific HTTP status codes and throw corresponding FeignExceptions + if (response.status() == HttpStatus.UNAUTHORIZED.value()) { + throw new FeignException.Unauthorized("Unauthorized", response.request(), response.request().body(), response.headers()); + } + if (response.status() == HttpStatus.FORBIDDEN.value()) { + throw new FeignException.Forbidden("Forbidden", response.request(), response.request().body(), response.headers()); + } + if (response.status() == HttpStatus.NOT_FOUND.value()) { + throw new FeignException.NotFound("Not Found", response.request(), response.request().body(), response.headers()); + } + if (response.status() == HttpStatus.METHOD_NOT_ALLOWED.value()) { + throw new FeignException.MethodNotAllowed("Method Not Allowed", response.request(), response.request().body(), response.headers()); + } + if (response.status() == HttpStatus.BAD_REQUEST.value()) { + throw new FeignException.BadRequest("Bad Request", response.request(), response.request().body(), response.headers()); + } + + // Deserialize the response body using Jackson + if (response.body() != null) { + InputStream inputStream = response.body().asInputStream(); + return objectMapper.readValue(inputStream, objectMapper.constructType(type)); + } + + return null; + } + } + + private static class CustomErrorDecoder implements ErrorDecoder { + + @Override + public Exception decode(String methodKey, Response response) { + HttpStatus status = HttpStatus.valueOf(response.status()); + // Handle specific HTTP status codes and return corresponding FeignExceptions + if (status == HttpStatus.UNAUTHORIZED) { + return new FeignException.Unauthorized("Unauthorized", response.request(), response.request().body(), response.headers()); + } + if (status == HttpStatus.FORBIDDEN) { + return new FeignException.Forbidden("Forbidden", response.request(), response.request().body(), response.headers()); + } + if (status == HttpStatus.NOT_FOUND) { + return new FeignException.NotFound("Not Found", response.request(), response.request().body(), response.headers()); + } + if (status == HttpStatus.METHOD_NOT_ALLOWED) { + return new FeignException.MethodNotAllowed("Method Not Allowed", response.request(), response.request().body(), response.headers()); + } + return new FeignException.BadRequest("Bad Request", response.request(), response.request().body(), response.headers()); + + } + } +} \ No newline at end of file diff --git a/productservice/src/main/java/com/springbootmicroservices/productservice/config/JacksonConfig.java b/productservice/src/main/java/com/springbootmicroservices/productservice/config/JacksonConfig.java new file mode 100644 index 0000000..9c17e6a --- /dev/null +++ b/productservice/src/main/java/com/springbootmicroservices/productservice/config/JacksonConfig.java @@ -0,0 +1,18 @@ +package com.springbootmicroservices.productservice.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.springbootmicroservices.productservice.serializer.UsernamePasswordAuthenticationTokenMixin; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.addMixIn(UsernamePasswordAuthenticationToken.class, UsernamePasswordAuthenticationTokenMixin.class); + return mapper; + } +} \ No newline at end of file diff --git a/productservice/src/main/java/com/springbootmicroservices/productservice/filter/CustomBearerTokenAuthenticationFilter.java b/productservice/src/main/java/com/springbootmicroservices/productservice/filter/CustomBearerTokenAuthenticationFilter.java index 5eb4b3b..4d8865e 100644 --- a/productservice/src/main/java/com/springbootmicroservices/productservice/filter/CustomBearerTokenAuthenticationFilter.java +++ b/productservice/src/main/java/com/springbootmicroservices/productservice/filter/CustomBearerTokenAuthenticationFilter.java @@ -49,8 +49,6 @@ protected void doFilterInternal(@NonNull final HttpServletRequest httpServletReq // Set authentication to SecurityContextHolder SecurityContextHolder.getContext().setAuthentication(authentication); - // Proceed with the filter chain - filterChain.doFilter(httpServletRequest, httpServletResponse); } catch (FeignException e) { log.error("Token validation failed for request: {}", httpServletRequest.getRequestURI(), e); @@ -64,7 +62,9 @@ protected void doFilterInternal(@NonNull final HttpServletRequest httpServletReq } } else { log.warn("Missing or invalid Authorization header for request: {}", httpServletRequest.getRequestURI()); - filterChain.doFilter(httpServletRequest, httpServletResponse); } + + // Proceed with the filter chain in any case + filterChain.doFilter(httpServletRequest, httpServletResponse); } } \ No newline at end of file diff --git a/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenDeserializer.java b/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenDeserializer.java new file mode 100644 index 0000000..3f93843 --- /dev/null +++ b/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenDeserializer.java @@ -0,0 +1,26 @@ +package com.springbootmicroservices.productservice.serializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; + +import java.io.IOException; + +public class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer { + + @Override + public UsernamePasswordAuthenticationToken deserialize(JsonParser p, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode node = p.getCodec().readTree(p); + + // Assuming the response contains the necessary fields + String principal = node.get("principal").asText(); + String credentials = node.get("credentials").asText(); + + return new UsernamePasswordAuthenticationToken(principal, credentials); + } +} diff --git a/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenMixin.java b/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenMixin.java new file mode 100644 index 0000000..6445ee5 --- /dev/null +++ b/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenMixin.java @@ -0,0 +1,11 @@ +package com.springbootmicroservices.productservice.serializer; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; + +@JsonDeserialize(using = UsernamePasswordAuthenticationTokenDeserializer.class) +public class UsernamePasswordAuthenticationTokenMixin extends UsernamePasswordAuthenticationToken { + public UsernamePasswordAuthenticationTokenMixin(Object principal, Object credentials) { + super(principal, credentials); + } +} diff --git a/userservice/src/main/java/com/springbootmicroservices/userservice/config/TokenConfigurationParameter.java b/userservice/src/main/java/com/springbootmicroservices/userservice/config/TokenConfigurationParameter.java index a0c7869..02d0392 100644 --- a/userservice/src/main/java/com/springbootmicroservices/userservice/config/TokenConfigurationParameter.java +++ b/userservice/src/main/java/com/springbootmicroservices/userservice/config/TokenConfigurationParameter.java @@ -11,7 +11,7 @@ @Getter @Configuration public class TokenConfigurationParameter { - private final String issuer; + private final int accessTokenExpireMinute; private final int refreshTokenExpireDay; private final PublicKey publicKey; @@ -19,7 +19,7 @@ public class TokenConfigurationParameter { public TokenConfigurationParameter() { - this.issuer = ConfigurationParameter.ISSUER.getDefaultValue(); + //this.issuer = ConfigurationParameter.ISSUER.getDefaultValue(); this.accessTokenExpireMinute = Integer.parseInt( ConfigurationParameter.AUTH_ACCESS_TOKEN_EXPIRE_MINUTE.getDefaultValue() diff --git a/userservice/src/main/java/com/springbootmicroservices/userservice/model/user/enums/ConfigurationParameter.java b/userservice/src/main/java/com/springbootmicroservices/userservice/model/user/enums/ConfigurationParameter.java index b5028b3..2f4821c 100644 --- a/userservice/src/main/java/com/springbootmicroservices/userservice/model/user/enums/ConfigurationParameter.java +++ b/userservice/src/main/java/com/springbootmicroservices/userservice/model/user/enums/ConfigurationParameter.java @@ -7,8 +7,6 @@ @RequiredArgsConstructor public enum ConfigurationParameter { - ISSUER("ISSUER"), - AUTH_ACCESS_TOKEN_EXPIRE_MINUTE("30"), AUTH_REFRESH_TOKEN_EXPIRE_DAY("1"), AUTH_PUBLIC_KEY(""" diff --git a/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java b/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java index 08d283e..60caf2d 100644 --- a/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java +++ b/userservice/src/main/java/com/springbootmicroservices/userservice/service/impl/TokenServiceImpl.java @@ -2,7 +2,6 @@ import com.springbootmicroservices.userservice.config.TokenConfigurationParameter; import com.springbootmicroservices.userservice.model.user.Token; -import com.springbootmicroservices.userservice.model.user.enums.ConfigurationParameter; import com.springbootmicroservices.userservice.model.user.enums.TokenClaims; import com.springbootmicroservices.userservice.model.user.enums.TokenType; import com.springbootmicroservices.userservice.model.user.enums.UserType; @@ -45,7 +44,6 @@ public Token generateToken(final Map claims) { .type(TokenType.BEARER.getValue()) .and() .id(UUID.randomUUID().toString()) - .issuer(ConfigurationParameter.ISSUER.getDefaultValue()) .issuedAt(tokenIssuedAt) .expiration(accessTokenExpiresAt) .signWith(tokenConfigurationParameter.getPrivateKey()) @@ -62,7 +60,6 @@ public Token generateToken(final Map claims) { .type(TokenType.BEARER.getValue()) .and() .id(UUID.randomUUID().toString()) - .issuer(tokenConfigurationParameter.getIssuer()) .issuedAt(tokenIssuedAt) .expiration(refreshTokenExpiresAt) .signWith(tokenConfigurationParameter.getPrivateKey()) @@ -96,7 +93,6 @@ public Token generateToken(final Map claims, final String refres .type(TokenType.BEARER.getValue()) .and() .id(UUID.randomUUID().toString()) - .issuer(tokenConfigurationParameter.getIssuer()) .issuedAt(accessTokenIssuedAt) .expiration(accessTokenExpiresAt) .signWith(tokenConfigurationParameter.getPrivateKey()) @@ -150,7 +146,7 @@ public void verifyAndValidate(final String jwt) { .parseSignedClaims(jwt); // Log the claims for debugging purposes - Claims claims = claimsJws.getBody(); + Claims claims = claimsJws.getPayload(); log.info("Token claims: {}", claims); // Additional checks (e.g., expiration, issuer, etc.)