Custom http code with Spring MVC - java

I use the following code to handle rest calls using Spring MVC.
#RequestMapping(value = "login", method = RequestMethod.GET)
public #ResponseBody
User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
...
return user;
}
I would like to send the client customer http codes for wrong username, wrong passwords, password changed and password expire conditions. How can I modify the existing code to send these error codes to the client?

You can use controller advice to map exception thrown within controller to some client specific data at runtime.
For example if user is not found, your controller should throw some exception (custom or existed one)
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UserNotFoundException(username); //or another exception, it's up to you
return user;
}
}
Then you should add #ControllerAdvice that will catch controller exceptions and make 'exception-to-status' mapping (pros: you will have single point of responsibility for 'exception-to-status-mapping'):
#ControllerAdvice
public class SomeExceptionResolver {
#ExceptionHandler(Exception.class)
public void resolveAndWriteException(Exception exception, HttpServletResponse response) throws IOException {
int status = ...; //you should resolve status here
response.setStatus(status); //provide resolved status to response
//set additional response properties like 'content-type', 'character encoding' etc.
//write additional error message (if needed) to response body
//for example IOUtils.write("some error message", response.getOutputStream());
}
}
Hope this helps.

One way is to add some additional classes for returning HTTP error. Your code will looks like this:
#RequestMapping(value = "login", method = RequestMethod.GET)
#ResponseBody
public User login(#RequestParam String username, #RequestParam String password) {
User user = userService.login(username, password);
if (user == null)
throw new UnauthorizedException();
return user;
}
}
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class UnauthorizedException extends RuntimeException{
}
In this case user will get 401 response status code
I hope it helps

You can return an HTTP 500 or code of your choosing (from the org.springframework.http.HttpStatus enumeration) and use a custom error to emulate something like a SOAP fault within the JSON response.
For example:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(YourTargetException.class)
#ResponseBody
Fault caughtYourTargetException(HttpServletRequest req, Exception ex) {
String code = ex.getClass().getName();
String reason = "Caught YourTargetException."
return new Fault(code, reason);
}
The Fault class could look something like this (inspired by http://www.w3.org/TR/soap12-part1/#soapfault):
/**
* A Fault is an object that can be serialized as JSON when expected errors occur.
*/
public class Fault {
#JsonProperty("faultCode")
private final String code;
#JsonProperty("faultReason")
private final String reason;
#JsonProperty("faultDetails")
private final List<String> details = new ArrayList<>();
public Fault(String code, String reason) {
this.code = code;
this.reason = reason;
}
public Fault(String code, String reason, String... detailEntries) {
this.code = code;
this.reason = reason;
details.addAll(Arrays.asList(detailEntries));
}
public String getCode() {
return code;
}
public String getReason() {
return reason;
}
/**
* Zero or more details may be associated with the fault. It carries
* additional information relative to the fault. For example, the Detail
* element information item might contain information about a message
* not containing the proper credentials, a timeout, etc.
* #return Zero or more detail entries.
*/
public Iterable<String> getDetails() {
return details;
}
#Override
public String toString() {
return String.format("Fault %s occurred. The reason is %s.", getCode(),
getReason());
}
}
You could use one of the existing SOAPFaults in Java frameworks, but I have found they don't play well in REST. Creating my own simple version turned out to be simpler.

You can define your own status code and returning objects. In your code throw custom exceptions and then define an exception handler as follows:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(MyException.class)
public ResponseEntity<MyRetObject> handleControllerError(HttpServletRequest req, MyException ex) {
LOG.warn("My error", ex);
MyRetObject errorMessage = new MyRetObject(ex.getMessage());
return ResponseEntity.status(600).body(errorMessage);
}
}
In your case replace MyExeption.class by UserNotFoundException.class and build your customer error response object and error code

Related

JWT add different subject depending on the data in the body

