I have a server and simple auth using controller. How to get request session and modify response body before running the controller method?
I want to check(for only methods marked by my own annotation) if session have an attribute (login) or not, and to send my error response body or go to the controller.
I tried to use aspectj and interceptor but as a result I can manage session after the controller method has been completed. Because interceptor preHandle method runs before aspectJ #Before.
#Retention(RUNTIME)
#Target({ TYPE, METHOD })
public #interface CheckSession {
}
-
#Aspect
#Component
public class CheckSessionAspect {
public CheckSessionAspect() {
super();
}
#Before("#annotation(path.to.CheckSession)")
public void check(JoinPoint joinPoint) throws Throwable {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
requestAttributes.setAttribute(SESSION_MARKER, "1", RequestAttributes.SCOPE_REQUEST);
}
}
-
#ControllerAdvice
public class ResponseModifierAdvice implements ResponseBodyAdvice<Object> {
#Autowired
ErrorService errorService;
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
if (servletRequest.getAttribute(SESSION_MARKER) != null)
if (servletRequest.getSession().getAttribute(LOGIN) == null || StringUtils.isBlank(servletRequest.getSession().getAttribute(LOGIN).toString())) {
ResponseEntity<Object> newResponseBody = errorService.sessionError(request, returnType.getMethod().getName(), NO_SESSION, getClass().toString());
body = newResponseBody.getBody();
}
return body;
}
}
I did it. You can use #Around and return ResponseEntity object if the user has no session.
#Aspect
#Component
public class CheckSessionAspect {
public CheckSessionAspect() {
super();
}
#Around("#annotation(path.to.CheckSession)")
public Object check(ProceedingJoinPoint pjp) throws Throwable {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
if ((request.getSession().getAttribute(LOGIN) == null || StringUtils.isBlank(request.getSession().getAttribute(LOGIN).toString())) ||
(request.getSession().getAttribute(IP) == null || StringUtils.isBlank(request.getSession().getAttribute(IP).toString()) ||
request.getSession().getAttribute(IP) != null && !request.getSession().getAttribute(IP).toString().equals(request.getRemoteAddr())))
{
requestAttributes.setAttribute(WRONG_SESSION_MARKER, "1", RequestAttributes.SCOPE_REQUEST);
return new ResponseEntity<Object>( new YourResponse(ERROR, "Invalid session"), HttpStatus.BAD_REQUEST);
}
Object proceed = pjp.proceed();
return proceed;
}
}
Related
Consider the below Controller method,
#GetMapping("/getdata")
public Data getDetails() {
try {
Data obj = template.getForObject("http://localhost:8090/details/get", Data.class);
return obj;
} catch (Exception e) {
.....
}
}
Let's say the response object for the above endpoint looks like,
data : {
name : "ABCD",
age : "20"
}
Now what I am trying to achieve is to Wrap the entire response object under another object, so the final response should look like,
{
status : "SOMETHING",
response : {
name : "ABCD",
age : "20"
},
extra : null
}
So the issue is, I don't want to create a function under each controller method to sent values to this Wrapper Object. My question is, Is there any possibility in Spring Framework that allows me to create a Global Wrapper function somewhere, and it will automatically pick it and wrap the response from the controller?
You will need to implement a ResponseBodyAdvice as a #ControllerAdvice. First, you need to have a model for your generic response.
public class GenericResponse<T> {
private String status;
private String extra;
private T response;
}
Then you need to implement the ResponseBodyAdvice itself:
#ControllerAdvice
public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object response, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
GenericResponse<Object> genericResponse = new GenericResponse<>();
output.setStatus("SOMETHING"); // I guess that you need some logic here
output.setResponse(response);
return genericResponse;
}
}
In my spring rest application I need to read the headers information for PUT and POST calls and set those information in the bean passed as #RequestBody. currently what am doing is like follows.
#RequestMapping(value = "/create", method = RequestMethod.POST)
#ResponseStatus(value = HttpStatus.OK)
#ResponseBody
public ReportRepresentation createDailyReport(#RequestBody ReportEntity reportEntity,
#RequestHeader(value= "FIRST_HEAD1", required = false) boolean isHeaderSet,
#RequestHeader(value= "SECOND_HEAD2", required = false) Long scondHead) {
// Setting the header values into bean properties .
}
So am extracting the headers in all methods(POST and PUT) and setting values in different entities.
My question is is there any way to parse/ override the #RequestBody in method param in global level and set those headers?
You can use RequestBodyAdvice:
For example:
Bean:
#Data
public class MyBean {
private String property;
}
Controller:
#RestController
public class MyController {
#RequestMapping("/")
public MyBean get(#RequestBody MyBean myBean) {
return myBean;
}
}
Advisor:
#ControllerAdvice(annotations = RestController.class)
public class MyRequestBodyAdvisor extends RequestBodyAdviceAdapter {
#Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.getParameterType() == MyBean.class;
}
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
MyBean myBean = (MyBean)body;
List<String> strings = inputMessage.getHeaders().get("X-Property");
myBean.setProperty(strings.get(0));
return myBean;
}
}
Testing:
$ curl localhost:8080 -d '{}' -X POST -H 'X-Property: myProp' -H 'Content-Type: application/json' -s
Output:
{"property":"myProp"}
I have implemented the same as #caco3 mentioned here is my implementation with set the values to bean.
#ControllerAdvice
public class RequestBodyAdviceChain implements RequestBodyAdvice {
.. Other methods
#Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
Class<? extends HttpMessageConverter<?>> converterType) {
HttpHeaders headers = inputMessage.getHeaders();
List<String> emulated = headers.get("FIRST_HEAD1");
Boolean isEmulated = false;
Long emulatedUserId = null;
if (!CollectionUtils.isEmpty(emulated)) {
isEmulated = Boolean.valueOf(emulated.get(0));
}
if (isEmulated) {
List<String> users = headers.get("SECOND_HEAD2");
if (!CollectionUtils.isEmpty(users)) {
emulatedUserId = Long.valueOf(users.get(0));
}
}
if (isEmulated) {
if (setField(body, Is_Emulated_Field, isEmulated)) {
setField(body, EmulatedUserId_FIELD, emulatedUserId);
}
}
return body;
}
/**
* <p>
* Method to set the field value for the emulated user and it's id wven
* though if the fields are defined in the super class.
*/
private static boolean setField(Object targetObject, String fieldName, Object fieldValue) {
Field field;
try {
field = targetObject.getClass().getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
field = null;
}
Class superClass = targetObject.getClass().getSuperclass();
while (field == null && superClass != null) {
try {
field = superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
superClass = superClass.getSuperclass();
}
}
if (field == null) {
return false;
}
field.setAccessible(true);
try {
field.set(targetObject, fieldValue);
return true;
} catch (IllegalAccessException e) {
return false;
}
}
}
How do I configure Spring Boot to return 204 in GET methods (typically findAll methods) when the method does not fetch records? I would not like to do treatment in each method, type the code below:
if(!result)
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
return new ResponseEntity<Void>(HttpStatus.OK)
I'd like to transform this method:
#GetMapping
public ResponseEntity<?> findAll(){
List<User> result = service.findAll();
return !result.isEmpty() ?
new ResponseEntity<>(result, HttpStatus.OK) : new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
In this one:
#GetMapping
public List<User> findAll(){
return service.findAll();
}
If the result from findAll() is empty or null then my controller should return 204 instead of 200.
You could register a custom ResponseBodyAdvice which allows customizing the response of #ResponseBody or ResponseEntity handler methods (right before the content is being serialized by a MessageConverter):
#ControllerAdvice
class NoContentControllerAdvice implements ResponseBodyAdvice<List<?>> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return List.class.isAssignableFrom(returnType.getParameterType());
}
#Override
public List<?> beforeBodyWrite(List<?> body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (body.isEmpty()) {
response.setStatusCode(HttpStatus.NO_CONTENT);
}
return body;
}
}
I've got multiple controllers for RESTful endpoints which currently return null if there's no resource at the endpoint. For instance,
#RequestMapping(method = ReqeustMethod.GET, value = "{id}")
#ResponseBody
public MyResource get(#PathVariable final Long id) {
return this.myService.get(id); // returns null if bad id
}
I want to return a specific, different resource to the client (ErrorResource) when there's no MyResource with the given id. I know I can do that with a separate method with #ExceptionHandler, such as:
#RequestMapping(method = RequestMethod.GET, value = "{id}")
#ResponseBody
public MyResource get(#PathVariable final Long id) {
final MyResource myResource = this.myService.get(id);
if (myResource == null) {
throw new NotFoundException();
}
return myResource;
}
#ExceptionHandler(NotFoundException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResource notFoundException(
final HttpServletRequest request,
final NotFoundException exception) {
final ErrorResource errorResource = new ErrorResource();
errorResource.setStatus(HttpStatus.NOT_FOUND.value());
errorResource.setDeveloperMessage("No resource found at " + request.getRequestURL());
return errorResource;
}
And that's nice. But what I'd really like to be able to do is have some kind of interceptor that figures out for me that whenever an API method is returning a null #ResponseBody, it should instead run the logic in my notFoundException() method. That will make all my controller methods a little cleaner. Is there any way to do that?
It sounds like a job for Spring's HttpMessageConverter.
You can write your own converter by implementing HttpMessageConverter<T> interface.
In your case I would implement a converter of HttpMessageConverter<MyResource> with a null check on the MyResource instance in the write method. If the MyResource instance is null, then build and write your ErrorResource instance.
Here is an example:
import java.io.IOException;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
public class MyResourceConverter implements HttpMessageConverter<MyResource> {
// a real message converter that will respond to ancillary methods and do the actual work
private HttpMessageConverter<Object> delegateConverter;
public MyResourceConverter(HttpMessageConverter<Object> delegateConverter){
this.delegateConverter = delegateConverter;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canRead(clazz, mediaType) && MyResource.class.equals(clazz);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canWrite(clazz, mediaType) && MyResource.class.equals(clazz);
}
#Override
public MyResource read(Class<? extends MyResource> clazz,
HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return (MyResource) delegateConverter.read(clazz, inputMessage);
}
#Override
public void write(MyResource t, MediaType contentType,
HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
Object result = null;
if(t == null){
result = // build your ErrorResource here
}else{
result = t;
}
delegateConverter.write(result, contentType, outputMessage);
}
}
Note that this converter needs to be registered in your Spring configuration.
The configuration class must extend WebMvcConfigurerAdapter and override the configureMessageConverters method, like:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// Here we add our custom-configured HttpMessageConverters.
// yourDelegateConverter may be a MappingJackson2HttpMessageConverter instance for example
converters.add(new EmployeeConverter(yourDelegateConverter));
super.configureMessageConverters(converters);
}
References (from official Spring documentation):
HTTP Message conversion
HttpMessageConverter
WebMvcConfigurerAdapter
I'm creating a Spring 4 REST application, using MappingJackson2XmlHttpMessageConverter to convert incoming XML requests to domain objects. Is there any way to apply XSD validation in that process? If not, I think my fallback is to just make the #RequestBody a String, parse and validate it, and then convert it to the domain object. Is there a better approach?
One approach to this may be to write a custom HttpMessageConverter<T> that checks XSD validation (look here for a way to validate XML with XSD) before returning the object.
Suppose that you have the following method in your Controller class:
#RequestMapping(value = "/")
public CustomObject getCustomObject(#RequestParam(value = "id") String id){
return new CustomObject();
}
Then your converter may look like this:
public class CustomObjectConverter implements HttpMessageConverter<CustomObject> {
// a real message converter that will respond to ancillary methods and do the actual work
protected HttpMessageConverter<Object> delegateConverter;
public CustomObjectConverter (HttpMessageConverter<Object> delegate) {
super(delegate, personService);
super.delegateConverter = delegate;
this.employeePhotoBaseUrl = employeePhotoBaseUrl;
}
#Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canRead(clazz, mediaType) && CustomObject.class.equals(clazz);
}
#Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return delegateConverter.canWrite(clazz, mediaType) && CustomObject.class.equals(clazz);
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return delegateConverter.getSupportedMediaTypes();
}
#Override
public CustomObject read(Class<? extends CustomObject> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return (CustomObject) delegateConverter.read(clazz, inputMessage);
}
#Override
public void write(CustomObject t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
if(validationOK)
delegateConverter.write(t, contentType, outputMessage);
else
// You may implement a custom exception handler to return a proper HTTP error code
throw new YourCustomException();
}
}
Remember to configure your new converter. I do this in my configuration class:
#Configuration
#EnableWebMvc
public class RestConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// initialize your MappingJackson2XmlHttpMessageConverter
MappingJackson2XmlHttpMessageConverter xmlMessageConverter = xmlMessageConverter();
//Here we add our custom-configured HttpMessageConverters
converters.add(new CustomObjectConverter(xmlMessageConverter));
super.configureMessageConverters(converters);
}
}