Throw custom exception in spring boot with an Autowired variable - spring-mvc

A custom exception that requires the use of an Autowired variable
// #Component // should be a component to use autowire variable
#ResponseStatus(value = HttpStatus.UNAUTHORIZED)
public class MyException extends Exception {
// This should be commented in
// #Autowired
// public RequestModel reqModel;
// public MyException(ExceptionType type) {
public MyException(ExceptionType type, RequestModel reqModel) {
super();
switch type {
// ...
}
}
}
And then in the rest controllers and interceptors we throw the error
public class MyInterceptor extends HandlerInterceptorAdapter {
// These should be commented out
#Autowired
public RequestModel reqModel;
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// This should be thrown without adding the reqModel as a param
// Unsure how to throw this without using "new"
throw new MyException.MyException(PARAM_MISSING, reqModel);
}
}
I understand that instantiating a class using new will null the Autowired variables. When I tried dropping the new and converting the MyException class to a #Component, I see issues with calling the MyException class constructor.
What is the best way to implement a custom exception in springboot using an autowired variable?

Related

Spring #ControllerAdvice messageSource doesn't work with Hibernate message parameters

I'm trying to add some custom bean validation in a Spring Boot REST controller, extending the ResponseEntityExceptionHandler class with a #ControllerAdvice annotation and overriding the #handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) method. In this method I'm trying to convert the given FieldError via the messageSource into a localized message. Although I'm receiving a NumberFormatException when trying to using message parameters which are be possible via the Hibernate validator.
I'm using the following dependencies:
org.hibernate.validator:hibernate-validator (6.0.11.Final)
org.springframework:spring-web (5.0.8.RELEASE)
org.springframework:spring-webmvc (5.0.8.RELEASE)
All included via org.springframework.boot:spring-boot-starter-web (2.0.4.RELEASE).
Consider using the following REST controller:
#RestController
public class FooController {
#PostMapping(value = "/foo")
public void submitFooRequest(#Validated #RequestBody FooRequest fooRequest) {
// ....
}
}
The FooRequest bean has a custom bean validation annotation & constraint validator:
The bean FooRequest:
#Getter
#Setter
#ValidBarRequest
public class FooRequest {
private String fieldFoo;
private BarRequest barRequest;
}
The bean BarRequest:
#Getter
#Setter
public class BarRequest {
private String fieldBar;
}
The validation annotation:
#Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = BarRequestValidator.class)
#Documented
public #interface ValidBarRequest {
String message() default "{org.example.validation.constraints.ValidBarRequest.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String fieldFoo() default "fieldFoo";
String barRequestFieldBar() default "barRequest.fieldBar";
}
The validation constraint validator:
#Log4j2
public class BarRequestValidator implements ConstraintValidator<ValidBarRequest, Object> {
// ....
#Override
public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
if (/* some condition */) {
HibernateConstraintValidatorContext hibernateValidatorContext = constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);
hibernateValidatorContext.disableDefaultConstraintViolation();
hibernateValidatorContext.addMessageParameter("fieldFoo", "some value...").buildConstraintViolationWithTemplate("{org.example.validation.constraints.ValidBarRequest.message}")
.addPropertyNode("barRequest.fieldBar").addConstraintViolation();
return false;
}
return true;
}
}
However via a #ControllerAdvice annotated bean and using a Spring messageSource a NumberFormatException is thrown on the following message (in messages.properties):
ValidBarRequest.fooRequest.barRequest.fieldBar=must be lower or equal than {fieldFoo}
The #ControllerAdvice bean:
#ControllerAdvice
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired private MessageSource messageSource;
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<ErrorDetails> errorDetails = new ArrayList<>();
for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {
errorDetails.add(new ErrorDetails(fieldError.getField(), messageSource.getMessage(fieldError, Locale.getDefault())));
}
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
#Getter
#AllArgsConstructor
class ErrorDetails {
private String field;
private String message;
}
}
This causes the following exception: Caused by: java.lang.NumberFormatException: For input string: "fieldFoo"
What am I doing wrong? Also I included the following bean in my #SpringBootApplication:
#Bean
public LocalValidatorFactoryBean validator(MessageSource messageSource) {
LocalValidatorFactoryBean localValidatorFactory = new LocalValidatorFactoryBean();
localValidatorFactory.setValidationMessageSource(messageSource);
return localValidatorFactory;
}
The populate the variables in the message source MessageFormat.format is and there you must use numbers in curly braces.
ValidBarRequest.fooRequest.barRequest.fieldBar=must be lower or equal than {0}

