How to extract a variable value in Spring AOP advise - java

The authentication method has been integrated with every REST calls in the API. I have been trying to implement an authentication method via Spring AOP so that I can remove all the duplicate code from end-points and have one single advise to look for all public methods in Controllers.
Please check the below my code,
#Aspect
public class EndpointAccessAspect {
/**
* All the request mappings in controllers need to authenticate and validate end-point access
*/
#Before("execution(public * com.xxxx.webapi.controllers.MenuController.getCategory(HttpServletRequest)) && args(request)")
public void checkTokenAccess(HttpServletRequest request){
String re =(String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println(" %%%%%%%%%%%%%%%%%%% checkTokenAccess %%%%%%%%%%%%%%%" + re);
}
public void checkEndPointPermission(){
System.out.println(" $$$$$$$$$$$$$$$$$$ checkEndPointPermission &&&&&&&&&&&&&");
}
}
However, I saw Intelij gives error near getCategory(HttpServletRequest)) && args(request) saying can not resolve symbol HttpServletRequest. I need the request to distingues each REST end-points. There are more variables than HttpServletRequest variable in the method but only that variable is needed.
The code is compiling when I test the functionality I noticed it doesn't reach to the advise. Can anybody help me to fix this?
I found this from Spring documentation
Spring doc
any join point (method execution only in Spring AOP) which takes a
single parameter, and where the argument passed at runtime is
Serializable
Does this mean I can not use methods that have multiple parameters?
Controller end-point
#RequestMapping(value = "{menuId}/categories/{categoryId}", method = RequestMethod.GET)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Successful retrieval of a category requested", response = ProductGroupModel.class),
#ApiResponse(code = 500, message = "Internal server error") })
public ProductGroupModel getCategory(
#ApiParam(name = "menuId", value = "Numeric value for menuId", required = true) #PathVariable(value = "menuId") final String menuId,
#ApiParam(name = "categoryId", value = "Numeric value for categoryId", required = true) #PathVariable(value = "categoryId") final String categoryId,
final HttpServletRequest request) {

The following syntax, resolved the above issue. Basically, I had to modify the code to deal with multiple parameters in the advise.
#Before("execution(public * com.xxxx.webapi.controllers.MenuController.getCategory( HttpServletRequest,..)) && args(request, ..)")
public void checkTokenAccess(HttpServletRequest request){
String re =(String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println(" %%%%%%%%%%%%%%%%%%% checkTokenAccess %%%%%%%%%%%%%%%" + re);
}

Related

Spring Webflux/Reactive in netty webserver returns 400 instead of 404 for missing path PARAM

I am using spring boot 2.6.6 and Spring Webflux 5.3.18 jar for handling my rest services in Reactive approach on Nettey server.
while validating the rest end point {{baseUrl}}/person/id,I am passing null/empty for path param "id"
{{baseUrl}}/person/
and expecting 404 but I am getting 400. the code not even reaching to GlobalExceptionHandler? Do i need to any additional logic using Filter to Handle the 400 and propagate to GlobalExceptionHandler? how to handle this?
Interface:
Mono<ResponseEntity<Person>> getPerson(
#Parameter(name = "id", description = "The Person identifier.", required = true) #PathVariable("id") String id,
#Parameter(hidden = true) final ServerWebExchange exchange
);
ExceptionHandler:
#Slf4j
#Component
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
#Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
if (ex instanceof WebClientResponseException && ((WebClientResponseException)ex).getStatusCode() == HttpStatus.NOT_FOUND){
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return writeErrorResponse(exchange, "NOT_FOUND");
}
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
return writeErrorResponse(exchange, "INTERNAL_SERVER_ERROR");
}
private Mono<Void> writeErrorResponse(ServerWebExchange exchange,String errorMessage) {
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
return exchange.getResponse().writeWith(Mono.just(bufferFactory.wrap(errorMessage.getBytes())));
}
}
Id is required(is set to required in your code), therefore you receive bad request (400) before even accessing function.
Try to set required = false on id then to manage response status.
By the way 'required = true' is set by default, so you do not need to write it.

FeignClient API with different response class

