I try to implement an example of Spring Boot Microservices with JWT. I have some problems following:
1 ) I cannot run all integration tests of product service even if I defined bearer token
2 ) After login and get access token, I cannot send any request to product service. I got 500 Internal Server Error.
How can I fix it? I hope you can help me?
Here is the repo : https://github.com/Rapter1990/springbootmicroserviceswithsecurity
#Spring Boot Microservices with JWT Issues
139 messages · Page 1 of 1 (latest)
⌛ This post has been reserved for your question.
Hey @tacit grail! Please use
/closeor theClose Postbutton above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically closed after 300 minutes of inactivity.
TIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here.
Can you show the stack trace?
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
In case your post is not getting any attention, you can try to use/help ping.
Warning: abusing this will result in moderative actions taken against you.
@woeful smelt I see the logs
c.s.a.filter.JwtAuthenticationFilter : Error validating token: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3
When I send a request to another service through Api gateway.
https://stackoverflow.com/q/47505778/10871900 or similar may be useful
don't call blocking methods in reactor
I think it happens here: https://github.com/Rapter1990/springbootmicroserviceswithsecurity/blob/main/apigateway%2Fsrc%2Fmain%2Fjava%2Fcom%2Fspringbootmicroservices%2Fapigateway%2Ffilter%2FJwtAuthenticationFilter.java#L56
userServiceClient.validateToken(jwt);
@woeful smelt I asked someone and opened a pull request. https://github.com/Rapter1990/springbootmicroserviceswithsecurity/pull/1/files
I got this error issue ?
@woeful smelt I still couldn't fix the issue ?
so you have this interface: https://github.com/Rapter1990/springbootmicroserviceswithsecurity/blob/main/apigateway%2Fsrc%2Fmain%2Fjava%2Fcom%2Fspringbootmicroservices%2Fapigateway%2Fclient%2FUserServiceClient.java
this is blocking
which you cannot use with reactive
you could use Spring's WebClient instead I guess
@woeful smelt One guy opened a pull request but I still got the same error.
Why would the PR change anything?
Product service has also this. https://github.com/Rapter1990/springbootmicroserviceswithsecurity/blob/main/productservice/src/main/java/com/springbootmicroservices/productservice/filter/CustomBearerTokenAuthenticationFilter.java
that's not in reactive code
Do you know the difference between synchronous/blocking and asynchronous/reactive/nonblocking code?
I revise the JwtAuthenticationFilter in api gateway. I got 401 instead of 500 even if I use a token.
I updated my repo -> https://github.com/Rapter1990/springbootmicroserviceswithsecurity
Spring Security verbose logging?
Here is the console result of API Gateway ?
Did you enable verbose logging in Spring Security?
Yeah, I got this message after I enabled it. I just updated my repo again.
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
In case your post is not getting any attention, you can try to use/help ping.
Warning: abusing this will result in moderative actions taken against you.
@iron wedge Hi, I think you can help me. I couldn't fix my issue.
@woeful smelt Hi, What's up? I couldn't still fix the issue.
I also add some logs to check if token is valid or not. The token is valid from user service.
Is it possible to look through it?
Hi, I revised it again. I got this error in product service.
[UserServiceClient#getAuthentication(String)]: [{"time":"2024-07-18T14:26:57.2236457","httpStatus":"NOT_FOUND","header":"API ERROR","message":"Could not write JSON: Unable to convert claim 'iss' of type 'class java.lang.String' to URL.","isSuccess":false}]
Here is the screenshot
I updated my repo again. I still got the error. @woeful smelt
Did you add verbose Spring Security logs yet? I don't see it in the image
What's the code responsible for that?
I added it into application.yml files of the services.
Can you look through my latest post as I still couldn't fix it?
which post?
the repo is a bit too much to look through on my phone lol
@woeful smelt
Here are the posts
Yeah, it comes from Custom filter in Product Service.
Is the filter called?
I debugged it. Filter is called.
Just a moment. I'm currently using my PC.
Here is the login
Here is the process of creating Product via access token
Here is the logs of api gateway
Here are the logs of other services
I run product service as debug mode.
Token is valid through filter defined in Product service.
Here is result which I show
What does getAuthentication() return?
seems like getAuthentication isn't doing something right
I can get the right values from claims
I look through it again. Here is the information
Can you step to the
return UsernamePasswordAuthenticationToken
.authenticated(jwt, null, authorities);
?
what is jwt and authorities there?
So you are using UsernamePasswordAuthenticationToken.authenticated(jwt, null, authorities)
Is the filter called once or twice per request?
Here is authentication to be sent to product service.
Which service is that?
User Service
I use user service to validate token through product service
Is that also the user service?
Right
so if you are there the jwt and authorities are filled but if you use step over/step out, you get an empty UsernamePasswordAuthenticationToken?
Here is the filter in product service. Authentication information is empty
When you were debugging that, did it come from the filter or from somewhere else?
It is UserController's method in User Service.
oh so the getAuthentication method is making a call feign
I think you can't transmit UsernamePasswordAuthenticationToken over the network like that
final UsernamePasswordAuthenticationToken authentication = userServiceClient.getAuthentication(jwt);
Where is the problem?
Can you make getAuthentication return a custom object (ideally a record) storing just the information you need?
you returning a UsernamePasswordAuthenticationToken there
this is a network call that cannot properly transmit a UsernamePasswordAuthenticationToken
instead, try to transmit a custom object both parties have that contains all the information for authentication
I already did https://github.com/Rapter1990/springbootmicroserviceswithsecurity/blob/main/productservice/src/main/java/com/springbootmicroservices/productservice/config/JacksonConfig.java for UsernamePasswordAuthenticationToken
and then your CustomBearerTokenAuthenticationFilter can convert it to a UsernamePasswordAuthenticationToken
oh I think you can also use that way but I think your mixin is not sufficient
I also defined serializer for it before.
I think you'd need a custom serializer/deserializer for the UsernamePasswordAuthenticationToken
Can you debug the deserializer? What is p? Does it have all the information you need?
You might also need a serializer
You just attempt to deserialize the principal and the credentials. You deserialize the principal as a string meaning you expect that to be a string. You don't deserialize the authorities. Are you sure about that?
https://github.com/Rapter1990/springbootmicroserviceswithsecurity/blob/8bbb6a01c7799c5b1b87406336ab76a20a32bd5d/productservice/src/main/java/com/springbootmicroservices/productservice/serializer/UsernamePasswordAuthenticationTokenDeserializer.java#L15-L25
@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);
}
I check it now
What the problem is: You serialize a token, send it, receive it, deserialize it and then you don't have the same token (but a token with pretty much no relevant information)
Here is the information of p ?
oh that's good
if you step over once, what is principal?
Here is the result
ok so the node.get("principal").asText() seems to be the problem
What should the principal be?
public class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<UsernamePasswordAuthenticationToken> {
@Override
public UsernamePasswordAuthenticationToken deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
// Extract the nested principal object
JsonNode principalNode = node.get("principal");
String principal = principalNode.toString(); // Converting the entire principal node to a JSON string
// Extracting the credentials
String credentials = node.get("credentials").isNull() ? null : node.get("credentials").asText();
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
}
I revised it but nothing changed. I still get the same error.
@woeful smelt I want to show the console output of it.
What should I do?
What should the principal be?
Here is the principal information
if you want that on the other side as well, you need to deserialize it to a Jwt or equivalent object
not a String
I did it but I got this error message.
ok it seems you cannot deserialize that jwt class
so just create a record with the same components
Ok. I'll try it.
public record JwtRecord(String tokenValue, Map<String, Object> headers, Map<String, Object> claims) {
// You can add any necessary methods or constructors here if needed
}
public class UsernamePasswordAuthenticationTokenDeserializer extends JsonDeserializer<UsernamePasswordAuthenticationToken> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public UsernamePasswordAuthenticationToken deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
// Extract the nested principal object and deserialize it into a JwtRecord object
JsonNode principalNode = node.get("principal");
JwtRecord principal = objectMapper.treeToValue(principalNode, JwtRecord.class);
// Extracting the credentials
String credentials = node.get("credentials").isNull() ? null : node.get("credentials").asText();
return new UsernamePasswordAuthenticationToken(principal, credentials);
}
}
I implemented it.
Here is the error
Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "issuedAt" (class com.springbootmicroservices.productservice.model.auth.JwtRecord), not marked as ignorable (3 known properties: "tokenValue", "headers", "claims"])
at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: com.springbootmicroservices.productservice.model.auth.JwtRecord["issuedAt"])
it means the JWT has an issuedAt field but your record doesn't
you could add that to the record
or if you don't need it, configure jackson to ignore missing fields
I get the same error again .
Here are console result
Is it possible to test my example as I'm stuck there?
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
In case your post is not getting any attention, you can try to use/help ping.
Warning: abusing this will result in moderative actions taken against you.
💤 Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
In case your post is not getting any attention, you can try to use/help ping.
Warning: abusing this will result in moderative actions taken against you.