I want to create an annotation that I will use on controller methods to validate access to the resource. I have written interceptor to intercept the request and also written code to create an annotation for their independent scenarios. Now I want intercept the request as well as take values provided in anotation to further processing.
Ideally
#RequestMapping("/release")
#ValidateAction("resource","release") //custom annotation that will accept two strings
public ResponseEntity releaseSoftware(Request request){
}
From the above I have to take those two values from #ValidateAction and send a request to another authorization server to authorize the action if the user have access to it (request contains oauth access token that will be used to authorize) and return true if the user have access otherwise throw AcceeDenied exception.
Can anybody point me in the right direction of doing it in Spring boot environment
Best way to achieve this is using Spring AOP Aspects.
Let us assume you have an Annotation like this
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateAction {
String resource();
String release();
}
Then write an Aspect like this
#Aspect
#Component
public class AspectClass {
#Around(" #annotation(com.yourpackage.ValidateAction)")
public Object validateAspect(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
ValidateAction validateAction = method.getAnnotation(ValidateAction.class);
String release = validateAction.release();
String resource = validateAction.resource();
// Call your Authorization server and check if all is good
if( hasAccess)
pjp.proceed();
.......
}
}
Control will come to validateAspect method when any method which is annotated with #ValidateAction is called. Here you capture the annotation values as shown and do the necessary check.
Make sure you have the right dependency needed and these imports
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
You can do it with Spring AOP:
First, add spring-aop dependency:
compile 'org.springframework.boot:spring-boot-starter-aop' // Mine using gradle
In your #Configuration class, add #EnableAspectJAutoProxy:
#Configuration
#EnableAspectJAutoProxy
public class Application {
....
}
Create an annotation handler:
#Aspect
#Component // This #Component is required in spring aop
public class ValidateActionHandler {
#Around("execution(#your.path.ValidateAction * *(..)) && #annotation(validateAction)")
public Object doValidate(ProceedingJoinPoint pjp, ValidateAction retryConfig) throws Throwable {
// Your logic here
// then
return pjp.proceed();
}
}
Related
I followed the rest client guide in Quarkus web site. It works fine. But when registering a global provider using the ServiceLoader pattern, as described in the specification, the CDI beans injection did not work, they are all null. I downloaded the example and simply added the following classes:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.core.Response;
#ApplicationScoped
public class MyExceptionMapper implements ResponseExceptionMapper<Exception> {
#Override
public Exception toThrowable (Response response) {
return new Exception();
}
}
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
#ApplicationScoped
public class MyListener implements RestClientBuilderListener {
#Inject MyExceptionMapper myExceptionMapper;
#Override
public void onNewBuilder (RestClientBuilder builder) {
builder.register(myExceptionMapper);
}
}
I also added the file META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientBuilderListener with the content org.acme.rest.client.MyListener. The MyListener onNewBuilder method is invoked, but the injected provider MyExceptionMapper is null. How to register a global provider in Quarkus client?
Implementation of RestClientBuilderListener are not CDI beans - they are just objects that are created via the normal Java ServiceLoader mechanism when RestClientBuilder is being used.
So if you want to obtain CDI beans when onNewBuilder is called, you can do something like:
CDI.current().select(MyExceptionMapper.class).get()
Furthermore, you need to annotate MyExceptionMapper with #Provider, not #ApplicationScoped.
I am using Spring Cloud Contracts in projects to test microservices, everything is ok. But when I added Spring Security in the producer side, the GET return 401 status code instead of 200.
#Autowired
WebApplicationContext context;
#Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(this.context);
}
My question is:
I have to avoid Security settings in the contract tests?
If I want to consider the security configuration, how to make it work.
I successfully used a custom annotation on the base class, as documented here test-method-withsecuritycontext
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUserDetails {
String username() default "email#example.com";
String role() default "DEFAULT_ROLE";
String password() default "123456";
}
and then
#WithMockCustomUserDetails
class AccountBase {
...
}
Two options AFAIK.
A) Use authorization header
request {
method 'POST'
urlPath '/check'
headers {
contentType(applicationJsonUtf8())
header(authorization(), "Bearer eyJhb.... ")
}
}
B)
Add #WithMockUser in my base test
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.context.WebApplicationContext;
#SpringBootTest
#WithMockUser //this will ensure a mock user will be injected to all requests
public abstract class BaseTestCloudContract {
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}
}
I have a simple service sending emails. It can be invoked using REST and JMS APIs. I want the requests to be validated before processing.
When I invoke it using REST I can see that org.springframework.validation.DataBinder invokes void validate(Object target, Errors errors, Object... validationHints) and then validator from Hibernate is invoked. This works as expected.
The problem is I can't achieve the same effect with JMS Listener. The listener is implemented as follows:
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import our.domain.mailing.Mailing;
import our.domain.mailing.jms.api.SendEmailFromTemplateRequest;
import our.domain.mailing.jms.api.SendSimpleEmailRequest;
import javax.validation.Valid;
#ConditionalOnProperty("jms.configuration.destination")
#Component
#AllArgsConstructor(onConstructor = #__(#Autowired))
#Slf4j
public class SendMailMessageListener {
Mailing mailing;
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmailUsingTemplate(#Valid SendEmailFromTemplateRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmailTemplate(
request.getEmailDetails().getRecipients(),
request.getEmailDetails().getAccountType(),
request.getTemplateDetails().getTemplateCode(),
request.getTemplateDetails().getLanguage(),
request.getTemplateDetails().getParameters());
}
#JmsListener(destination = "${jms.configuration.destination}")
public void sendEmail(#Valid SendSimpleEmailRequest request) {
log.debug("Received jms message: {}", request);
mailing.sendEmail(
request.getRecipients(),
request.getSubject(),
request.getMessage());
}
}
The methods receive payloads but they are not validated. It's Spring Boot application and I have #EnableJms added. Can you guide what part of Spring source code is responsible for discovering #Validate and handling it? If you have any hints on how to make it running I would appreciate it a lot.
The solution is simple and was clearly described in official documentation: 29.6.3 Annotated endpoint method signature. There are few things you have to do:
Provide configuration implementing JmsListenerConfigurer (add #Configuration class implementing this interface)
Add annotation #EnableJms on the top of this configuration
Create bean DefaultMessageHandlerMethodFactory. It can be done in this configuration
Implement method void configureJmsListeners(JmsListenerEndpointRegistrar registrar) of interface JmsListenerConfigurer implemented by your configuration and set MessageHandlerMethodFactory using the bean you've just created
Add #Validated instead of #Valid to payload parameters
You can use #Valid in your listeners. Your answer was very close to it. In the step when you create DefaultMessageHandlerMethodFactory call .setValidator(validator) where validator is from org.springframework.validation. You can configure validator like this:
#Bean
public LocalValidatorFactoryBean configureValidator ()
{
return new LocalValidatorFactoryBean();
}
And then inject validator instance into your jms config
I have a service implementation carrying a class-wide #Transactional annotation. I also have an aspect that uses the #Around advice to retry failed transactions. I'm now trying (for type-safety reasons) to make the pointcut definition annotation based:
#Around("#annotation(TransactionRetryable)")
TransactionRetryable.java:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface TransactionRetryable {
}
Service layer:
#Transactional
public class ... {
#Override
#TransactionRetryable
public String forceError() {
throw new RuntimeException(someNastyMessage);
}
In that form, it only applies to method, not whole classes. However, the #Transactional annotation propagates from class level to each method. Is there a way to avoid putting the #TransactionRetryable annotation above each method and simply once above the class like the #Transactional annotation? Desired form:
#Transactional
#TransactionRetryable
public class ... {
#Override
public String forceError() {
throw new RuntimeException(someNastyMessage);
}
This pointcut would advise all public methods of a class annotated with #TransactionRetryable:
#Around("execution(public * *(..)) && within(#your.package.TransactionRetryable *)")
I want to create an exception handler which will intercept all controllers in my project. Is that possible to do? Looks like I have to put a handler method in each controller. Thanks for your help. I have a spring controller that sends Json response. So if an exception happens I want to send an error response which can be controlled from one place.
(I found a way to implement it in Spring 3.1, this is described in the second part of this answer)
See chapter 16.11 Handling exceptions of Spring Reference
There are some more ways than using #ExceptionHandler (see gouki's answer)
You could implement a HandlerExceptionResolver (use the servlet not the portlet package) - that is some kind of global #ExceptionHandler
If you do not have a specific logic for the exception, but only specifc view then you could use the SimpleMappingExceptionResolver, which is at least an implementation of the HandlerExceptionResolver where you can specify an Exception name pattern and the view (jsp) which is shown when the exception is thrown. For example:
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"
p:defaultErrorView="uncaughtException">
<property name="exceptionMappings">
<props>
<prop key=".DataAccessException">dataAccessFailure</prop>
<prop key=".TypeMismatchException">resourceNotFound</prop>
<prop key=".AccessDeniedException">accessDenied</prop>
</props>
</property>
</bean>
In Spring 3.2+ one can annotate a class with #ControllerAdvice, all #ExceptionHandler methods in this class work in a global way.
In Spring 3.1 there is no #ControllerAdvice. But with a little hack one could have a similar feature.
The key is the understanding of the way #ExceptionHandler works. In Spring 3.1 there is a class ExceptionHandlerExceptionResolver. This class implements (with help of its superclasses) the interface HandlerExceptionResolver and is responsible invoking the #ExceptionHandler methods.
The HandlerExceptionResolver interface has only one Method:
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex);`.
When the request was handled by a Spring 3.x Controller Method, then this method (represented by org.springframework.web.method.HandlerMethod) is the handler parameter.
The ExceptionHandlerExceptionResolver uses the handler (HandlerMethod) to obtain the Controller class and scan it for methods annotated with #ExceptionHandler. If one of this methods matches the exception (ex) then this methods get invoked in order to handle the exception. (else null get returned in order to signal that this exception resolver feels no responsible).
The first idea would be to implement an own HandlerExceptionResolver that behaves like ExceptionHandlerExceptionResolver, but instead of search for #ExceptionHandler in the controller class, it should search for them in one special bean. The drawback would be, that one has to (copy (or subclass ExceptionHandlerExceptionResolver) and must) configure all nice message converters, argument resolvers and return value handlers by hand (the configuration of the real one and only ExceptionHandlerExceptionResolver is done by spring automatically). So I came up with another idea:
Implement a simple HandlerExceptionResolver that "forwards" the exception to THE (already configured) ExceptionHandlerExceptionResolver, BUT with an modified handler which points to the bean that contains the global Exception handlers (I call them global, because they do the work for all controllers).
And this is the implementation: GlobalMethodHandlerExeptionResolver
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
public class GlobalMethodHandlerExeptionResolver
implements HandlerExceptionResolver, Ordered {
#Override
public int getOrder() {
return -1; //
}
private ExceptionHandlerExceptionResolver realExceptionResolver;
private List<GlobalMethodExceptionResolverContainer> containers;
#Autowired
public GlobalMethodHandlerExeptionResolver(
ExceptionHandlerExceptionResolver realExceptionResolver,
List<GlobalMethodExceptionResolverContainer> containers) {
this.realExceptionResolver = realExceptionResolver;
this.containers = containers;
}
#Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
for (GlobalMethodExceptionResolverContainer container : this.containers) {
ModelAndView result = this.realExceptionResolver.resolveException(
request,
response,
handlerMethodPointingGlobalExceptionContainerBean(container),
ex);
if (result != null)
return result;
}
// we feel not responsible
return null;
}
protected HandlerMethod handlerMethodPointingGlobalExceptionContainerBean(
GlobalMethodExceptionResolverContainer container) {
try {
return new HandlerMethod(container,
GlobalMethodExceptionResolverContainer.class.
getMethod("fakeHanderMethod"));
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException(e);
}
}
}
The global Handler has to implement this interface (in order to get found and to implement the fakeHanderMethod used for the handler
public interface GlobalMethodExceptionResolverContainer {
void fakeHanderMethod();
}
And example for an global Handler:
#Component
public class JsonGlobalExceptionResolver
implements GlobalMethodExceptionResolverContainer {
#Override
public void fakeHanderMethod() {
}
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDto handleMethodArgumentNotValidException(
MethodArgumentNotValidException validationException,
Locale locale) {
...
/* map validationException.getBindingResult().getFieldErrors()
* to ValidationErrorDto (custom class) */
return validationErrorDto;
}
}
BTW: You do not need to register the GlobalMethodHandlerExeptionResolver because spring automatically register all beans that implements HandlerExceptionResolver for exception resolvers. So a simple <bean class="GlobalMethodHandlerExeptionResolver"/> is enough.
Since Spring 3.2 you can use #ControllerAdvice annotation.
You can declare an #ExceptionHandler method within an #ControllerAdvice class
in which case it handles exceptions from #RequestMapping methods from all controllers.
#ControllerAdvice
public class MyGlobalExceptionHandler {
#ExceptionHandler(value=IOException.class)
public #ResponseBody String iOExceptionHandler(Exception ex){
//
//
}
// other exception handler methods
// ...
}
An abstract class where you define the exception handlers will do. And then make your controllers inherit it.