I am using Jhipster. I have a yaml file, then generate java code using jhipster openapi-client. It generate several files, including the all the model class needed (to contain the request and response).
DefaultApiClient
#FeignClient(name="${default.name:default}", url="${default.url:https://test.api.com/testing}", configuration = ClientConfiguration.class)
public interface DefaultApiClient extends DefaultApi {
}
DefaultApi
#javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2021-01-22T14:50:31.377193700+08:00[Asia/Singapore]")
#Validated
#Api(value = "Default", description = "the Default API")
public interface DefaultApi {
/**
* POST /req/v1 : This is the request
*
* #param authorization JWT header for authorization (required)
* #param body (required)
* #return successful operation (status code 200)
* or server cannot or will not process the request (status code 400)
*/
#ApiOperation(value = "This is the request", nickname = "Verification", notes = "", response = ResponseType.class, authorizations = {
#Authorization(value = "clientID")
}, tags={ })
#ApiResponses(value = {
#ApiResponse(code = 200, message = "successful operation", response = ResponseType.class),
#ApiResponse(code = 400, message = "server cannot or will not process the request", response = ServiceMessagesType.class) })
#RequestMapping(value = "/req/v1",
produces = "application/json",
consumes = "application/json",
method = RequestMethod.POST)
ResponseEntity<ResponseType> Verification(#ApiParam(value = "JWT header for authorization" ,required=true, defaultValue="Bearer REPLACE_THIS_KEY") #RequestHeader(value="Authorization", required=true) String authorization,#ApiParam(value = "" ,required=true ) #Valid #RequestBody RequestType body);
}
I can manage to get the response successfully, but the problem appear when I send a false request, It will response with and Bad Request 400 and crash my program.
As you can see on the swagger annotation #ApiResponse, it return different class.
I am really confuse with it. My question is:
Just for confirm, #ApiResponse is only for documentation, right? Does this code affect the program like when it return code 400, the response will automatically be ServiceMessageType class?
How can I handle different response class? As you can see in the function deffinition, ResponseEntity Verification, it will return ResponseType as the body of ResponseEntity. But when I send an error request to this Api, this Api will return ServiceMessageType. And fyi, the code 400 will give my program an error says "failed and no fallback available" so I think I need an error handle to do it.
For no.2, I already search for the solution in several source
https://programmer.group/feign-call-error-failed-and-no-fallback-available.html
but I don't really get it. I use the fallbackFactory, and it can handle the 400 code exception. But I still really confuse about how to return different response class. And I get the result not in correct structure, as the link said:
By implementing FallbackFactory, you can get the exception thrown by the service in the create method. However, please note that the exception here is encapsulated by Feign, and the exception thrown by the original method cannot be seen directly in the exception information. The abnormal information obtained is as follows: status 500 reading TestService#addRecord(ParamVO); content: {"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}
To illustrate, in this example, the interface return information of the service provider will be uniformly encapsulated in the user-defined class Result, and the content is the above content: {"success":false,"resultCode":null,"message":"/ by zero","model":null,"models":[],"pageInfo":null,"timelineInfo":null,"extra":null,"validationMessages":null,"valid":false}
Please explain to me how it work, or you can give me a link about how it works, I will really appreciate the help.

Spring REST get url path for multiple mapped endpoint

I have REST endpoint with multiple paths as following:
#RequestMapping(method = RequestMethod.POST, path = {"/xxx/yyy", "/zzz"})
#ResponseBody
public Mono<EpcPain> paymentOrder(#RequestHeader(name = "Timeout", defaultValue = "10000") int timeout,
#RequestHeader(name = "X-Request-Id", required = false) String xRequestId) {
...
}
How can I resolve if request path was xxx/yyy or zzz? I do not want to duplicate this endpoint nor pass some params. I am looking for some spring code magic.
org.springframework.web.context.request.RequestContextHolder may be used to get the path
import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE;
import static org.springframework.web.servlet.HandlerMapping.LOOKUP_PATH;
import static org.springframework.web.servlet.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
and
#RequestMapping(value = {"/getDetails","/getDetailsMore"}, method = RequestMethod.GET)
public String getCustomerDetails(TestFormBean bean) {
RequestAttributes reqAttributes = RequestContextHolder.currentRequestAttributes();
System.out.println(reqAttributes.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, 0));
System.out.println(reqAttributes.getAttribute(LOOKUP_PATH, 0));
System.out.println(reqAttributes.getAttribute(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, 0));
return "test";
}}
All three prints the path.
Here 0 - is request scope and 1 - is session scope.
Hope this helps.
You could add ServerHttpRequest as a method argument and then get the URI for the current request using getURI(). It should work for both Spring MVC and Spring WebFlux.
Have a look at the handler methods documentation for details.

