Spring webflux: ServerResponse redirection - spring-boot

This is my related code:
#RestController
public class GicarController {
#PostMapping("/login")
public Mono<ServerResponse> gicar(#RequestHeader("GICAR_ID") String gicarId) {
return ServerResponse.temporaryRedirect(URI.create("/me")).build();
}
}
Issue arises when I'm calling to _/login endpoint:
$ curl -i -X POST localhost:8080/login -H "GICAR_ID: tre"
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/event-stream;charset=UTF-8
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
curl: (18) transfer closed with outstanding read data remaining
Why am I getting an 200 http code response?
On spring boot logging I'm getting this exception:
022-06-27 13:11:19.931 ERROR 79654 --- [or-http-epoll-2] r.n.http.server.HttpServerOperations : [9750a9d8-1, L:/127.0.0.1:8080 - R:/127.0.0.1:33150] Error finishing response. Closing connection
org.springframework.core.codec.CodecException: Type definition error: [simple type, class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$WriterFunctionResponse]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultServerResponseBuilder$WriterFunctionResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
Why above exception is reaised?
Any ideas?

According to Spring documentation ServerResponse
Represents a typed server-side HTTP response, as returned by a handler function or filter function.
and it supposed to be used in Functional Endpoints
#Configuration
public class GicarConfiguration {
#Bean
public RouterFunction<ServerResponse> route() {
return RouterFunctions
.route(POST("/login"), this::loginHandler);
}
private Mono<ServerResponse> loginHandler(ServerRequest request) {
var gicarId = request.headers().firstHeader("GICAR_ID");
return ServerResponse.temporaryRedirect(URI.create("/me")).build();
}
}
If you still want to use Annotated Controllers, use ResponseEntity instead
#RestController
public class GicarController {
#PostMapping("/login")
public Mono<ResponseEntity<Void>> gicar() {
return Mono.just(ResponseEntity
.status(HttpStatus.TEMPORARY_REDIRECT)
.header(HttpHeaders.LOCATION, "/me")
.build()
);
}
}

Related

#PreAuthorize makes validation not working for primitive type

