Define spring bean extending MethodBeforeAdvice that proxies another component - spring

For security purposes, I need to wrap/proxy my "#Service" bean with MethodBeforeAdvice that checks that the called method has a PreAuthorize annotation.
I have an implementation of MethodBeforeAdvice that does the check.
public class MethodHasPreAuthorizeAnnotationAdvice implements MethodBeforeAdvice {
#Override
public void before(Method method, Object[] args, #Nullable Object target) throws Throwable {
PreAuthorize securedAnnotation = method.getAnnotation(PreAuthorize.class);
if (securedAnnotation == null) {
throw new InsecureMethodException(method.getName());
}
}
}
I have confirmed that this works as follows :
ProxyFactory factory = new ProxyFactory();
factory.addAdvice(new MethodHasPreAuthorizeAnnotationAdvice());
factory.setTarget(customerService);
securedCustomerService = (CustomerService) factory.getProxy();
I have a few requirements :
Proxy all my services with this advice. It seems that using an #Aspect on the stereotype #Service might be the solution to this (see: Baeldung 8. Stereotype Annotations and AOP)
Prefer to limit the use of my services so that access can only generally be obtained through the secured MethodBeforeAdvice advised proxies.
Preferably enable the original services to be unit testable without the secured proxies (although this is optional as a requirement).
Make the secure proxies available through IoC, i.e. #AutoWired-able or constructor injected equivalent.

Related

Spring authorization at package level; AOP?

we have a spring boot app with a java package that has spring controllers with endpoints for admin-like functionality. right now they all start with the same request mapping.
so one way i could do authorization of every endpoint in the package is by WebSecurityConfigurerAdapter implementation...
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
but i was thinking it would be nice to use AOP somehow to target the package of the admin controllers to PreAuthorize all controller methods in the package. just in case someone on the project creates a new controller in the proejct with a different request mapping it would automatically be projected. also, if we decided to PreAuthorize at the #Service level instead of the controller level then this way could be used as well.
so, is it possible to PreAuthorize at the package level with AOP or some other way?
Spring provides default AOP interceptor for #Secured and #PreAuthorized annotations, but it works only on class or method level, and AFAIK not intended to be expanded to package level.
To put it simply, Spring intercepts calls to certain methods or all class methods with these annotations and check whether SecurityContextHolder holds Authority object and whether its collection of GrantedAuthority matches any of the annotation's value field values. So, you can do the same thing using AOP, for example like this:
#Aspect
#Component
public class AdminServiceAOPAuthorization {
private static final List<String> ALLOWED_ROLES = List.of("ADMIN", "SUPER_ADMIN");
#Pointcut("within(com.example.service.admin.*)") // <- any method in any class of the package
public void adminServiceLayer() {}
#Before("adminServiceLayer()")
public void authorize(JoinPoint jp) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
// throw some exception depending on the logic
}
boolean authorized = authentication.getAuthorities().stream()
.anyMatch(ga -> ALLOWED_ROLES.contains(ga.getAuthority()));
if (!authorized) {
throw new AccessDeniedException("Access denied");
// log or whatever
}
}
}
For better performance I'd advise to use it at the service layer to let Spring create proxy using implemented interface (if you use them, of course) instead of proxying controller class.

Spring #Cachable method within the same class (self-invocation, proxy issue) - What is the best way to solve it?