MD5 hashcode using spring for http request

i want to generate unique md5 for every http request that will hit REST API.
So far i have just used String requestParameters but actual httpRequest will have many other things.
How can i achieve this ?
public final class MD5Generator {
public static String getMd5HashCode(String requestParameters) {
return DigestUtils.md5DigestAsHex(requestParameters.getBytes());
}
}
My Controller
#RequestMapping(value = { "/dummy" }, method = RequestMethod.GET)
public String processOperation(HttpServletRequest request) {
serviceLayer = new ServiceLayer(request);
return "wait operation is executing";
}
Service layer
private String httpRequestToString() {
String request = "";
Enumeration<String> requestParameters = httpRequest.getParameterNames();
while (requestParameters.hasMoreElements()) {
request += String.valueOf(requestParameters.nextElement());
}
if (!request.equalsIgnoreCase(""))
return request;
else {
throw new HTTPException(200);
}
}
private String getMD5hash() {
return MD5Generator.getMd5HashCode(httpRequestToString());
}
Do you see any issues with generating an UUID for every request and use that instead?
For example, you could generate the UUID and attach it to the request object if you need it during the request life-cycle:
String uuid = UUID.randomUUID().toString();
request.setAttribute("request-id", uuid);
You can combine request time (System.currentTimeMillis()) and remote address from HttpServletRequest. However, if you're expecting high loads, multiple requests may arrive from a particular client in the same millisecond. To overcome this situation, you may add a global atomic counter to your String combination.
Once you generate an MD5 key, you can set it in ThreadLocal to reach afterwards.
You can do this but in future maybe. I search and not found automated way to achieve this
#GetMapping("/user/{{md5(us)}}")

How to handle exceptions in Spring MVC differently for HTML and JSON requests

