How to handle NullBean object when #RequestScope bean could be null - spring

My spring boot application has been secured with keycloak, everything works well except when I'm trying to load current AccessToken from request.
With the following statements the current Token is loaded from RequestContext:
#Bean
#RequestScope(proxyMode = TARGET_CLASS)
public AccessToken accessToken() {
return Optional.ofNullable(
(KeycloakSecurityContext)
RequestContextHolder.currentRequestAttributes()
.getAttribute(KeycloakSecurityContext.class.getName(), SCOPE_REQUEST))
.map(KeycloakSecurityContext::getToken)
.orElse(null);
}
and then injected where requested.
#Resource(name = "accessToken")
private AccessToken accessToken;
Eveything works as expected when user is logged and token found in request, but when the request is anonymous and AccessToken has not been found the we have this error:
Caused by: java.lang.ClassCastException: class org.springframework.beans.factory.support.NullBean cannot be cast to class org.keycloak.representations.JsonWebToken (org.springframework.beans.factory.support.NullBean and org.keycloak.representations.JsonWebToken are in unnamed module of loader 'app')
I can't find the right way to handle with the NullBean object. Null check is always successfully passed, either whth if ==null statement, and exception is thrown.
public Optional<TenantAdapter> current() {
// This is always false
if (accessToken.equals(null)) {
return Optional.empty();
} else {
// and here exception is thrown
Map<String, Object> claims = accessToken.getOtherClaims();
}
}
Do you have any suggestion ?
Thanks
Stefano

Try with
#Autowired(required=false)
#Qualifier("accessToken")
private AccessToken accessToken;
#Autowired(required=false) <-- will accept if the bean does not exist in application context.
#Qualifier("accessToken") <-- will allow only a bean with name accessToken to be picked so that autowire is not allowed to pick only by type if only 1 bean is available. This is needed because you already pick by name only using the #Resource(name = "accessToken").

Related

Spring boot websocket: how to get the current principal programmatically?

By this thread I know that I can access to the principal by passing it as an argument to the method.
Nevetheless I need to access to this information in a transparent way, I tried with:
SecurityContextHolder.getContext().getAuthentication()
But it gives me null. So, isn't there another way?
It seems that, in order to obtain the full reference I have to define a custom channel interceptor:
private static class MyReceiver implements ChannelInterceptor{
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
SimpMessageType type = getType(message);
if(type == SimpMessageType.SUBSCRIBE) {
message.getHeaders().get("simpUser")); //it works here
}
return ChannelInterceptor.super.preSend(message, channel);
}
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
This will give you the current logged-in Username in Spring Security
Note :
UserDetails object is the one that Spring Security uses to keep user-related information.
SecurityContext is used to store the details of the currently authenticated user and SecurityContextHolder is a helper class that provides access to the security context

Validating if request body in HTTP POST request is null in Spring Boot controller

I am replacing manual validation of input to a POST request in a Spring Boot REST-controller. JSR-303 Spring Bean Validation is used for validating the instance variables in the request body and this is working as expected. What is the recommended method to validate that the object in the request body is not null?
I have tried:
annotating the entire object such as this: #NotNull #Valid #RequestBody Foo foo
annotating the entire class with #NotNull
I am replacing:
#PostMapping...
public ResponseEntity<Map<String, Object>> editFoo(
#RequestBody Foo foo, ...) {
if(foo == null) {
return (new ResponseEntity<>(headers, HttpStatus.BAD_REQUEST));
}
}
with a Bean Validation equivalent:
#PostMapping...
public ResponseEntity<Map<String, Object>> editFoo(
#Valid #RequestBody Foo foo, ...) {
...
}
I tried unit testing the controller method by:
// Arrange
Foo foo = null;
String requestBody = objectMapper.writeValueAsString(foo);
// Act + assert
mockMvc
.perform(
post("/end_point")
.contentType("application/json")
.content(requestBody))
.andExpect(status().isBadRequest());
I expected a MethodArgumentNotValidException which is handled by a #ControllerAdvice for this exception, but I get HttpMessageNotReadableException when executing the unit test.
My questions:
is it necessary to test if the request body is null?
if 1. is true, how should this be done with Bean Validation?
Seeing your code, you already check if the body is null. In fact #RequestBody has a default parameter required which defaults to true. So no need for Bean validation for that !
Your main issue here seems to be in your test. First of all it is good to write a test to validate your endpoint behavior on null.
However, in your test you does not pass null. You try to create a Json object from a null value with your objectMapper.
The object you are writting seems not to be a valid json. So when your sending this body, Spring says that it cannot read the message, aka the body of your request, as you say it is a application/json content but there is not json in it.
To test null body, just send your request in your test just removing the .content(requestBody) line and it should work !
--- Edit 1
I thought it was rejecting the message because of the body, but in fact it seems to work right away for me. Here is my controler and test so you can compare to your full code :
#RestController()
#RequestMapping("end_point")
public class TestController {
#PostMapping
public ResponseEntity<Map<String, Object>> editFoo(#RequestBody Foo foo) {
// if(foo == null) {
// return (new ResponseEntity<>(new HashMap<>(), HttpStatus.BAD_REQUEST));
// }
return (new ResponseEntity<>(new HashMap<>(), HttpStatus.OK));
}
}
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#Test
public void test_body_is_null() throws Exception {
Foo foo = null;
String requestBody = objectMapper.writeValueAsString(foo);
// Act + assert
mvc
.perform(
post("/end_point")
.contentType("application/json")
.content(requestBody))
.andExpect(status().isBadRequest());
}
}
This was made using Spring Boot 2.1.6.RELEASE
--- Edit 2
For the record if you want to use validation for null here, here is a snippet of the controller :
#RestController()
#RequestMapping("end_point")
#Validated
public class TestController {
#PostMapping
public ResponseEntity<Map<String, Object>> editFoo(#NotNull #RequestBody(required = false) Foo foo) {
return (new ResponseEntity<>(new HashMap<>(), HttpStatus.OK));
}
}
First you have to set required to false for the body, as default is true. Then you have to add the #NotNull annotation on the request body and #Validated on the controller.
Here if you launch your test you will see that the request fails with :
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: editFoo.foo: must not be null
As you said you had a #ControllerAdvice you can then map the exception as you wish !