I'm trying to call a #Cacheable method from within the same class.
And it didn't work. Because of:
In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object that calls another method of the target object) does not lead to actual caching at runtime even if the invoked method is marked with #Cacheable. Consider using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, #PostConstruct).
It means, #Cachable(also #Transactional) works by proxy classes which is Spring AOP in. a internal call in the same class make call by 'this' instead of proxy classes.
To solve the problem, I should call a method by proxy or using AspectJ(another AOP).
So, I found 4 solutions.
What is your choice? and why others are not recommended?
Please, share your opinion!
using AspectJ (another AOP)
get the Bean from ApplicationContext and use it
#Service
public class UserService implements Service {
#Autowired
private ApplicationContext applicationContext;
private Service self;
#PostConstruct
private void init() {
self = applicationContext.getBean(UserService.class);
}
}
self-autowiring using #Resource //since Spring 4.3
#Component
#CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {
/**
* 1. Self-autowired reference to proxified bean of this class.
*/
#Resource
private SphereClientFactory self;
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull TenantConfig tenantConfig) {
// 2. call cached method using self-bean
return self.createSphereClient(tenantConfig.getSphereClientConfig());
}
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull SphereClientConfig clientConfig) {
return CtpClientConfigurationUtils.createSphereClient(clientConfig);
}
}
make the Bean scope of the class as 'prototype' instead of 'singleton'
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {
private final AService _aService;
#Autowired
public AService(AService aService) {
_aService = aService;
}
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = _aService.getEmployeeData(date);
...
}
}
I'm a newbie in spring :)
Actually, I choose the 4th solution, but I felt it isn't a good way. because I just need to call the caching method by proxy, and it make several beans to achieve it.
After reading articles, I think AspectJ is the best choice. It looks cool, Spring recommends it, and many people also recommend too.
But I don't understand how to AspectJ works (I will study) and I also don't know why others is not recommended.
references
Spring Cache #Cacheable - not working while calling from another method of the same bean
Spring cache #Cacheable method ignored when called from within the same class
https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring
https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache

Spring Boot role validation controller using aspect

I have several controller functions separated by role, and instead of doing role validation in each controller method, I found that it seems to be able to get done by using Aspect, however something isn't right in my implementation as the code in Aspect never runs
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ForMerchantOnly {}
Aspect:
#Aspect
#Configuration
public class ForMerchantOnlyAspect {
private static final Logger logger = LogManager.getLogger(ForMerchantOnlyAspect.class);
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && args(request)")
public void before(HttpServletRequest request) throws ServiceException {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("request should be HttpServletRequesttype");
}
String domain = request.getServerName();
System.out.println("Aspect showing domain " + domain);
// -- other code
}
}
Controller
#ForMerchantOnly
#GetMapping("/list")
public ResponseEntity<ApiResp> list() {
System.out.println("Show something");
return ResponseEntity.ok().body();
}
I'm assuming when i call controller /list method via chrome browser, it would hit the code in ForMerchantOnlyAspect but it just went into the controller method directly. Am I missing something?
The Aspect was not working as it could not find a matching joinpoint . There are no controller methods that has annotation #ForMerchantOnly and has an argument of type HttpServletRequest
From the documentation :
args: Limits matching to join points (the execution of methods when
using Spring AOP) where the arguments are instances of the given
types.
Following aspect may be used for the requirement . Scoping designator within will set the scope to advice.
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && within(com.example.api..*)")
public void before() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
System.out.println("Aspect showing domain " + request.getServerName());
}
Also note that an Aspect is better annotated with #Component and #Configuration be used for configurations.
You may also have a look at Method Security of Spring security framework , which lets to secure a method with annotations.
From the documentation
From version 2.0 onwards Spring Security has improved support
substantially for adding security to your service layer methods. It
provides support for JSR-250 annotation security as well as the
framework’s original #Secured annotation. From 3.0 you can also make
use of new expression-based annotations. You can apply security to a
single bean, using the intercept-methods element to decorate the bean
declaration, or you can secure multiple beans across the entire
service layer using the AspectJ style pointcuts.

Error on injecting service: UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl

I am trying to inject service in spring boot app. However I'm getting following error:
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=RecommendationService,parent=RecommendationResourceImpl,qualifiers={},position=-1,optional=false,self=false,unqualified=null,1163111460)
Here is the code:
package com.example.test.recommendations.resources;
#Provider
public class RecommendationResourceImpl implements RecommendationResource {
#Inject
private RecommendationService recommendationService;
#Override
public List<Recommendation> get(String currency,
String entity) {
return recommendationService.getRecommendations(currency, entity));
}
}
Service interface
package com.example.test.recommendations.resources;
// imports
public interface RecommendationService {
List<Recommendation> getRecommendations(String currency, String entity);
Recommendation get(UUID uuid);
}
Service implementation
package com.example.test.recommendations.resources;
//imports
#Component
public class RecommendationServiceImpl implements RecommendationService{
#Override
public List<Recommendation> getRecommendations(String currency, String entity) {
return Collections.emptyList();
}
#Override
public Recommendation get(UUID uuid) {
return null;
}
}
What is correct way to inject services in spring boot applications?
I am using spring boot version 1.3.8 and Jersey version 2.25.1
From your stacktrace it is evident that the server cannot find the dependency bean to be injected.So initially check that the desired bean for the class is getting created during applciation start up.Verify that the service class is in the classpath for component scan to take place, otherwise include the package for scanning.
You are using the #Inject annotation instead of the spring #Autowired annotation to inject the beans.It will work fine but the first and most important difference between #Autowired and #Inject annotation is that the #Inject annotation is only available from Spring 3.0 onwards, so if you want to use annotation-driven dependency injection in Spring 2.5 then you have to use the #Autowired annotation.
Secondly, use the annotation #Service for the service layer rather than using the #Component annotation.
Indicates that an annotated class is a "Service", originally defined
by Domain-Driven Design (Evans, 2003) as "an operation offered as an
interface that stands alone in the model, with no encapsulated state."
May also indicate that a class is a "Business Service Facade" (in the
Core J2EE patterns sense), or something similar. This annotation is a
general-purpose stereotype and individual teams may narrow their
semantics and use as appropriate.
This annotation serves as a specialization of #Component, allowing for
implementation classes to be autodetected through classpath scanning.
#Service
public class RecommendationServiceImpl implements RecommendationService{
#Override
public List<Recommendation> getRecommendations(String currency, String entity) {
return Collections.emptyList();
}
#Override
public Recommendation get(UUID uuid) {
return null;
}
}
I am not an expert on using jersey with springboot , so i do not know if any configurations are causing this issue.
Maybe this thread might be of help to you more:
Dependency injection with Jersey 2.0
You probably never registered your Service with the DI-container. You can do that in your ResourceConfig, which you probably have since you are using jersey:
public class MyApplication extends ResourceConfig {
public MyApplication() {
register(new org.glassfish.hk2.utilities.binding.AbstractBinder() {
#Override
protected void configure() {
bind(RecommendationServiceImpl.class).to(RecommendationService.class).in(Singleton.class);
}
});
packages("com.example.test.recommendations.resources");
}
}
I am using hk2 without spring, so I usually annotate my interfaces with org.jvnet.hk2.annotations.Contract and the implementations with org.jvnet.hk2.annotations.Service. (note: not the spring #Service annotation), so I recommend trying that as well.

#Endpoint and #Transactional on the same class using Spring-ws library

I am trying to implement a web-service endpoint which would be transactional because I don't want to create a special "worker" class with transactional methods. I'm using Spring-ws library together with Spring framework.
Here is my class definition:
#Endpoint
#Transactional
#Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)
public class MyEndpoint implements ApplicationContextAware { ... }
Notice that I explicitly specified proxying method to force using CGLIB.
Also notice that my class implements some interface(s), so by default Spring uses JDK dynamic proxy unless another proxying method is specified. This kind of proxies is not appropriate in my case.
The problem begins on application deployment when PayloadRootAnnotationMethodEndpointMapping class starts working. It collects names of all Spring beans with #Endpoint annotation. My endpoint class is counted twice with names "myEndpoint" and "scopedTarget.myEndpoint". This duplication causes ApplicationContextException with message "Cannot map endpoint [...] on registration key [...]: there's already endpoint [...] mapped".
Question: how can I make my endpoint class being transactional?
You might write your own PayloadRootAnnotationMethodEndpointMapping extension and override the initApplicationContext method. There you can check for the scopedTarget. prefix to filter out unwanted beans:
public class ProxiedBeanAwareEndpointMapping extends PayloadRootAnnotationMethodEndpointMapping {
#Override
protected void initApplicationContext() throws BeansException {
initInterceptors();
String[] beanNames = getApplicationContext().getBeanNamesForType(Object.class);
for (String beanName : beanNames) {
if (!beanName.startsWith("scopedTarget.")) {
Class<?> endpointClass = getApplicationContext().getType(beanName);
if (endpointClass != null && AnnotationUtils.findAnnotation(endpointClass, getEndpointAnnotationType()) != null) {
registerMethods(beanName);
}
}
}
}
}
Or you can use the open session in view approach so you don't need to proxy your #Endpoints.

Resources