How do I register a HandlerInterceptor with constructor dependencies in Spring Boot

My use case is running custom code before a controller method by annotating methods.
HandlerInterceptor seems the way to go but it seems impossible to inject dependencies into it because it needs to be registered before the context is being created.
All examples I've found so far use empty constructors (see spring boot adding http request interceptors) or autowire properties in the configuration which fails because I declare dependent beans in the same configuration (Requested bean is currently in creation: Is there an unresolvable circular reference?).
Is there a better way that does not involve AOP?
Assume that your interceptor has constructor dependencies like that:
public class CustomInterceptor extends HandlerInterceptor {
private final DependentBean bean;
public CustomInterceptor(DependentBean bean) {
this.bean = bean;
}
}
Then you can register your handler like that:
#Configuration
public WebConfig extends WebMvcConfigurerAdapater {
#Bean
public DependentBean dependentBean() {
return new DependentBean();
}
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor(dependentBean());
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor());
}
}
#Configuration will ensure each Bean method call return the same bean instance
Building on the answer above from Mạnh, if using component scan for dependency injection of the dependency, then that can be Autowired in the WebConfig
#Configuration
public WebConfig extends WebMvcConfigurerAdapater {
#Autowired
DependentBean dependentBean;
#Bean
public CustomInterceptor customInterceptor() {
return new CustomInterceptor(dependentBean);
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(customInterceptor());
}
}
Also building on previous answers, and if you use Lombok, you can further simplify.
Have your interceptor implementation been a #Component
Add a private final DependentBean field to it.
Also add a #RequiredArgsConstructor annotation to it, to have Lombok generating a constructor with a single DependentBean parameter.
In your WebConfig, use the same technic to have a private final CustomInterceptor field been injected by Spring IOC.
This way the CustomInterceptor instance will be available & initialized the right way when addInterceptors will be called
Here are the corresponding code samples :
The CustomInterceptor :
#Component
#RequiredArgsConstructor
public class CustomInterceptor implements HandlerInterceptor {
private final DependentBean dependentBean;
#Override
public boolean preHandle( final HttpServletRequest request,
final HttpServletResponse response,
final Object handler ) throws Exception {
// your Interceptor Implementation goes here ...
}
}
The WebConfig :
#Configuration
#RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
private final CustomInterceptor customInterceptor;
#Override
public void addInterceptors( final InterceptorRegistry registry ) {
registry.addInterceptor( customInterceptor );
}
}

Return 404 for every null response