spring boot override default REST exception handler

I am not able to override default spring boot error response in REST api. I have following code
#ControllerAdvice
#Controller
class ExceptionHandlerCtrl {
#ResponseStatus(value=HttpStatus.UNPROCESSABLE_ENTITY, reason="Invalid data")
#ExceptionHandler(BusinessValidationException.class)
#ResponseBody
public ResponseEntity<BusinessValidationErrorVO> handleBusinessValidationException(BusinessValidationException exception){
BusinessValidationErrorVO vo = new BusinessValidationErrorVO()
vo.errors = exception.validationException
vo.msg = exception.message
def result = new ResponseEntity<>(vo, HttpStatus.UNPROCESSABLE_ENTITY);
result
}
Then in my REST api I am throwing this BusinessValidationException. This handler is called (I can see it in debugger) however I still got default spring boot REST error message. Is there a way to override and use default only as fallback? Spring Boot version 1.3.2 with groovy. Best Regards
Remove #ResponseStatus from your method. It creates an undesirable side effect and you don't need it, since you are setting HttpStatus.UNPROCESSABLE_ENTITY in your ResponseEntity.
From the JavaDoc on ResponseStatus:
Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.
With HttpServletResponse.sendError, the response is considered complete and should not be written to any further. Furthermore, the Servlet container will typically write an HTML error page therefore making the use of a reason unsuitable for REST APIs. For such cases it is preferable to use a ResponseEntity as a return type and avoid the use of #ResponseStatus altogether.
I suggest you to read this question: Spring Boot REST service exception handling
There you can find some examples that explain how to combine ErrorController/ ControllerAdvice in order to catch any exception.
In particular check this answer:
https://stackoverflow.com/a/28903217/379906
You should probably remove the annotation #ResponseStatus from the method handleBusinessValidationException.
Another way that you have to rewrite the default error message is using a controller with the annotation #RequestMapping("/error"). The controller must implement the ErrorController interface.
This is the error controller that I use in my app.
#RestController
#RequestMapping("/error")
public class RestErrorController implements ErrorController
{
private final ErrorAttributes errorAttributes;
#Autowired
public MatemoErrorController(ErrorAttributes errorAttributes) {
Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
this.errorAttributes = errorAttributes;
}
#Override
public String getErrorPath() {
return "/error";
}
#RequestMapping
public Map<String, Object> error(HttpServletRequest aRequest) {
return getErrorAttributes(aRequest, getTraceParameter(aRequest));
}
private boolean getTraceParameter(HttpServletRequest request) {
String parameter = request.getParameter("trace");
if (parameter == null) {
return false;
}
return !"false".equals(parameter.toLowerCase());
}
private Map<String, Object> getErrorAttributes(HttpServletRequest aRequest, boolean includeStackTrace)
{
RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
} }

How can I get HttpServletRequest and HttpServletResponse object in Spring AOP

I want to get the response object in spring AOP before advice. If the session is invalidate I want to redirect to the login page, but unable to get the HttpServletResponse object in the Before advice method.
Tried with the following way.
#Autowired
private HttpServletResponse response;
public void setResponse(HttpServletResponse response) {
this.response = response;
}
Stacktrace:
caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: javax.servlet.http.HttpServletResponse com.****.****.aspect.LogProvider.response; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No matching bean of type [javax.servlet.http.HttpServletResponse] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:506)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:284)
... 33 more
Any help will be appreciated.
You can get response by under method:
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletResponse response = ((ServletRequestAttributes)requestAttributes).getResponse();
Basically we do redirect from a jsp page i.e. from UI layer we handle such kind of operation(redirection). So, I hope you will be using some restful services in your application. And for most of the restful services we go for Asynchronous request. If it is combination of Asynchronous and restful services; and I am sure you will be using this in your application. If your session is invalid and you try to access perform any operation on 'session' then it will land you in 'IllegalStateException'. For such type of scenario please follow the below centralized 'Exception Handling' mechanism provided by JAX-RS: javax.ws.rs.ext.ExceptionMapper.
Please follow below steps:
step-1: Create a user-defined unchecked exception like MyApplicationException:
public class MyApplicationException extends RuntimeException {
public MyApplicationException() {super();}
// implement other methods of RuntimeException as per your requirement
}
step-2: Create a user-defined type of ExceptionMapper
public class MyApplicationExceptionHandler implements ExceptionMapper<MyApplicationException>
{
#Override
public Response toResponse(MyApplicationException exception)
{
return Response.status(Status.FORBIDDEN).entity(exception.getMessage()).build();
// set any Status code of 4XX as this is client side error not server side
}
}
step-3: In all your ajax request in the UI code check this Status Code and redirect to the login page.
That's it and you are done with a finer implementation. Guaranteed...
/**
* #return the HttpServletResponse handled by the current thread
*/
public static Optional<HttpServletResponse> getThreadLocalResponse() {
return Optional.ofNullable(RequestContextHolder.getRequestAttributes())
.filter(ra -> ra instanceof ServletRequestAttributes)
.map(ServletRequestAttributes.class::cast)
.map(ServletRequestAttributes::getResponse);
}
To get the response object you can use this code:
ServletWebRequest servletWebRequest=new ServletWebRequest(request);
HttpServletResponse response=servletWebRequest.getResponse();
To get the request object:
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getR‌equest();
If you get a null response then I can see the response is not yet formed when the control is returned. Then the only way ahead is to go with interceptors.

