given the following dto and controller
public class PasswordCredentials implements AuthenticationProvider {
#NotNull
#NotEmpty
#JsonProperty( access = JsonProperty.Access.WRITE_ONLY )
private String user;
#NotNull
#NotEmpty
#JsonProperty( access = JsonProperty.Access.WRITE_ONLY )
private CharSequence pass;
public void setPass( final CharSequence pass ) {
this.pass = pass;
}
public void setUser( final String user ) {
this.user = user;
}
#Override
public Authentication toAuthentication() {
return new UsernamePasswordAuthenticationToken( user, pass );
}
}
#RestController
#RequestMapping( path = "authentication" )
class AuthenticationController {
private final AuthenticationManager am;
AuthenticationController( final AuthenticationManager am ) {
this.am = am;
}
#RequestMapping( path = "password", method = RequestMethod.POST, consumes = {
"!" + MediaType.APPLICATION_FORM_URLENCODED_VALUE
} )
ResponseEntity<?> login( #Valid #RequestBody final PasswordCredentials credentials ) {
Authentication authenticate = am.authenticate( credentials.toAuthentication() );
if ( authenticate.isAuthenticated() ) {
return ResponseEntity.status( HttpStatus.NO_CONTENT ).build();
}
return ResponseEntity.status( HttpStatus.FORBIDDEN ).build();
}
}
if for example pass is null there will be a validation error, and a 400 will happen without ever calling my controller, which is fine. That 400 however has no content, is there any way to have the controllers BindResults output as content so that the consumer of the API knows what caused the problem? Ideally I would not do this in the controller method, so that it would happen on all controllers?
I was able to get this behavior with spring data rest as follows, but I'd like it for all API controllers.
class RestConfig extends RepositoryRestConfigurerAdapter {
#Bean
Validator validator() {
return new LocalValidatorFactoryBean();
}
#Override
public void configureValidatingRepositoryEventListener(
final ValidatingRepositoryEventListener validatingListener ) {
Validator validator = validator();
//bean validation always before save and create
validatingListener.addValidator( "beforeCreate", validator );
validatingListener.addValidator( "beforeSave", validator );
}
#Override
public void configureRepositoryRestConfiguration( final RepositoryRestConfiguration config ) {
config.setBasePath( "/v0" );
config.setReturnBodyOnCreate( false );
config.setReturnBodyOnUpdate( false );
}
Spring have #ControllerAdvice and #ExceptionHandler annotation to handle errors in controllers.
#ControllerAdvice
public class ExceptionTranslator {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Error processValidationError(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
.....
return new Error();
}
// Other exceptions
}
i want to improve the answer of Anton Novopashin: just return the error in response entity.
#ControllerAdvice
public class ExceptionTranslator {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<String> processValidationError(MethodArgumentNotValidException ex) {
return new ResponseEntity(ex.getMessage, HttpStatus.BAD_REQUEST);
}
// Other exceptions
}
I'm not sure who or why downvoted the existing answers but they are both right - the best way to handle validation errors would be to declare a #ControllerAdvice and then handle the exceptions there. Here's a snippet of my global error handler, taken from an existing project:
#ControllerAdvice
#ResponseBody
public class RestfulErrorHandler {
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResponse methodValidationError(MethodArgumentNotValidException e) {
final ErrorResponse response = new ErrorResponse();
for (ObjectError error : e.getBindingResult().getAllErrors()) {
if (error instanceof FieldError) {
response.addFieldError((FieldError) error);
} else {
response.addGeneralError(error.getDefaultMessage());
}
}
return response;
}
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
public ErrorResponse constraintViolationError(ConstraintViolationException e) {
final ErrorResponse response = new ErrorResponse();
for (ConstraintViolation<?> v : e.getConstraintViolations()) {
response.addFieldError(v.getPropertyPath(), v.getMessage());
}
return response;
}
}
You should also process ConstraintViolationExceptions since they too may be thrown. Here's a link to my ErrorResponse class, I'm including it as a Gist so as not to obscure the main point.
You should also probably process the RepositoryConstraintViolationException, I'm not sure if spring-data-rest handles them already.
Related
everyone!
I making a defense against password brute force.
I successfully handle AuthenticationFailureBadCredentialsEvent when the user writes the right login and wrong password. But the problem is that I want to return JSON with two fields
{
message : '...' <- custom message
code : 'login_failed'
}
The problem is that it returns standart forbidden exception, but I need custom json.
#Log4j2
#Component
#RequiredArgsConstructor
public class AuthenticationAttemptsHandler {
protected final MessageSource messageSource;
private final AuthenticationAttemptsStore attemptsStore;
private final UserDetailsService userDetailsService;
private final UserDetailsLockService userDetailsLockService;
#EventListener
public void handleFailure(AuthenticationFailureBadCredentialsEvent event) {
val authentication = event.getAuthentication();
val userDetails = findUserDetails(authentication.getName());
userDetails.ifPresent(this::failAttempt);
}
private Optional<UserDetails> findUserDetails(String username) {
...
}
private void failAttempt(UserDetails details) {
val username = details.getUsername();
val attempt = attempt(loginAttemptsProperties.getResetFailuresInterval());
int failures = attemptsStore.incrementFailures(username, attempt);
if (failures >= 2) {
Instant lockedUntil = Instant.now().plus(loginAttemptsProperties.getLockDuration());
userDetailsLockService.lockUser(username, lockedUntil);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
String date = formatter.format(lockedUntil);
String message = String.format("Account will locked till %s", date);
throw new SecurityException(message);
//FailAttemptsExceptionResponse response = new FailAttemptsExceptionResponse(message, //
//"login_ failed"); <---- tryed return entity from this method. Does not work.
// return new ResponseEntity<>(response,HttpStatus.FORBIDDEN);
} else {
String message = String.format("You have %s attempts.", (3 - failures));
// FailAttemptsExceptionResponse response = new FailAttemptsExceptionResponse(message,
"login_ failed");
throw new SecurityException(message);
// return new ResponseEntity<>(response,HttpStatus.FORBIDDEN);
}
}
}
RuntimeException returns 500 status? but I need forbidden
public class SecurityException extends RuntimeException {
private static final long serialVersionUID = 1L;
public SecurityException(String msg) {
super(msg);
}
}
Responce model
public class FailAttemptsExceptionResponse {
String message;
String code;
public FailAttemptsExceptionResponse(String message, String code) {
super();
this.message = message;
this.code = code;
}
public String getMessage() {
return message;
}
public String getCode() {
return code;
}
}
Tried to handle SecurityException and then returns model? but it does not work
#ControllerAdvice
public class SeurityAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(SecurityException.class)
public ResponseEntity<FailAttemptsExceptionResponse> handleNotFoundException(SecurityException ex) {
FailAttemptsExceptionResponse exceptionResponse = new FailAttemptsExceptionResponse(ex.getMessage(),
"login_ failed");
return new ResponseEntity<FailAttemptsExceptionResponse>(exceptionResponse,
HttpStatus.NOT_ACCEPTABLE);
}
}
I successfully handle AuthenticationFailureBadCredentialsEvent, but how can I return JSON response model from the handler with a custom message?
#ControllerAdvice
public class SeurityAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(SecurityException.class)
public ResponseEntity<FailAttemptsExceptionResponse> handleNotFoundException(SecurityException ex, HttpServletResponse response) {
FailAttemptsExceptionResponse exceptionResponse = new FailAttemptsExceptionResponse(ex.getMessage(),
"login_ failed");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return new ResponseEntity<FailAttemptsExceptionResponse>(exceptionResponse,
HttpStatus.NOT_ACCEPTABLE);
}
}
maybe you need to add HttpServletResponse and set the http status.
Register the entry point
As mentioned, I do it with Java Config. I just show the relevant configuration here, there should be other configuration such as session stateless, etc.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling().authenticationEntryPoint(new CustomEntryPoint());
}
}
U can create AuthenticationEntryPoint.
Короч тут почитай xD
Handle spring security authentication exceptions with #ExceptionHandler
Have a custom error controller on Spring boot:
package com.example.controllers;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.boot.web.servlet.error.ErrorController;
import javax.servlet.http.HttpServletRequest;
#Controller
public class CustomErrorController implements ErrorController
{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
...
}
#Override
public String getErrorPath()
{
return "/error";
}
}
But, when compile says: getErrorPath() in ErrorController has been deprecated. Ok, i found information: use server.error.path property. Ok, add this in application.properties and delete the function, but now says: CustomErrorController is not abstract and does not override abstract method getErrorPath() in ErrorController, ¿need a deprecated function?.
How to made the custom error controller?, the ErrorController requires getErrorPath but it is deprecated, what is the correct alternative?.
Starting version 2.3.x, Spring boot has deprecated this method. Just return null as it is anyway going to be ignored. Do not use #Override annotation if you want to prevent future compilation error when the method is totally removed. You can also suppress the deprecation warning if you want, however, the warning (also the #Override annotation) is helpful to remind you to cleanup/fix your code when the method is removed.
#Controller
#RequestMapping("/error")
#SuppressWarnings("deprecation")
public class CustomErrorController implements ErrorController {
public String error() {
// handle error
// ..
}
public String getErrorPath() {
return null;
}
}
#Controller
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public ModelAndView handleError(HttpServletResponse response) {
int status = response.getStatus();
if ( status == HttpStatus.NOT_FOUND.value()) {
System.out.println("Error with code " + status + " Happened!");
return new ModelAndView("error-404");
} else if (status == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
System.out.println("Error with code " + status + " Happened!");
return new ModelAndView("error-500");
}
System.out.println(status);
return new ModelAndView("error");
}
}
there is an #ControllerAdvice annotation
#ControllerAdvice
public class MyErrorController {
#ExceptionHandler(RuntimeException.class)
public String|ResponseEntity|AnyOtherType handler(final RuntimeException e) {
.. do handle ..
}
#ExceptionHandler({ Exception1.class, Exception2.class })
public String multipleHandler(final Exception e) {
}
}
To handle errors, There is no need to define a controller class
implementing an error controller.
To handle errors in your entire application instead of writing
#Controller
public class CustomErrorController implements ErrorController{
#RequestMapping("/error")
public String handleError(HttpServletRequest request)
{
...
}
}
use the below class
#ControllerAdvice
public class myExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public final ResponseEntity<YourResponseClass> handleAllExceptions(Exception ex, WebRequest request) {
YourResponseClassexceptionResponse = new YourResponseClass(new Date(), ex.getMessage());// Its an example you can define a class with your own structure
return new ResponseEntity<>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(CustomException.class)
public final ResponseEntity<YourResponseClass> handleAllExceptions(Exception ex, WebRequest request) {
YourResponseClass exceptionResponse = new YourResponseClass(new Date(), ex.getMessage()); // For reference
return new ResponseEntity<YourResponseClass>(exceptionResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(BadCredentialsException.class)
public final ResponseEntity<YourResponseClass> handleBadCredentialsException(BadCredentialsException ex, WebRequest request){
YourResponseClass exceptionResponse = new YourResponseClass(new Date(), ex.getMessage());// For refernece
return new ResponseEntity<>(exceptionResponse, HttpStatus.UNAUTHORIZED);
}
}
The class above annoted with #ControllerAdvice acts as custom exception handler and it handles all the expecptions thrown by ur application. In above code sample only three exceptions are showed for understanding. It can handle many exceptions
In your application if there's any exception thrown it will come to this class and send the response back. You can have a customized message and structure as per ur needs.
#Controller
public class AppErrorController implements ErrorController {
#RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
if(status != null) {
int statusCode = Integer.valueOf(status.toString());
if (statusCode == HttpStatus.FORBIDDEN.value()) {
return "errorpages/error-403";
} else if (statusCode == HttpStatus.NOT_FOUND.value()) {
return "errorpages/error-404";
} else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
return "errorpages/error-500";
}
}
return "errorpages/error";
}
}
I'm using Spring along with Spring boot to make a REST API.
I'm almost done implementing my first endpoints but I have a problem with validation and error handling.
I first found a way to do the validation by creating a #ControllerAdvice class as follow:
#ControllerAdvice
public class RestErrorHandler {
private MessageSource messageSource;
#Autowired
public RestErrorHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
#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);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError: fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getField(), localizedErrorMessage);
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
//If the message was not found, return the most accurate field error code instead.
//You can remove this check if you prefer to get the default error message.
if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
String[] fieldErrorCodes = fieldError.getCodes();
localizedErrorMessage = fieldErrorCodes[0];
}
return localizedErrorMessage;
}
}
Then create a messages.properties:
Size.userWithPasswordDTO.password=The password should be between 8 and 250 characters
And finally adding the #Size and #NotNull annotations in the DTO file and the #Valid annotations in the endpoint.
The validation of the DTO works well like this, but I'm trying to find a way to handle errors in the db. For example a duplicate user.
In the official documentation, they make a class like this:
#ResponseStatus(HttpStatus.NOT_FOUND)
class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String userId) {
super("could not find user '" + userId + "'.");
}
}
That handles the exceptions thrown in the endpoints.
Now my question is, how do you make the two methods return the same DTOs?
I was also wondering if the annotations #NotNull and #Size should also be in the Entities as well as in the DTOs?
EDIT
I tried updating my #ControllerAdvice to handle DataIntegrityViolationException but it doesn't seem to work. Also, I don't know what I should put inside the FieldError class.
#ControllerAdvice
public class RestErrorHandler {
private MessageSource messageSource;
#Autowired
public RestErrorHandler(MessageSource messageSource) {
this.messageSource = messageSource;
}
#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);
}
#ExceptionHandler(DataIntegrityViolationException.class)
#ResponseStatus(HttpStatus.CONFLICT)
#ResponseBody
public ValidationErrorDTO processDuplicateError(DataIntegrityViolationException ex) {
//BindingResult result = ex.getMessage();
List<FieldError> fieldErrors = new ArrayList<>();
fieldErrors.add(new FieldError("duplicate", "email", "error"));
return processFieldErrors(fieldErrors);
}
private ValidationErrorDTO processFieldErrors(List<FieldError> fieldErrors) {
ValidationErrorDTO dto = new ValidationErrorDTO();
for (FieldError fieldError: fieldErrors) {
String localizedErrorMessage = resolveLocalizedErrorMessage(fieldError);
dto.addFieldError(fieldError.getField(), localizedErrorMessage);
}
return dto;
}
private String resolveLocalizedErrorMessage(FieldError fieldError) {
Locale currentLocale = LocaleContextHolder.getLocale();
String localizedErrorMessage = messageSource.getMessage(fieldError, currentLocale);
//If the message was not found, return the most accurate field error code instead.
//You can remove this check if you prefer to get the default error message.
if (localizedErrorMessage.equals(fieldError.getDefaultMessage())) {
String[] fieldErrorCodes = fieldError.getCodes();
localizedErrorMessage = fieldErrorCodes[0];
}
return localizedErrorMessage;
}
}
In my controllers I have everywhere:
if(result.hasErrors()) {
return "redirect:/foo/bar";
}
Is there a way to automatically redirect to that uri? I am thinking if I can create and annotation to do this, something like:
#HasErrors(redirect="/foo/bar")
You need a exception mechanism to handle the error assuming like this in your controller :
#RequestMapping(value = "/checkError")
public ModelAndView process( ) throws Exception {
User user = new User();
if (user==null) {
throw new GlobalExceptionHandler ( ); // throws GlobalDefaultExceptionHandler
}
// do something when there isn't error
}
And assuming this the exception ( CustomeException class ) that will be thrown , and when this class thrown you will be redirect to a view , via global exception
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public String handleException( ModelMap model) {
return "/foo/bar";
}
}
or just a custom exception (assuming thrown as new UserNotFound () in your controller )
public class UserNotFound extends Exception {
public UserNotFoundException( ) {
toView ();
}
public String toView (){
return "/foo/bar";
}
}
I have defined a global exception handling in my Spring Boot based Rest service:
#ControllerAdvice
public class GlobalExceptionController {
private final Logger LOG = LoggerFactory.getLogger(getClass());
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR, reason = "Internal application error")
#ExceptionHandler({ServiceException.class})
#ResponseBody
public ServiceException serviceError(ServiceException e) {
LOG.error("{}: {}", e.getErrorCode(), e.getMessage());
return e;
}
}
and a custom ServiceException:
public class ServiceException extends RuntimeException {
private static final long serialVersionUID = -6502596312985405760L;
private String errorCode;
public ServiceException(String message, String errorCode, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
// other constructors, getter and setters omitted
}
so far so good, when an exception is fired the controller works as it should and respond with:
{
"timestamp": 1413883870237,
"status": 500,
"error": "Internal Server Error",
"exception": "org.example.ServiceException",
"message": "somthing goes wrong",
"path": "/index"
}
but the field errorCode isn't shown in the JSON response.
So how can I define a custom exception response in my application.
Spring Boot uses an implementation of ErrorAttributes to populate the Map that's rendered as JSON. By default, an instance of DefaultErrorAttributes is used. To include your custom errorCode you'll need to provide a custom ErrorAttributes implementation that knows about ServiceException and its error code. This custom implementation should be an a #Bean in your configuration.
One approach would be to sub-class DefaultErrorAttributes:
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(
RequestAttributes requestAttributes,
boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Throwable error = getError(requestAttributes);
if (error instanceof ServiceException) {
errorAttributes.put("errorCode", ((ServiceException)error).getErrorCode());
}
return errorAttributes;
}
};
}
#Alex You can use annotation #ExceptionHandler(YourExceptionClass.class) to handle the specific exception in specific RestController. I think it's a better way to handle complicated scenarios in business applications. Moreover i will suggest you to use custom exception translator to deal with different type of exceptions. You can consider spring oauth2 exception translator as reference code for exception translator.
Note: Following code is only to understand concept of this solution. It's not production ready code. Feel free to discuss more about it.
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
/**
* #author Harpreet
* #since 16-Aug-17.
*/
#RestController
public class RestTestController {
#RequestMapping(value = "name", method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseObject name(#RequestParam(value="name") String value){
//your custom logic
if (value == null || value.length() < 2) {
//throwing exception to invoke customExceptionHandler
throw new NullPointerException("value of request_param:name is invalid");
}
ResponseObject responseObject = new ResponseObject();
responseObject.setMessage(value)
.setErrorCode(1);
return responseObject;
}
// to handle null pointer exception
#ExceptionHandler(NullPointerException.class)
public ResponseObject customExceptionHandler
(NullPointerException e) {
ResponseObject responseObject = new ResponseObject();
responseObject.setMessage(e.getMessage())
.setErrorCode(-1);
return responseObject;
}
// response data transfer class
static class ResponseObject{
String message;
Integer errorCode;
public String getMessage() {
return message;
}
public ResponseObject setMessage(String message) {
this.message = message;
return this;
}
public Integer getErrorCode() {
return errorCode;
}
public ResponseObject setErrorCode(Integer errorCode) {
this.errorCode = errorCode;
return this;
}
}
}