I'm using the following exception handler in Spring 4.0.3 to intercept exceptions and display a custom error page to the user:
#ControllerAdvice
public class ExceptionHandlerController
{
#ExceptionHandler(value = Exception.class)
public ModelAndView handleError(HttpServletRequest request, Exception e)
{
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
}
But now I want a different handling for JSON requests so I get JSON error responses for this kind of requests when an exception occurred. Currently the above code is also triggered by JSON requests (Using an Accept: application/json header) and the JavaScript client doesn't like the HTML response.
How can I handle exceptions differently for HTML and JSON requests?
The ControllerAdvice annotation has an element/attribute called basePackage which can be set to determine which packages it should scan for Controllers and apply the advices. So, what you can do is to separate those Controllers handling normal requests and those handling AJAX requests into different packages then write 2 Exception Handling Controllers with appropriate ControllerAdvice annotations. For example:
#ControllerAdvice("com.acme.webapp.ajaxcontrollers")
public class AjaxExceptionHandlingController {
...
#ControllerAdvice("com.acme.webapp.controllers")
public class ExceptionHandlingController {
The best way to do this (especially in servlet 3) is to register an error page with the container, and use that to call a Spring #Controller. That way you get to handle different response types in a standard Spring MVC way (e.g. using #RequestMapping with produces=... for your machine clients).
I see from your other question that you are using Spring Boot. If you upgrade to a snapshot (1.1 or better in other words) you get this behaviour out of the box (see BasicErrorController). If you want to override it you just need to map the /error path to your own #Controller.
As you have the HttpServletRequest, you should be able to get the request "Accept" header. Then you could process the exception based on it.
Something like:
String header = request.getHeader("Accept");
if(header != null && header.equals("application/json")) {
// Process JSON exception
} else {
ModelAndView mav = new ModelAndView("/errors/500"));
mav.addObject("exception", e);
return mav;
}
Since i didn't find any solution for this, i wrote some code that manually checks the accept header of the request to determine the format. I then check if the user is logged in and either send the complete stacktrace if he is or a short error message.
I use ResponseEntity to be able to return both JSON or HTML like here.
Code:
#ExceptionHandler(Exception.class)
public ResponseEntity<?> handleExceptions(Exception ex, HttpServletRequest request) throws Exception {
final HttpHeaders headers = new HttpHeaders();
Object answer; // String if HTML, any object if JSON
if(jsonHasPriority(request.getHeader("accept"))) {
logger.info("Returning exception to client as json object");
headers.setContentType(MediaType.APPLICATION_JSON);
answer = errorJson(ex, isUserLoggedIn());
} else {
logger.info("Returning exception to client as html page");
headers.setContentType(MediaType.TEXT_HTML);
answer = errorHtml(ex, isUserLoggedIn());
}
final HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return new ResponseEntity<>(answer, headers, status);
}
private String errorHtml(Exception e, boolean isUserLoggedIn) {
String error = // html code with exception information here
return error;
}
private Object errorJson(Exception e, boolean isUserLoggedIn) {
// return error wrapper object which will be converted to json
return null;
}
/**
* #param acceptString - HTTP accept header field, format according to HTTP spec:
* "mime1;quality1,mime2;quality2,mime3,mime4,..." (quality is optional)
* #return true only if json is the MIME type with highest quality of all specified MIME types.
*/
private boolean jsonHasPriority(String acceptString) {
if (acceptString != null) {
final String[] mimes = acceptString.split(",");
Arrays.sort(mimes, new MimeQualityComparator());
final String firstMime = mimes[0].split(";")[0];
return firstMime.equals("application/json");
}
return false;
}
private static class MimeQualityComparator implements Comparator<String> {
#Override
public int compare(String mime1, String mime2) {
final double m1Quality = getQualityofMime(mime1);
final double m2Quality = getQualityofMime(mime2);
return Double.compare(m1Quality, m2Quality) * -1;
}
}
/**
* #param mimeAndQuality - "mime;quality" pair from the accept header of a HTTP request,
* according to HTTP spec (missing mimeQuality means quality = 1).
* #return quality of this pair according to HTTP spec.
*/
private static Double getQualityofMime(String mimeAndQuality) {
//split off quality factor
final String[] mime = mimeAndQuality.split(";");
if (mime.length <= 1) {
return 1.0;
} else {
final String quality = mime[1].split("=")[1];
return Double.parseDouble(quality);
}
}
The trick is to have a REST controller with two mappings, one of which specifies "text/html" and returns a valid HTML source. The example below, which was tested in Spring Boot 2.0, assumes the existence of a separate template named "error.html".
#RestController
public class CustomErrorController implements ErrorController {
#Autowired
private ErrorAttributes errorAttributes;
private Map<String,Object> getErrorAttributes( HttpServletRequest request ) {
WebRequest webRequest = new ServletWebRequest(request);
boolean includeStacktrace = false;
return errorAttributes.getErrorAttributes(webRequest,includeStacktrace);
}
#GetMapping(value="/error", produces="text/html")
ModelAndView errorHtml(HttpServletRequest request) {
return new ModelAndView("error.html",getErrorAttributes(request));
}
#GetMapping(value="/error")
Map<String,Object> error(HttpServletRequest request) {
return getErrorAttributes(request);
}
#Override public String getErrorPath() { return "/error"; }
}
References
ModelAndView -- return type for HTML
DefaultErrorAttributes -- data used to render HTML template (and JSON response)
BasicErrorController.java -- Spring Boot source from which this example was derived
The controlleradvice annotation has several properties that can be set, since spring 4. You can define multiple controller advices applying different rules.
One property is "annotations. Probably you can use a specific annotation on the json request mapping or you might find another property more usefull?
Use #ControllerAdvice
Let the exception handler send a DTO containing the field errors.
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
This code is of this website:http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
Look there for more info.

Categories

Resources