I'd like to return 404 when the response object is null for every response automatically in spring boot.
I need suggestions.
I don't want to check object in controller that it is null or not.
You need more than one Spring module to accomplish this. The basic steps are:
Declare an exception class that can be used to throw an exception when a repository method does not return an expected value.
Add a #ControllerAdvice that catches the custom exception and translates it into an HTTP 404 status code.
Add an AOP advice that intercepts return values of repository methods and raises the custom exception when it finds the values not matching expectations.
Step 1: Exception class
public class ResourceNotFoundException extends RuntimeException {}
Step 2: Controller advice
#ControllerAdvice
public class ResourceNotFoundExceptionHandler
{
#ExceptionHandler(ResourceNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public void handleResourceNotFound() {}
}
Step 3: AspectJ advice
#Aspect
#Component
public class InvalidRepositoryReturnValueAspect
{
#AfterReturning(pointcut = "execution(* org.example.data.*Repository+.findOne(..))", returning = "result")
public void intercept(final Object result)
{
if (result == null)
{
throw new ResourceNotFoundException();
}
}
}
A sample application is available on Github to demonstrate all of this in action. Use a REST client like Postman for Google Chrome to add some records. Then, attempting to fetch an existing record by its identifier will return the record correctly but attempting to fetch one by a non-existent identifier will return 404.
Simplest way to do this in Spring is write your own exception class like below
#ResponseStatus(value = HttpStatus.NOT_FOUND)
class ResourceNotFoundException extends RuntimeException{
}
Then just throw the ResourceNotFoundException from anywhere.
if (something == null) throw new ResourceNotFoundException();
For more -> Read
Similar to #manish's answer (https://stackoverflow.com/a/43891952/986160) but without the AspectJ pointcut and using another #ControllerAdvice instead:
Step 1: NotFoundException class:
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException() {}
}
Step 2: Check if body returned in endpoint is null and throw NotFoundException:
#ControllerAdvice
public class NotFoundAdvice implements ResponseBodyAdvice {
#Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
#Override
#SuppressWarnings("unchecked")
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
throw new NotFoundException("resource not found");
}
return body;
}
}
Step 3: handle NotFoundException and make the response have a status of 404
#ControllerAdvice
public class GlobalExceptionAdvice {
#Data
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
super();
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
}
#ExceptionHandler(NotFoundException.class)
public final ResponseEntity<ErrorDetails> notFoundHandler(Exception ex, WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(),
request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
}
Alternative to Step 3:
You can just annotate your NotFoundException with #ResponseStatus and override fillInStackTrace() (from https://stackoverflow.com/a/31263942/986160) so that it has similar effect to GlobalExceptionAdvice and doesn't show stacktrace like this:
#ResponseStatus(value = HttpStatus.NOT_FOUND,reason = "resource not found")
public class NotFoundException extends RuntimeException {
public NotFoundException(String msg) {
super(msg);
}
public NotFoundException() {}
#Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}
There is no need to throw exceptions, now ResponseBodyAdvice does the trick:
#ControllerAdvice
public class NullTo404 implements ResponseBodyAdvice<Object> {
#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) {
if (body == null) {
response.setStatusCode(HttpStatus.NOT_FOUND);
}
return body;
}
}
Similarly, you can implement ResponseBodyAdvice<Optional<?>>, and check for Optional.isEmpty() before setting the response status. It has the added benefit of working nicely with CrudRepository. Most controller methods eventually ends like this:
public Optional<Product> getProductBySku(#PathVariable String sku) {
// logic goes here...
return productRepository.findBySku(sku);
}

How to wire AuthenticationManager for Spring AbstractPreAuthenticatedProcessingFilter

