I have a RestController defined as follows:
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.PostMapping
import javax.validation.Valid
#RestController
class CaseController(
private val caseService: CaseService,
private val remoteFileService: RemoteFileService,
private val clientService: ClientService
) {
#PostMapping("/api/v1/cases", consumes = [MediaType.APPLICATION_JSON_VALUE])
#NeedsAuth
fun createCase(
#RequestBody #Valid caseCreationRequest: CaseCreationRequest,
#RequestHeader("Api-Key", required = false) apiKey: String,
): ResponseEntity<Case> { }
I have defined NeedsAuth as an annotation.
The problem is that the #Valid annotation is being called before #NeedsAuth.
If I send invalid request body with invalid authentication, I receive "Validation Error" as response.
If I send valid request body with invalid authentication, I receive "Authentication Error".
If I remove #Valid annotation from code and then send invalid request body with invalid authentication, I receive "Authentication Error".
What I want this to do?
I want it to call #NeedsAuth before #Valid.
Any help is greatly appreciated.
Thanks
Update:
Code related to handling of #NeedsAuth:
//NeedsAuth.kt
package com.jimdo.debtcollectionservice.adapters.apis.http.auth
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FUNCTION)
annotation class NeedsAuth
//AuthAspect.kt
package com.jimdo.debtcollectionservice.adapters.apis.http.auth
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.context.request.ServletRequestAttributes
#Component
#Aspect
class AuthAspect {
#Autowired
lateinit var authTokenHandler: AuthTokenHandler
#Before("execution(* *.*(..)) && #annotation(NeedsAuth)")
fun validateToken(joinPoint: JoinPoint) {
val request = (RequestContextHolder.currentRequestAttributes() as ServletRequestAttributes).request
authTokenHandler.authenticateToken(request.getHeader("Api-Key"))
}
}
Related
I am using some external API to GET and POST some ressources, locally my code works fine with the call of different endPoints (GET, POST...) and even with Postman, but when i try to run my code in another platerform (where the ressources are), i get the 412 HTTP error due to a POST call : after looking on the internet, i found out that i should generate an ETagd of the entity (that i went to modify) and add it into the header of my POST endPoint.
For that, i used ShallowEtagHeaderFilter and the #Bean annotation(above the filter method) and the #SpringBootApplication annotation above my class, here is my code :
package main.Runners;
import io.testproject.java.annotations.v2.Parameter;
import okhttp3.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Bean;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import javax.servlet.Filter;
#SpringBootApplication
public class ActionRunner {
#Parameter(description = "the project ID")
public static String projectId = "xxx";
#Parameter(description = "the test ID")
public static String testId = "yyy";
public static void main(String[] args) throws Exception {
try {
OkHttpClient client = new OkHttpClient().newBuilder()
.build();
Request request = new Request.Builder()
.url("https://api.testproject.io/v2/projects/"+projectId+"/tests/"+testId)
.method("GET", null)
.addHeader("Authorization", "nzmo4DI08ykizYgcp9-5cCTArlxq7k7zt9MYhGmTcRk1")
.build();
Response response = client.newCall(request).execute();
System.out.println("================ this is our response headers ::: \n"+ response.headers());
} catch(Exception e) {
System.out.println(e);
}
}
#Bean
public ShallowEtagHeaderFilter shallowEtagHeaderFilter(){
return new ShallowEtagHeaderFilter();
}
}
I really need Your help since i cant generate any ETag parameter on my GET response header(after checking reponse.headers() ).
Thanks in advance!
I am new in this project and I got a task to add some services to the project.
First I created a package NCDM beside other Rest packages and created my class NCDMMemberController:
package ir.anarestan.ipc.controller.NCDM;
import ir.anarestan.ipc.controller.helper.NCDM.NCDMMemberDTO;
import ir.anarestan.ipc.controller.helper.ResponseDTO;
import ir.anarestan.ipc.service.NCDM.NCDMMemberService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.mobile.device.Device;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequestMapping(path = "/ncdmMember")
public class NCDMMemberController {
private final static Logger logger = LoggerFactory.getLogger(NCDMMemberController.class);
#Autowired
private NCDMMemberService ncdmMemberService;
#ResponseBody
#RequestMapping(value = "/getMember", method = RequestMethod.GET, produces = "application/hal+json")
public Object get(#RequestBody NCDMMemberDTO memberDTO, Device device) {
ResponseDTO responseDTO = new ResponseDTO();
try {
responseDTO.setSuccess(true);
responseDTO.setHttpStatus(HttpStatus.OK.value());
responseDTO.setResponseBody(ncdmMemberService.getMemberByMemberIdAndImei(memberDTO));
return responseDTO;
} catch (Exception e) {
logger.info("error occurred!", e);
responseDTO.setSuccess(false);
responseDTO.setErrorMessage(e.getMessage());
responseDTO.setHttpStatus(HttpStatus.EXPECTATION_FAILED.value());
responseDTO.setResponseBody(null);
return responseDTO;
}
}
#ResponseBody
#RequestMapping(value = "/saveMember", method = RequestMethod.POST, produces = "application/hal+json")
public Object save(#RequestBody NCDMMemberDTO member, Device device) {
ResponseDTO responseDTO = new ResponseDTO();
try {
ncdmMemberService.saveMember(member);
responseDTO.setSuccess(true);
responseDTO.setHttpStatus(HttpStatus.OK.value());
responseDTO.setResponseBody(null);
return responseDTO;
} catch (Exception e) {
logger.info("error occurred!", e);
responseDTO.setSuccess(false);
responseDTO.setErrorMessage(e.getMessage());
responseDTO.setHttpStatus(HttpStatus.EXPECTATION_FAILED.value());
responseDTO.setResponseBody(null);
return responseDTO;
}
}
}
but when I try to send request from swagger, I got the following response:
{
"timestamp": 1579957861338,
"status": 401,
"error": "Unauthorized",
"message": "Unauthorized",
"path": "/ncdmMember/saveMember"
}
And that happen when sendig request to the previous services has no error.
Does anyone know what is the problem?Do I need to introduce my package/class somewhere?
And when I copy one of the written services to the privious classes everything is ok.
Any help would be appreciated.
it is because, you've Spring Security set up in your project. Based on the type of Authentication (OAuth2, Basic Auth, Digest Auth,... etc.), you need to add securityScheme in your Docket bean defined as SwaggerConfiguration.
Example:
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).
... // more configurations
.securitySchemes(/*List of Your SecuritySchemes*/)
.build();
Then you can use Swagger Documentation's Authorize button. Below is an example of OAuth2
After some researches and of course by the clues that answers gave me, i found out that my problem raised from SpringSecurity, and the only thing that i needed to do was just adding my base URL of the controller class to WebSecurityConfig as permitted URLs like below:
.antMatchers("/ncdmMember/**").permitAll()
I'm trying to find a way to retrieve the URL that is currently mapped by Feign Client method interface in a Spring app, but I could not find a solution yet. The following code works:
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
application.yml
api:
url: https://jsonplaceholder.typicode.com
ApiClient.class
package com.example.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
#FeignClient(name = "json-placeholder", url = "${api.url}")
public interface ApiClient {
#GetMapping(value = "/posts", consumes = "application/json")
ResponseEntity<String> getPosts();
}
FeignApplication.class
package com.example.feign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import javax.annotation.PostConstruct;
#EnableFeignClients
#SpringBootApplication
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
#Autowired
private ApiClient apiClient;
#PostConstruct
public void test() {
// this works
System.out.println(apiClient.getPosts().getBody());
// apiClient. ... getPosts request URL
}
}
I tried to compose this URL reading directly from annotations, but it doesn't seem to work. Can anybody give me an alternative? Thank you.
EDIT notes -
Sorry, but I had do a small change in my question, due to an unexpected problem while applying provided solutions.
Reading directly from Annotations works if the value set in annotation property is literal. If the value is read from application.yml file, the URL returned by the annotation property is the expression ifself, not the parsed value.
Any ideas about this updated scenario? What I need is the URL actually been called by FeignClient. I'm understands all provided solutions are actually workarounds.
I'm not sure if you are still looking for the answer, but my method is below
Return Response
import feign.Response;
#FeignClient(name = "json-placeholder", url = "${api.url}")
public interface ApiClient {
#GetMapping(value = "/posts", consumes = "application/json")
Response getPosts();
}
get Request.url from the Response
Response response = apiClient.getPosts();
String url = response.request().url()
then you can get the URL
I am staring working on spring boot and trying a simple Rest Controller.
I have two methods using HTTP GET and they work fine.
However when I do a HTTP POST it is not working showing :
: Request method 'POST' not supported
My Controller code as below:-
enter code here
package com.example.web.api;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.example.model.Greeting;
#RestController
public class GreetingController {
private static BigInteger nextId;
private static Map<BigInteger, Greeting> greetingMap;
private static Greeting save(Greeting greeting){
if (greetingMap==null){
greetingMap = new HashMap<BigInteger, Greeting>();
nextId = BigInteger.ONE;
}
greeting.setId(nextId);
nextId=nextId.add(BigInteger.ONE);
greetingMap.put(greeting.getId(), greeting);
return greeting;
}
static {
// First Greeting
Greeting g1 = new Greeting();
g1.setText("Hello World!!");
save(g1);
// Second Greeting
Greeting g2 = new Greeting();
g2.setText("Hola Mundo!!");
save(g2);
}
/*
*
* Issue a GET to view greetings
*
*/
#RequestMapping(
value="/api/greetings",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<Collection<Greeting>> getGreetings(){
Collection<Greeting> greetings=greetingMap.values();
return new ResponseEntity<Collection<Greeting>>(greetings, HttpStatus.OK);
}
/*
*
* Issue a GET to view single greeting by id value
*
*/
#RequestMapping(
value="/api/greetings/{id}",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<Greeting> getGreeting(#PathVariable("id") BigInteger id){
Greeting greeting = greetingMap.get(id);
if(greeting == null){
return new ResponseEntity<Greeting>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity <Greeting> (greeting, HttpStatus.OK);
}
/*
*
* Create a POST to add a greeting
*
*/
#RequestMapping(
value="/api/greetings/",
method=RequestMethod.POST,
consumes=MediaType.APPLICATION_JSON_VALUE,
produces=MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<Greeting> createGreeting(#RequestBody Greeting greeting){
Greeting savedGreeting = save(greeting);
return new ResponseEntity <Greeting> (savedGreeting, HttpStatus.CREATED);
}
/* End of HTTP Methods */
}
Kindly advise , what is wrong with createGreeting Method.
Kind regards
your POST method has a trailing slash /api/greetings/ which you missed in your curl call and the other thing you missed is a Content-type header. You should say to server what type of data you send.
curl -X POST -d '{"text":"hello"}' -H "Content-type:application/json" http://localhost:8080/api/greetings/ is a working curl call.
You need to modify the Content-Type to json
try changing link the value to another, for example:
#RequestMapping(
value="/api/greeting",
method=RequestMethod.GET,
produces=MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<Greeting> createGreeting(#RequestBody Greeting greeting){
Greeting savedGreeting = save(greeting);
return new ResponseEntity <Greeting> (savedGreeting, HttpStatus.CREATED);
}
Following the example provided by spring.io for creating a simple REST service, I run into a strange issue.
If I make a request to localhost:8080/greeting the greeting route is called and I receive the expected response:
{"id":1, "content":"Hello, World!"}
If I add a route "/test" and then make an HTTP GET request to localhost:8080/test I get the expected response:
I'm a teapot
The problem arises when I do one of two things. Firstly, if I add HttpServletResponse or HttpServletRequest as a parameter to the test route and make an HTTP GET request to localhost:8080/test, the request hangs, the route is not called/executed, and maybe but not always the following is returned:
BODY: OK STATUS CODE: UNKNOWN 43
The second case is when I try to overcome this by using the #Autowire annotation. If I remove the HttpServletResponse/HttpServletRequest method parameters and instead autowire them in as class members, I get the same behavior.
If I make a request to any other invalid/undefined route e.g. HTTP GET localhost:8080/undefinedroute I receive the expected 404.
package hello;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
//#Autowired
//HttpServletRequest request;
//#Autowired
//HttpServletResponse response;
#RequestMapping("/test")
public String index() {
return HttpStatus.I_AM_A_TEAPOT.getReasonPhrase();
}
//#RequestMapping("/test")
//public String index(HttpServletResponse response) {
//response.setStatus(HttpStatus.I_AM_A_TEAPOT.ordinal());
//return HttpStatus.I_AM_A_TEAPOT.getReasonPhrase();
//}
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
You cannot autowire HttpServletRequest or HttpServletResponse, because these objects are created when the server receives and handles an HTTP request. They are not beans in the Spring application context.
The status code of your response is 43 (unknown) because of this line:
response.setStatus(HttpStatus.I_AM_A_TEAPOT.ordinal()); // status 43?
ordinal() gives you the position of enum declaration, not the value of the status code. I_AM_A_TEAPOT is the 43rd enum declared in HttpStatus. The request hangs because 43 is an invalid status code and your browser does not know how to deal with it. You should use:
response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); // status 418