I've been asked to generate a token depending on the username that is asking for it. Now I'm creating a token just with a single subject but I don't know how to change the subject dinamically before creating the token depending on the body of the request.
This is what I've done so far to generate a token with a single subject:
The service class:
#Component
#RequiredArgsConstructor
public class JwtService {
#Value("${issuer}")
private String issuer;
#Value("${kid}")
private String keyId;
#Value("#{'${audience}'.split(',')}")
private List<String> audiences;
#Value("#{'${subject}'.split(',')}")
private List<String> subject;
private final JwtKeyProvider jwtKeyProvider;
public String generateToken() throws JoseException {
JwtClaims claims = new JwtClaims();
claims.setIssuer(issuer);
claims.setAudience(Lists.newArrayList(audiences));
claims.setExpirationTimeMinutesInTheFuture(60);
claims.setJwtId(keyId);
claims.setIssuedAtToNow();
claims.setNotBeforeMinutesInThePast(0);
claims.setSubject(subject);
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toJson());
jws.setHeader("typ", "JWT");
jws.setKey(jwtKeyProvider.getPrivateKey());
jws.setKeyIdHeaderValue(keyId);
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
String jwt = jws.getCompactSerialization();
return jwt;
}
}
And the controller:
#RestController
#RequiredArgsConstructor
public class JWTController {
private final JwtService jwtService;
#PostMapping("/getToken")
public ResponseEntity getJwt(#RequestBody JwtRequest request) throws JoseException {
return ResponseEntity.ok(
JwtResponse.builder()
.token(jwtService.generateToken())
.build()
);
}
}
I could do it doing like this:
#PostMapping("/getToken")
public ResponseEntity getJwt(#RequestBody JwtRequest request) throws JoseException {
return ResponseEntity.ok(
JwtResponse.builder()
.token(jwtService.generateToken(request.getUsername()))
.build()
);
}
}
But I don't want to send any parameters in the generateToken function as I would have to change a lot of code then.
To resume I want to assign to the subject the value of the username that is sent in the body. So is there a way in the JwtService class to receive that username and set as the subject after?
Thanks in advance!
First you need to put whitelist=user1,user2 in your application.properties, because sometimes names might trigger as system variables (for example username does)
Then in JWTController you need to check if not user equals, but contains in list
#Value("#{'${whitelist}'.split(',')}")
private List<String> whitelist;
#PostMapping("/getToken")
public ResponseEntity<?> getJwt(#RequestBody JwtRequest request) throws JoseException {
if(whitelist.contains(request.username())) {
return ResponseEntity.ok(
JwtResponse.builder()
.token(jwtService.generateToken(request.username()))
.build()
);
} else {
return ResponseEntity.ok("Invalid username");
}
}
In your JWTService you need to set JWT Subject to username which passed through whitelist
claims.setSubject(username);
And finally you need to do JSON request to server
{
"username": "user2"
}

Handle an exception in another method without losing the request objects from original one

There is an Exception that is thrown when an user is not found in database and i'd like to handle that particular exception from the controller perspective layer in a separated method by #ExceptionHandler annotation without losing the original data sent by the user. Well, so, i'm using Sessions and my first attempt was trying to get the object back from it by HttpServletRequest but i got:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'pontoEditar' available as request attribute
The code:
#ExceptionHandler(ConversionFailException.class)
public String handleConversionFailException(HttpServletRequest request, RedirectAttributes attr) {
PontoEditarDTO ponto = (PontoEditarDTO) request.getAttribute("pontoEditar");
// I'd like to get the original object back ...
return "pontos/editar";
}
How would it be if i use a try-catch block
#PostMapping
public String editar(#ModelAttribute("pontoEditar") PontoEditarDTO ponto, HttpSession session) {
// ... simplified.
Ponto pontoConvertido = null;
try {
pontoConvertido = pontoConverter.convert(ponto);
catch (ConversionFailException ex) {
attr.addFlashAttribute("error", "User not found!");
return "redirect:/ponto/listar";
}
// ...
return "redirect:/ponto/listar";
}
Here the simplified code:
public class ConversionFailException extends RuntimeException {
public ConversionFailException(String mensagem) {
super(mensagem);
}
}
Controller with POST.
The exception happens in the POST at line with: Ponto pontoConvertido = pontoConverter.convert(ponto);
#Controller
#SessionAttributes("pontoEditar")
#RequestMapping("/ponto/editar")
public class PontoEditarController {
// ... GET Removed.
#PostMapping
public String editar(#ModelAttribute("pontoEditar") PontoEditarDTO ponto, HttpSession session) {
// ... simplified.
Ponto pontoConvertido = pontoConverter.convert(ponto);
// ...
return "redirect:/ponto/listar";
}
#ExceptionHandler(ConversionFailException.class)
public String handleConversionFailException(HttpServletRequest request, RedirectAttributes attr) {
attr.addFlashAttribute("falha", "Usuário não foi encontrado");
/* I tried but it failed, how can i get ? */
PontoEditarDTO ponto = (PontoEditarDTO) request.getAttribute("pontoEditar");
return "pontos/editar";
}
#GetMapping("pontoEditar")
public PontoEditarDTO getPontoModel() {
return new PontoEditarDTO();
}
}
You can add WebRequest (or HttpSession, etc...) as a parameter in your exception handler, it will be injected by Spring.
You can have a look at the documentation here to see what parameter can be injected by Spring when the handler is called.

How to send Status Codes Along with my Custom Class Using Spring?

I am trying to make a log in system using spring. Problem is if username is not in the database I want to send a different status code and if username is in the database but password is wrong I want to send different status code. Because in my front end i am going to inform user using different alerts according to status code.
I cannot use HttpStatus.NOT_ACCEPTABLE or something like that because my controller is returning a User(my custom class). It will either return User or null.
#GetMapping("/users")
public User userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return null;
}
if(user.getPassword().equals(password)) {
return user;
} else {
return null;
}
}
Here I am trying to change status while returning nulls.
you can return ResponseEntity to meet your requirement
#GetMapping("/users")
public ResponseEntity<User> userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return new ResponseEntity<>(null,HttpStatus.NOT_FOUND);
}
if(user.getPassword().equals(password)) {
return new ResponseEntity<>(user,HttpStatus.OK);
} else {
return new ResponseEntity<>(null,HttpStatus.FORBIDDEN);
}
}
Spring 5 introduced the ResponseStatusException class. We can create an instance of it providing an HttpStatus and optionally a reason and a cause:
#GetMapping(value = "/{id}") public Foo findById(#PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
} }
Maybe this is which you looking for?
Detail in https://www.baeldung.com/exception-handling-for-rest-with-spring#controlleradvice