I have a class derived from AbstractPreAuthenticatedProcessingFilter in my Spring Security Filter Chain. The purpose of this filter is to massage role data left in a special Principal object by a corporate authentication service into a Collection so SpringSecurity can use them.
However, I cannot get past this exception:
Caused by: java.lang.IllegalArgumentException: An AuthenticationManager must be set
at org.springframework.util.Assert.notNull(Assert.java:112) ~[spring-core-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter.afterPropertiesSet(AbstractPreAuthenticatedProcessingFilter.java:97) ~[spring-security-web-4.0.1.RELEASE.jar:4.0.1.RELEASE]
I am using Java config, not XML config. My code following the example of How To Inject AuthenticationManager using Java Configuration in a Custom Filter is as follows:
the security configurer adaptor
#Configuration
#EnableWebSecurity
public class MyWebSecurityAdaptor extends WebSecurityConfigurerAdapter {
...
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
The filter class itself:
#Component
public class MyPreauthFilter extends AbstractPreAuthenticatedProcessingFilter {
...
#Autowired
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
If instead of the code in Item 1 above, I try the following:
#Autowired
#Override
protected AuthenticationManager authenticationManager() throws Exception
{
// TODO Auto-generated method stub
return super.authenticationManager();
}
Then the error changes.
It then becomes:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.authentication.AuthenticationManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate}
I guess that makes sense, this way does not define a bean. But then why didn't the original way, which DID define a bean, fail?
Instead of adding 'myAuthernticationManager' to the WebSecurityConfigurerAdapter class. Add this directly to you filter class and autowire it.
#Autowired
#Override
public void setMyAuthenticationManager(MyAuthenticationManager myAuthenticationManager) {
this.myAuthenticationManager = myAuthenticationManager;
super.setAuthenticationManager(this.myAuthenticationManager);
}
Remove all the code related to myAuthenticationManager from your WebSecurityConfigurerAdapter.

Configuration of Spring security with Spring boot and a Spring MVC Rest API on Jetty

I'm currently making a Rest API using spring boot and spring mvc hosted on Jetty. At this point everything works. Now I'd like to add spring security but it throws an exception :
FAILED org.springframework.boot.context.embedded.jetty.ServletContextInitializerConfiguration$InitializerListener#36895c35: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public javax.servlet.Filter org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.springSecurityFilterChain() throws java.lang.Exception] threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration.setGlobalAuthenticationConfigurers(java.util.List) throws java.lang.Exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.security.AuthenticationManagerConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private java.util.List org.springframework.boot.autoconfigure.security.AuthenticationManagerConfiguration.dependencies; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityProperties': Could not bind properties; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'bean' of bean class [org.springframework.boot.autoconfigure.security.SecurityProperties]: Bean property 'bean' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
So here is my main class :
#Configuration
#ComponentScan
#EnableAutoConfiguration
#PropertySource({"classpath:configuration.properties"})
#Import({ApplicationConfig.class, SecurityConfig.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Autowired
private Environment environment;
public Environment getEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
Here is my application configuration
#EnableWebMvc
#Configuration
#EnableTransactionManagement
public class ApplicationConfig {
#Autowired
private Environment environment;
public Environment getEnvironment() {
return environment;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Bean(name = "dataSource")
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(this.getEnvironment().getProperty("database.driver"));
driverManagerDataSource.setUrl(this.getEnvironment().getProperty("database.url"));
driverManagerDataSource.setUsername(this.getEnvironment().getProperty("database.username"));
driverManagerDataSource.setPassword(this.getEnvironment().getProperty("database.password"));
return driverManagerDataSource;
}
#Bean(name = "sessionFactory")
public SessionFactory getSessionFactory() {
LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(this.getDataSource());
builder.scanPackages("apt.model").addProperties(this.getHibernateProperties());
return builder.buildSessionFactory();
}
private Properties getHibernateProperties() {
Properties prop = new Properties();
prop.put("hibernate.format_sql", this.getEnvironment().getProperty("database.verbose"));
prop.put("hibernate.show_sql", this.getEnvironment().getProperty("database.verbose"));
prop.put("hibernate.dialect", this.getEnvironment().getProperty("database.dialect"));
prop.put("hbm2ddl.auto", this.getEnvironment().getProperty("database.hbm2ddl"));
prop.put("c3p0.min_size", "5");
prop.put("c3p0.max_size", "50");
prop.put("c3p0.timeout", "300");
prop.put("c3p0.max_statements", "50");
prop.put("c3p0.idle_test_period", "3000");
return prop;
}
#Bean(name = "txManager")
public HibernateTransactionManager getTransactionManager() {
return new HibernateTransactionManager(this.getSessionFactory());
}
}
and here is the security configuration
#Configuration
#EnableWebSecurity
#EnableAutoConfiguration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccountService accountService;
#Autowired
private AuthenticationService authenticationService;
public AccountService getAccountService() {
return accountService;
}
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
public AuthenticationService getAuthenticationService() {
return authenticationService;
}
public void setAuthenticationService(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
#Override
public void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
super.setAuthenticationConfiguration(authenticationConfiguration);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll().anyRequest().authenticated();
http.formLogin().loginPage("/authentication/login").permitAll().and().logout().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(this.getAuthenticationService()).passwordEncoder(this.getPasswordEncoder());
}
#Bean(name = "passwordEncoder")
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
Do you know where it comes from ?
It sounds as if you have a property defined named security.bean which is causing a binding error on Spring Boot's org.springframework.boot.autoconfigure.security.SecurityProperties.
This happens since SecurityProperties is annotated with #ConfigurationProperties(name = "security", ignoreUnknownFields = false) and does not contain a property named bean.
In short, you should not have any properties that start with security. that are not listed in the reference.

Resources