First of all, this is my development environent.
Spring boot: org.springframework.boot:2.5.6
io.spring.dependency-management: 1.0.11.RELEASE
Spring Security: org.springframework.boot:spring-boot-starter-security
org.springframework.boot:spring-boot-starter-webflux
kotlin
org.springframework.boot:spring-boot-starter-validation
When I call this api with invalid value of limit(as 100), validator(#Max) is working successfuly.
Call: GET {{apiEndpoint}}/workspaces/{{workspaceId}}/test?limit=1000
Code
#GetMapping("/test")
#ResponseStatus(HttpStatus.OK)
suspend fun test(
auth: AuthToken,
#PathVariable workspaceId: String,
#RequestParam(name = "limit", defaultValue = "15") #Max(20) limit: Int
): String {
return "OK"
}
Response
HTTP/1.1 500 Internal Server Error
requestId: 32511EB3433F4D1DBEAC56641E6BE1A2
Content-Type: application/json
Content-Length: 213
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
{
"requestId": "32511EB3433F4D1DBEAC56641E6BE1A2",
"message": "test.limit: 20 이하여야 합니다",
"sys": {
"id": "UnhandledError",
"code": "50001",
"type": "Error"
},
"details": {
"exception": "ConstraintViolationException"
}
}
But with #PreAutorize annotation, validator is not working.
Code
#GetMapping("/test")
#ResponseStatus(HttpStatus.OK)
#PreAuthorize("hasRole('USER')")
suspend fun test(
auth: AuthToken,
#PathVariable workspaceId: String,
#RequestParam(name = "limit", defaultValue = "15") #Max(20) limit: Int
): String {
return "OK"
}
Response
HTTP/1.1 200 OK
requestId: B0F832B0AACA49DDB2F5774FE3BFB8D4
Content-Type: application/json;charset=utf8
Content-Length: 2
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
OK
Why spring-security makes validator not working??
Plus, validator for Object(not primitive type) type is working successfuly with #PreAuthorize.
#PostMapping("/test")
#ResponseStatus(HttpStatus.OK)
#PreAuthorize("hasRole('USER')")
suspend fun test(
auth: AuthToken,
#PathVariable workspaceId: String,
#RequestBody #Valid body: Payload // It works!!
): String {
return "OK"
}
data class Payload(
#field:Max(10)
val age: Int
)
Please help me.
I also tried to use `groups(ValidationGroups)', but it's not works too.

Spring Boot Test with Mockito : #Validated annotation is being ignored during unit tests

I'm using Spring Boot 2.1.1, JUnit 5, Mockito 2.23.4.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
Here's my controller :
#RestController
#Validated
public class AramaController {
#ResponseStatus(value = HttpStatus.OK)
#GetMapping("/arama")
public List<Arama> arama(#RequestParam #NotEmpty #Size(min = 4, max = 20) String query) {
return aramaService.arama(query);
}
}
This controller works as expected.
curl with no "query" parameter returns Bad Request 400 :
~$ curl http://localhost:8080/arama -v
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /arama HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 400
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Length: 0
< Date: Wed, 12 Dec 2018 21:47:11 GMT
< Connection: close
<
* Closing connection 0
curl with "query=a" as parameter returns Bad Request 400 as well :
~$ curl http://localhost:8080/arama?query=a -v
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /arama?query=a HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 400
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Wed, 12 Dec 2018 21:47:33 GMT
< Connection: close
<
* Closing connection 0
{"message":"Input error","details":["size must be between 4 and 20"]}
This controller and validation works flawlessly when running on a server.
During unit tests the #Validated annotation doesn't seem to have any effect.
Here my test code :
#ExtendWith(MockitoExtension.class)
class AramaControllerTest {
#Mock
private AramaService aramaService;
#InjectMocks
private AramaController aramaController;
private MockMvc mockMvc;
#BeforeEach
private void setUp() {
mockMvc = MockMvcBuilders
.standaloneSetup(aramaCcontroller)
.setControllerAdvice(new RestResponseEntityExceptionHandler())
.build();
}
#Test
void aramaValidationError() throws Exception {
mockMvc
.perform(
get("/arama").param("query", "a")
)
.andExpect(status().isBadRequest());
verifyNoMoreInteractions(aramaService);
}
}
This test results in failure :
java.lang.AssertionError: Status expected:<400> but was:<200>
Expected :400
Actual :200
Since the #Valid annotations pass my other test cases, and they work without loading the Spring context, is there a way to make the #Validated annotation work as well with Mockito (again, without loading the Spring context) ?
I got the answer elsewhere and wanted to share :
Without starting up the context, you won't have #Validator getting
tested because validator instances are Spring beans. However, #Valid
will work as it is a JSR-303 standard.
As of now, what I can suggest is.
#SpringBootTest
#ExtendWith(SpringExtension.class)
maybe you can try using #WebMvcTest and add SpringExtension
#ExtendWith({SpringExtension.class, MockitoExtension.class})
#WebMvcTest(AramaController.class)
class AramaControllerTest {
#Mock
private AramaService aramaService;
#InjectMocks
private AramaController aramaController;
#Autowired
private MockMvc mockMvc;
#Test
void aramaValidationError() throws Exception {
mockMvc
.perform(
get("/arama").param("query", "a")
)
.andExpect(status().isBadRequest());
verifyNoMoreInteractions(aramaService);
}
}

Wiremock returning wrong content-type for Spring Cloud Contract stub runner

I'm trying out Spring-Cloud-Contract for the first time. I'm trying to have my client auto-discover the contract stubs, but even though my contract specifies a content-type of 'application/json' on the response, what I get from WireMock has a content-type of 'application/octet'. What am I doing wrong?
I have a simple method in my service which returns a model like this from a /status endpoint:
{
"name": string,
"status": string
}
My contract looks like this:
import org.springframework.cloud.contract.spec.Contract
Contract.make {
request {
method('GET')
headers {
contentType(applicationJson())
}
url("/status")
}
response {
status OK()
body(
name: "Demo",
status: "RUNNING"
)
headers {
contentType(applicationJson())
}
}
}
In my client, I have a class which uses a Spring RestTemplate to query this endpoint:
#Component
public class StatusClient {
private final RestTemplate restTemplate;
public StatusClient(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public Status getStatus() {
return this.restTemplate
.exchange("http://localhost:8080/status", HttpMethod.GET, null, Status.class)
.getBody();
}
}
#Data
class Status implements Serializable {
private String name;
private String status;
}
My unit test uses #AutoConfigureStubRunner to pull the latest version of the contract from the local repository and assert against the response from the contract (eg name = Demo, status = RUNNING).
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureStubRunner(ids = {"com.example:contract-demo:+:8080"}, stubsMode = StubRunnerProperties.StubsMode.LOCAL)
public class StatusClientTests {
#Autowired
private StatusClient client;
#Test
public void testThatStatusReturnsSuccessfully() {
Status result = this.client.getStatus();
assertEquals("Demo", result.getName());
assertEquals("RUNNING", result.getStatus());
}
}
When I run the test, WireMock reports the contract it received as expected:
2018-05-31 11:36:49.919 INFO 14212 --- [tp1255723887-26] WireMock : Request received:
127.0.0.1 - GET /status
User-Agent: [Java/1.8.0_161]
Connection: [keep-alive]
Host: [localhost:8080]
Accept: [application/json, application/*+json]
Matched response definition:
{
"status" : 200,
"body" : "{\"name\":\"Demo\",\"status\":\"RUNNING\"}",
"headers" : {
"contentType" : "application/json"
},
"transformers" : [ "response-template" ]
}
Response:
HTTP/1.1 200
contentType: [application/json]
But when the RestTemplate tries to deserialize it, it throws an exception because the response content type is actually "application/octet" once it hits the methods to extract the data:
org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.example.contractclientdemo.Status] and content type [application/octet-stream]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:119)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:991)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:974)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:725)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:680)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:600)
at com.example.contractclientdemo.StatusClient.getStatus(StatusClient.java:18)
I'm using Finchley.RC2 for the Spring cloud version, and spring-cloud-starter-contract-stub-runner is my only test dependency other than spring-boot-starter-test.
I know WireMock is returning the wrong content type because I debugged deep into the HttpMessageConverterExtractor class in Spring and that's what the getContentType method returned when queried.
Why is WireMock returning the wrong content type, though it reports the correct one in the log? And how can I get it to properly return an application/json so I can deserialize my simple message?
I had the exact same problem than you. I solved it by adding
headers {
header 'Content-Type': 'application/json;charset=UTF-8'
}
to the response.
You seem to have in your response, although written in another way, but that did solve the problem in my case. So this have something to do with it.
Before making the change, curl showed no Content-Type response header :
curl -v -H "Accept: application/json" localhost:6565/products/ABC
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 6565 (#0)
GET /products/ABC HTTP/1.1
Host: localhost:6565
User-Agent: curl/7.58.0
Accept:application/json
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Server: Jetty(9.2.z-SNAPSHOT)
Connection #0 to host localhost left intact
{
"price": {
"currencyCode": "EUR",
"value": "100.50"
},
"name": "Fake product"
}
After doing the change, curl returned this and the RestTemplate managed to deserialize it.
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Server: Jetty(9.2.z-SNAPSHOT)
Here's my working contract :
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return product information"
request{
method GET()
url("/products/ABC")
}
response {
status 200
headers {
header 'Content-Type': 'application/json;charset=UTF-8'
}
body([
name: 'Fake product',
price:[
currencyCode: 'EUR',
value: 100.50
]
])
}
}
Hope this helps
I think you should file it as an issue in WireMock. Also you're not setting the application / json content type header in the request explicit. Maybe that's a problem? Also shouldn't it be content-type as the header name in the response stub?

Spring Boot CORS headers

I am new to CORS headers and implementing with Spring boot. I am enabling CORS header on POST service which accept request body.
First time preflight request is made which runs fine and return 200 but when actual post request is invoked, it always return 403 forbidden with response body "Invalid CORS request". I have read almost all spring docs and all google/stackoverflow discussions but could not find out what am I missing..huh..
In Below snippet I have tested by adding crossOrigin at top of class and top of method but no luck.
#CrossOrigin(origins = "https://domain/", allowCredentials = "false")
#RequestMapping(value = ApplicationConstants.URI_PATH)
class MainController {
#RequestMapping(value = '/postMethod', method = RequestMethod.POST)
Map<String, Object> postMethod(HttpServletResponse servletResponse,
#RequestBody(required = false) AccessToken requestedConsumerInfo) {...}
For POST method - Preflight request is invoked and result is 200 but main POST call returns 403.
Call with OPTIONS: Status code 200
Response headers (616 B)
Access-Control-Allow-Credentials true
Access-Control-Allow-Headers content-type
Access-Control-Allow-Methods POST
Access-Control-Allow-Origin https://domain
Allow GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Cache-Control max-age=0, private, no-cache, …roxy-revalidate, no-transform
Connection close
Content-Length 0
Date Wed, 20 Dec 2017 17:57:14 GMT
Pragma no-cache
Server nginx/1.9.1
Strict-Transport-Security max-age=63072000; includeSubdomains;
Vary Origin,User-Agent
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1; mode=block
Request headers (512 B)
Accept text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.5
Access-Control-Request-Headers content-type
Access-Control-Request-Method POST
Connection keep-alive
Host domain
Origin https://domain
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/57.0
Call with POST: Status code 403
Response headers (364 B)
Cache-Control max-age=0, private, no-cache, …roxy-revalidate, no-transform
Connection close
Content-Length 20
Date Wed, 20 Dec 2017 17:57:14 GMT
Pragma no-cache
Server nginx/1.9.1
Strict-Transport-Security max-age=63072000; includeSubdomains;
Vary User-Agent
X-Frame-Options SAMEORIGIN
X-XSS-Protection 1; mode=block
Request headers (2.507 KB)
Accept application/json, text/plain, */*
Accept-Encoding gzip, deflate, br
Accept-Language en-US,en;q=0.5
Connection keep-alive
Content-Length 102
Content-Type application/json
Cookie rxVisitor=1513720811976ARCUHEC…B4SL3K63V8|6952d9a33183e7bc|1
Host domain
Origin https://domain
Referer https://domain/home/account/register
User-Agent Mozilla/5.0 (Windows NT 6.1; W…) Gecko/20100101 Firefox/57.0
Since this was not working, I have also tested by adding global configurations alone and also along with above snippet but no luck.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
void addCorsMappings(CorsRegistry registry) {
super.addCorsMappings(registry);
registry.addMapping(ApplicationConstants.MEMBER_URL_PATH)
.allowedOrigins("https://domain/")
.allowedMethods(HttpMethod.GET.toString(),
HttpMethod.POST.toString(), HttpMethod.PUT.toString());
}
}
}
On the preflight OPTIONS request, the server should respond with all the following (looks like you're doing this already):
Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Allow-Credentials (if cookies are passed)
On the actual POST request, you'll need to return at least Access-Control-Allow-Origin and Access-Control-Allow-Credentials. You're not currently returning them for the POST response.
I had the same issue, then used the annotation #CrossOrigin and it works fine, but just for GET, when I tried to make a POST I still got Cross Origin error, then this fixed for me:
Create an interceptor and added the Access Controll headers to the response.
(You might not need all of them)
public class AuthInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse httpResponse, Object handler, ModelAndView modelAndView)
throws Exception {
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Allow-Headers", "*");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Max-Age", "4800");
}
}
Then add the interceptor:
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
System.out.println("++++++ WebConfig addInterceptors() ");
registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**");
}
}
I hope this save you some time, it took me a while to get this working .

Spring/Eureka/Feign - FeignClient setting Content-Type header to application/x-www-form-urlencoded

When I use a FeignClient it is setting the Content-Type to application/x-www-form-urlencoded instead of application/json;charset=UTF-8.
If I use a RestTemplate to send the same message the message header Content-Type is correctly set to application/json;charset=UTF-8.
Both the FeignClient and RestTemplate are using Eureka for service discovery, and I discovered this problem by debugging the HTTP message received by the server.
The controller on the server side looks like this:
#RestController
#RequestMapping("/site/alarm")
public class SiteAlarmController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<RaiseAlarmResponseDto> raiseAlarm(#RequestBody RaiseSiteAlarmRequestDto requestDto) {
...
}
My FeignClient interface in the service that calls the alarm looks like this:
#FeignClient("alarm-service")
public interface AlarmFeignService {
#RequestMapping(method = RequestMethod.POST, value = "/site/alarm")
RaiseAlarmResponseDto raiseAlarm(#RequestBody RaiseSiteAlarmRequestDto requestDto);
}
The HTTP message headers from the FeignClient are:
Accept: */*
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_60
Host: smit005s-MacBook-Pro.local:9120
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 323
The alarm service doesn't like the Content-Type and throws the following exception:
2015-04-22 12:12:28.580 thread="qtp1774842986-25" class="org.eclipse.jetty.servlet.ServletHandler" level="WARN"
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is feign.FeignException: status 415 reading AlarmFeignService#raiseAlarm(RaiseSiteAlarmRequestDto); content:
{"timestamp":1429701148576,"status":415,"error":"Unsupported Media Type","exception":"org.springframework.web.HttpMediaTypeNotSupportedException","message":"Unsupported Media Type","path":"/site/alarm"}
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978) ~[spring-webmvc-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:857) ~[spring-webmvc-4.1.5.RELEASE.jar:4.1.5.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618) ~[tomcat-embed-core-8.0.20.jar:8.0.20]
...
... /* commented rest of stack out */
...
If I change the client side code to use a RestTemplate as follows:
#Service
public class AlarmService {
#Autowired
private RestTemplate restTemplate;
...
public void send(RaiseSiteAlarmRequestDto alarm) {
RaiseAlarmResponseDto result = restTemplate.postForObject("http://alarm-service/site/alarm",
raiseSiteAlarmRequestDto, RaiseAlarmResponseDto.class);
}
}
It works with the RestTemplate, the alarm-service receives the message and processes it successfully. The message headers sent by the RestTemplate are:
Accept: application/json, application/*+json
Content-Type: application/json;charset=UTF-8
Cache-Control: no-cache
Pragma: no-cache
User-Agent: Java/1.7.0_60
Host: smit005s-MacBook-Pro.local:9120
Connection: keep-alive
Content-Length: 323
The answer was to do as #spencergibb suggests; use the consumes directive in the #RequestMapping annotation on the FeignClient interface. This Spring/Netflix documentaition also has an example.
So for example the #FeignClient interface declaration in the client is now:
#FeignClient("alarm-service")
public interface AlarmFeignService {
#RequestMapping(method = RequestMethod.POST, value = "/site/alarm", consumes = "application/json"))
RaiseAlarmResponseDto raiseAlarm(RaiseSiteAlarmRequestDto requestDto);
}
Note this is only necessary on the client side and the server side controller does not need to have this change.
Would be nice if this was done by default on the #FeignClient and then it would be the consistent with RestTemplate and the server side controller #RequestMapping annotation. Maybe that can be done in a future release of spring-cloud.

Resources