Define custom error message in REST using Spring on Weblogic

I am hosting Spring Boot app on weblogic 10.3.6 for REST api
I'd like to implement these 2 features:
Whenever my custom exception occurs I would like to send a http response with message e.g. 500 - "request couldn't be parsed because of something..."
Whenever any error is thrown I would like to get the stack trace that is usually printed to console (for debugging purposes)
I tried to solve the first part the following way:
#ControllerAdvice
public class ExceptionHandlerAdvice {
#ExceptionHandler(MyException.class)
public ResponseEntity handleException(MyException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
However as a response I only get 500 Internal Server Error with no message
As for the second part I tried simmilar approach but there was no error message either.
How can I solve this?
EDIT:
The best I could achieve was removing ExceptionHandlerAdvice and using annotation on my exception class instead like this:
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason="This is a message I'd like to send")
It works, but I have to add some variables from code to the message and this method does not allow it
EDIT2:
This is a bit weird, perhaps a behavior of weblogic server, but when I set the httpStatus to HttpStatus.ACCEPTED I can see the message, if it is HttpStatus.Forbidden or any other 4xx error I just get the error without message
Create 'ResponseEntity' object with message and status and return it, it will display with error message.
/**
* HTTP_STATUS - 500 -Service Unavailable.
*
* #param exception
* Catches the following: MyException
* #return
*/
#ExceptionHandler({ MyException.class})
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public static ResponseEntity<?> handleConnectionErrorResponse(MyException exception) {
return new ResponseEntity<String>("Some error occurred at server", HttpStatus.INTERNAL_SERVER_ERROR);
}
Do not return something, throw an :
#ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
public class UnprocessableEntity extends RuntimeException {
public UnprocessableEntity(String string) {
super(string);
}
}
Or another thing like that.
I also went through the same requirement.
Below is the code which is working for me:
String errMsg = "{\"errorMessage\":\"Parking Slot is not available\"}";
return new ResponseEntity<String>(errMsg, HttpStatus.INTERNAL_SERVER_ERROR);
Whereas errMsg should be written in the format which you want. Like I had requirment for response in JSON.
Hope this will help some of you
Well In my case I have done custom error handling logic.
We can define a custom Base Response class wich accepts generic type(Eg: user desired model)
Return BaseResponse as a response for each REST Methods
(GET, PUT, POST, DELETE, etc)
BaseResponse.java
public class BaseResponse<T> {
int statusCode;
String message;
T data;
public int getStatusCode() {
return statusCode;
}
public BaseResponse<T> getValidResponse(String message, T data) {
BaseResponse<T> baseResponse = new BaseResponse<T>();
baseResponse.statusCode = 200;
baseResponse.message = message;
baseResponse.data = data;
return baseResponse;
}
public BaseResponse<T> getErrorResponse(int StatusCode, String message) {
BaseResponse<T> baseResponse = new BaseResponse<T>();
baseResponse.statusCode = StatusCode;
baseResponse.message = message;
return baseResponse;
}
public void setStatusCode(int statusCode) {
this.statusCode = statusCode;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
User.java
public class User {
String userName;
String userAddress;
String userEmail;
String userPhoneNumber;
...
//Getters & Setters
}
YourController.java
....
#PostMapping("/addUser")
public BaseResponse<User> addUser(User user) {
if (user.getUserName() != null && !user.getUserName().equals("")) {
UserEntity userEntity = new UserEntity();
userEntity.setName(user.getUserName());
...
userRepository.save(userEntity);
return new BaseResponse<User>().getValidResponse("Successfully Added User", user);
} else {
return new BaseResponse<User>().getErrorResponse(400, "Name field is required");
}
}
...
#DeleteMapping("/deleteUser/{userId}")
//Using ? for uncertain Response.Eg: Some response might have data response and some may not have data response...
public BaseResponse<?> deleteUser(#PathVariable(value = "userId") int userId) {
//After delete operation...we don't require data response.
return new BaseResponse<User>().getValidResponse("Successfully deleted the User", null);
}
This might not be an exact solution for the question asked but will surely help someone else.

Add a body to a 404 Not Found Exception

In an REST API generated with JHipster, I want to throw some 404 exceptions. It is normally done with
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
which actualy results in a 404 response to the xhr request. The problem is that in the front side, JHipster parses the response with
angular.fromJson(result)
and such result is empty when the 404 is the actual response, which makes the parse to fail.
If I point to an unmapped URI, lets say /api/user while my controller maps to /api/users (note the plural) the 404 I got from the API has a body in it:
{
"timestamp": "2016-04-25T18:33:19.947+0000",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/api/user/myuser/contact"
}
which is correctly parse in angular.
How can I create a body like this? Is this exception thrown by spring or is tomcat who throws it?
I tried this: Trigger 404 in Spring-MVC controller? but I cant set the parameters of the response.
Basic Idea
First option is to define error objects and return them as 404 Not Found body. Something like following:
Map<String, String> errors = ....;
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errors);
Instead of returning a typical ResponseEntity, you can throw an Exception that will be resolved to a 404 Not Found. Suppose you have a NotFoundException like:
#ResponseStatus(code = HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {}
Then if you throw this exception in your controllers, you would see something like:
{
"timestamp":1461621047967,
"status":404,
"error":"Not Found",
"exception":"NotFoundException",
"message":"No message available",
"path":"/greet"
}
If you want to customize the message and other parts of body, you should define a ExceptionHandler for NotFoundException.
Introducing Exception Hierarchies
If you're creating a RESTful API and want to have different Error Codes and Error Messages for different exceptional cases, you can create a hierarchy of exceptions representing those cases and extract message and code from each one.
For example, you can introduce an exception, say, APIException which is super-class of all other exceptions thrown by your controllers. This class defines a code/message pair like:
public class APIException extends RuntimeException {
private final int code;
private final String message;
APIException(int code, String message) {
this.code = code;
this.message = message;
}
public int code() {
return code;
}
public String message() {
return message;
}
}
Each subclass depending on the nature of its exception can provide some sensible values for this pair. For example, we could have an InvalidStateException:
#ResponseStatus(code = HttpStatus.BAD_REQUEST)
public class InvalidStateException extends APIException {
public InvalidStateException() {
super(1, "Application is in invalid state");
}
}
Or that notorious not found ones:
#ResponseStatus(code = HttpStatus.NOT_FOUND)
public class SomethingNotFoundException extends APIException {
public SomethingNotFoundException() {
super(2, "Couldn't find something!");
}
}
Then we should define an ErrorController that catches those exceptions and turn them to meaningful JSON representations. That error controller may look like following:
#RestController
public class APIExceptionHandler extends AbstractErrorController {
private static final String ERROR_PATH = "/error";
private final ErrorAttributes errorAttributes;
#Autowired
public APIExceptionHandler(ErrorAttributes errorAttributes) {
super(errorAttributes);
this.errorAttributes = errorAttributes;
}
#RequestMapping(path = ERROR_PATH)
public ResponseEntity<?> handleError(HttpServletRequest request) {
HttpStatus status = getStatus(request);
Map<String, Object> errors = getErrorAttributes(request, false);
getApiException(request).ifPresent(apiError -> {
errors.put("message" , apiError.message());
errors.put("code", apiError.code());
});
// If you don't want to expose exception!
errors.remove("exception");
return ResponseEntity.status(status).body(errors);
}
#Override
public String getErrorPath() {
return ERROR_PATH;
}
private Optional<APIException> getApiException(HttpServletRequest request) {
RequestAttributes attributes = new ServletRequestAttributes(request);
Throwable throwable = errorAttributes.getError(attributes);
if (throwable instanceof APIException) {
APIException exception = (APIException) throwable;
return Optional.of(exception);
}
return Optional.empty();
}
}
So, if you throw an SomethingNotFoundException, the returned JSON would be like:
{
"timestamp":1461621047967,
"status":404,
"error":"Not Found",
"message":"Couldn't find something!",
"code": 2,
"path":"/greet"
}
I guess you can do this if you want to return some message or test with your error code
#ResponseBody
public ResponseEntity somthing() {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json; charset=utf-8");
return new ResponseEntity<>(new Gson().toJson("hello this is my message"), headers, HttpStatus.NOT_FOUND);
}
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "message");

Categories