Empty Exception Body in Spring MVC Test

I am having trouble while trying to make MockMvc to include the exception message in the response body. I have a controller as follows:
#RequestMapping("/user/new")
public AbstractResponse create(#Valid NewUserParameters params, BindingResult bindingResult) {
if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
// ...
}
where BadRequestException looks sth like this:
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {
public BadRequestException(String cause) { super(cause); }
public static BadRequestException of(BindingResult bindingResult) { /* ... */ }
}
And I run the following test against /user/new controller:
#Test
public void testUserNew() throws Exception {
getMockMvc().perform(post("/user/new")
.param("username", username)
.param("password", password))
.andDo(print())
.andExpect(status().isOk());
}
which prints the following output:
Resolved Exception:
Type = controller.exception.BadRequestException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = bad request
Headers = {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 = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Does anybody have an idea on why is Body missing in the print() output?
Edit: I am not using any custom exception handlers and the code works as expected when I run the server. That is, running the application and making the same request to the server returns back
{"timestamp":1423076185822,
"status":400,
"error":"Bad Request",
"exception":"controller.exception.BadRequestException",
"message":"binding failed for field(s): password, username, username",
"path":"/user/new"}
as expected. Hence, there is a problem with the MockMvc I suppose. It somehow misses to capture the message field of the exception, whereas the default exception handler of the regular application server works as expected.
After opening a ticket for the issue, I was told that the error message in the body is taken care of by Spring Boot which configures error mappings at the Servlet container level and since Spring MVC Test runs with a mock Servlet request/response, there is no such error mapping. Further, they recommended me to create at least one #WebIntegrationTest and stick to Spring MVC Test for my controller logic.
Eventually, I decided to go with my own custom exception handler and stick to MockMvc for the rest as before.
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(Throwable.class)
public #ResponseBody
ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
HttpStatus status = Optional
.ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
.map(ResponseStatus::value)
.orElse(HttpStatus.INTERNAL_SERVER_ERROR);
response.setStatus(status.value());
return new ExceptionResponse(throwable.getMessage());
}
}
#Data
public class ExceptionResponse extends AbstractResponse {
private final long timestamp = System.currentTimeMillis();
private final String message;
#JsonCreator
public ExceptionResponse(String message) {
checkNotNull(message, "message == NULL");
this.message = message;
}
}
This likely means that you either didn't handle the exception or you've really left the body empty. To handle the exception either add an error handler in the controller
#ExceptionHandler
public #ResponseBody String handle(BadRequestException e) {
return "I'm the body";
}
or user the global error handler if you're on 3.2 or above
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler
public #ResponseBody String handleBadRequestException(BadRequestException ex) {
return "I'm the body";
}
}
with this the body will be populate, you should populate it with your error message
Updated solution:
If you don't want to do a full integration test but still want to make sure the message is as expected, you can still do the following:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");

Resources