Adding additional Spring Security method annotations - java

I'm writing a library that uses Spring Security and method security to check whether a user is licensed to perform a certain operation. This is in addition to the usual role-based security, and this is causing a problem.
The annotations look like they do in this test class:
#RestController
class TestController {
#RolesAllowed("ROLE_USER")
#Licensed("a")
public ResponseEntity<String> a() {
return ResponseEntity.ok("a");
}
#RolesAllowed("ROLE_USER")
#Licensed("b")
public ResponseEntity<String> b() {
return ResponseEntity.ok("b");
}
#RolesAllowed("ROLE_USER")
#Licensed("c")
public ResponseEntity<String> c() {
return ResponseEntity.ok("c");
}
}
Having the annotations processed seems simple enough, because you add a customMethodSecurityDataSource:
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
#Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new LicensedAnnotationSecurityMetadataSource();
}
// more configurations
}
But the problem is in Spring's implementation:
#Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
Collection<ConfigAttribute> attributes = null;
for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null && !attributes.isEmpty()) {
break;
}
}
// Put it in the cache.
if (attributes == null || attributes.isEmpty()) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return NULL_CONFIG_ATTRIBUTE;
}
this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
My custom metadata source is processed first, and as soon as it finds an annotation that it recognises, it stops processing. Specifically, in this if-block:
if (attributes != null && !attributes.isEmpty()) {
break;
}
The result is that my LicenceDecisionVoter votes to abstain; after all, there could be other annotation processors that check roles. And because there are no more attributes to vote upon, only ACCESS_ABSTAIN is returned, and as per Spring's default and recommended configuration, access is denied. The roles are never checked.
Do I have an alternative, other than to implement scanning for Spring's own annotation processors, like the #Secured and JSR-250 annotations?
Or was the mistake to use Spring Security in the first place for this specific purpose?

As promised, the solution. It was more work than I imagined, and the code may have issues because it is partly copied from Spring, and some of that code looks dodgy (or at least, IntelliJ thinks it does).
The key is to remove the GlobalMethodSecurityConfiguration. Leave that to the application itself. The (auto) configuration class looks like the following:
#EnableConfigurationProperties(LicenceProperties.class)
#Configuration
#Import(LicensedMetadataSourceAdvisorRegistrar.class)
public class LicenceAutoConfiguration {
#Bean public <T extends Licence> LicenceChecker<T> licenceChecker(
#Lazy #Autowired final LicenceProperties properties,
#Lazy #Autowired final LicenceFactory<T> factory
) throws InvalidSignatureException, LicenceExpiredException, WrappedApiException,
IOException, ParseException, InvalidKeySpecException {
final LicenceLoader loader = new LicenceLoader(factory.getPublicKey());
final T licence = loader.load(properties.getLicenceFile(), factory.getType());
return factory.getChecker(licence);
}
#Bean MethodSecurityInterceptor licenceSecurityInterceptor(
final LicensedMetadataSource metadataSource,
final LicenceChecker<?> licenceChecker
) {
final MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
interceptor.setAccessDecisionManager(decisionManager(licenceChecker));
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean LicenceAccessDecisionManager decisionManager(#Autowired final LicenceChecker<?> licenceChecker) {
return new LicenceAccessDecisionManager(licenceChecker);
}
#Bean LicensedMetadataSource licensedMetadataSource() {
return new LicensedMetadataSource();
}
}
The registrar:
public class LicensedMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
final BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(LicensedMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgReference("licensedMetadataSource");
registry.registerBeanDefinition("licensedMetadataSourceAdvisor", advisor.getBeanDefinition());
}
}
And finally, the advisor:
public class LicensedMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final LicenceMetadataSourcePointcut pointcut = new LicenceMetadataSourcePointcut();
private transient LicensedMetadataSource attributeSource;
private transient BeanFactory beanFactory;
private transient MethodInterceptor interceptor;
private transient volatile Object adviceMonitor = new Object();
public LicensedMetadataSourceAdvisor(final LicensedMetadataSource attributeSource) {
this.attributeSource = attributeSource;
}
#Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override public Pointcut getPointcut() {
return pointcut;
}
#Override public Advice getAdvice() {
synchronized (this.adviceMonitor) {
if (this.interceptor == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
this.interceptor = this.beanFactory.getBean("licenceSecurityInterceptor", MethodInterceptor.class);
}
return this.interceptor;
}
}
class LicenceMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
#Override public boolean matches(final Method method, final Class<?> targetClass) {
final LicensedMetadataSource source = LicensedMetadataSourceAdvisor.this.attributeSource;
final Collection<ConfigAttribute> attributes = source.getAttributes(method, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
}
The latter two classes are copied and modified from Spring. The advisor was copied from MethodSecurityMetadataSourceAdvisor, and that's a class that somebody at Spring might have a look at, because of the transient volatile synchronisation object (which I copied, because I can't yet establish if it should be final instead), and because it has a private method that is never used.

Related

How force BeanPostProcessor proxy to look up on classes not interfaces?

I am not sure if question is correctly asked because I am still newbie in this stuff.
I want to complete the following scenario using a BeanPostProcessor:
Filter all beans that are marked with #Service annotation.
Filter all methods that have the marker annotation #Refreshable over themselves.
Perform the specified method on the return objects of these methods.
Below is my working example:
#Retention(RUNTIME)
public #interface Refreshable {
}
public interface VisitServiceI {
#Refreshable
VisitDtoOut addVisitToPatient(UUID idPatient, VisitDtoIn visitDtoIn);
}
public interface RefreshableDto {
void copyId();
}
#Component
public class MethodBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof VisitServiceI) {
ProxyFactory factory = new ProxyFactory(bean);
factory.addInterface(VisitServiceI.class);
factory.addAdvice((AfterReturningAdvice) (returnValue, method, args, target) -> {
if (method.isAnnotationPresent(Refreshable.class)) {
var refreshableDto = (RefreshableDto) returnValue;
if (refreshableDto != null) {
refreshableDto.copyId();
}
}
});
factory.setExposeProxy(true);
return factory.getProxy();
}
return bean;
}
}
Is this possible to get rid of that useless interfaces like: VisitServiceI
I want to force BeanPostProcessor to somehow works when I will give him standard classes instead of interfaces of them.
Ditch the BeanPostProcessor and just write an aspect instead, let Spring do the heavy lifting.
#Aspect
#Component
public RefreshableAspect {
#AfterReturn("within(#Service) && #annotation(#Refreshable)", returning="retVal")
public void refresh(Object retVal) {
if (retVal instanceof RefreshableDto) {
((RefreshableDto) retVal).copyId();
}
}
}
Something like that will accomplish what you need without interfaces and without an additional BeanPostProcessor.
But if you really want the complex route do something like this
#Component
public class MethodBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (AnnotationUtils.findAnnotation(bean.getClass(), Service.class) != null) {
ProxyFactory factory = new ProxyFactory(bean);
factory.setProxyTargetClass(true);
factory.addAdvice((AfterReturningAdvice) (returnValue, method, args, target) -> {
if (method.isAnnotationPresent(Refreshable.class)) {
var refreshableDto = (RefreshableDto) returnValue;
if (refreshableDto != null) {
refreshableDto.copyId();
}
}
});
factory.setExposeProxy(true);
return factory.getProxy();
}
return bean;
}
}

Spring Security hasPermission for Collection<Object>

I have working application secured with method-level security:
RestController:
#PreAuthorize("hasPermission(#product, 'WRITE')")
#RequestMapping(value = "/save", method = RequestMethod.POST)
public Product save(#RequestBody Product product) {
return productService.save(product);
}
PermissionEvaluator:
public class SecurityPermissionEvaluator implements PermissionEvaluator {
private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class);
private final PermissionService permissionService;
public SecurityPermissionEvaluator(PermissionService permissionService) {
this.permissionService = permissionService;
}
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// almost the same implementation
}
}
And everything works fine until I implemented API which saves collection of objects. The logic of this service is to update existing entities and/or create new entities.
#PreAuthorize("hasPermission(#products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
After this my permission service handles the collection object and looks like this now:
PemissionService:
public class PermissionService {
public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
if (targetDomainObject instanceof TopAppEntity) {
if (((TopAppEntity) targetDomainObject).getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
} else if(targetDomainObject instanceof Collection) {
boolean isAuthorized = false;
Collection targetDomainObjects = (Collection) targetDomainObject;
for (Object targetObject : targetDomainObjects) {
isAuthorized = isAuthorized(user, targetObject, permission);
if (!isAuthorized) break;
}
return isAuthorized;
}
}
}
My question is:
How I can handle collections using #PreAuthorize("hasPermission(#object, '...')") more elegant way? Is there some implementations in Spring Security for handling collections? At least, how can I optimize PemissionService for handling Collections?
I have a couple of workarounds.
1. The first one is to use my own MethodSecurityExpressionHandler and MethodSecurityExpressionRoot.
Creating a CustomMethodSecurityExpressionRoot and define a method which will be our new expression for Collection handling. It will extend SecurityExpressionRoot to include default expressions:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private final PermissionEvaluator permissionEvaluator;
private final Authentication authentication;
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication, PermissionEvaluator permissionEvaluator) {
super(authentication);
this.authentication = authentication;
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
public boolean hasAccessToCollection(Collection<Object> collection, String permission) {
for (Object object : collection) {
if (!permissionEvaluator.hasPermission(authentication, object, permission))
return false;
}
return true;
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
#Override
public Object getThis() {
return target;
}
}
Create custom expression handler and inject CustomMethodSecurityExpressionRoot:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final PermissionEvaluator permissionEvaluator;
public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root =
new CustomMethodSecurityExpressionRoot(authentication, permissionEvaluator);
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
I also injected SecurityPermissionEvaluator used in question, so it will be a single point of entry for custom and default expressions. As an alternate option we could inject and use PermissionService directly.
Configuring our method-level security:
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private PermissionService permissionService;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(permissionService);
return new CustomMethodSecurityExpressionHandler(permissionEvaluator);
}
}
Now we can use new expression in RestController:
#PreAuthorize("hasAccessToCollection(#products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
As a result a part with handling collection in PermissionService could be omitted as we took out this logic to custom expression.
2. The second workaround is to call method directly using SpEL.
Now I'm using PermissionEvaluator as Spring bean (any service could be used here, but I'm preferring single point of entry again)
#Component
public class SecurityPermissionEvaluator implements PermissionEvaluator {
#Autowired
private PermissionService permissionService;
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (!(targetDomainObject instanceof TopAppEntity))
throw new IllegalArgumentException();
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
try {
return permissionService.isAuthorized(userDetails.getUser(), targetId,
Class.forName(targetType), String.valueOf(permission));
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("No class found " + targetType);
}
}
public boolean hasPermission(Authentication authentication, Collection<Object> targetDomainObjects, Object permission) {
for (Object targetDomainObject : targetDomainObjects) {
if (!hasPermission(authentication, targetDomainObject, permission))
return false;
}
return true;
}
}
Configuring method security:
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private PermissionEvaluator permissionEvaluator;
#Autowired
private ApplicationContext applicationContext;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
// Pay attention here, or Spring will not be able to resolve bean
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
}
Usage of the service in expression:
#PreAuthorize("#securityPermissionEvaluator.hasPermission(authentication, #products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
Spring beans created by default with class name if no other name specified.
Summary: both approaches based on using custom services calling them directly or registering them as expressions and could handle the logic of collection before it will be sent to authority checking service, so we can omit the part of it:
#Service
public class PermissionService {
public boolean isAuthorized(User user, TopAppEntity domainEntity, String permission) {
// removed instanceof checks and can operate on domainEntity directly
if (domainEntity.getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
}
}
Yes, there is a smart way. I can tell you what I did.
#Component("MySecurityPermissionEvaluator ")
#Scope(value = "session")
public class PermissionService {
#Autowired
private PermissionEvaluator permissionEvaluator;
public boolean myPermission(Object obj, String permission) {
boolean isAuthorized = false;
Authentication a = SecurityContextHolder.getContext()
.getAuthentication();
if (null == obj) {
return isAuthorized;
}
if (a.getAuthorities().size() == 0) {
logger.error("For this authenticated object, no authorities could be found !");
return isAuthorized;
} else {
logger.error("Authorities found " + a.getAuthorities());
}
try {
isAuthorized = myPermissionEval
.hasPermission(a, obj, permission);
} catch (Exception e) {
logger.error("exception while analysisng permissions");
}
return isAuthorized;
}
Please do not use hard coded permissions, Use this way instead,
import org.springframework.security.acls.domain.DefaultPermissionFactory;
public class MyPermissionFactory extends DefaultPermissionFactory {
public MyPermissionFactory() {
registerPublicPermissions(MyPermission.class);
}
}
To make custom permissions,
import org.springframework.security.acls.domain.BasePermission;
public class MyPermission extends BasePermission { //use this class for creating custom permissions
private static Map<String, Integer> customPerMap = new HashMap<String, Integer>();
static {
customPerMap.put("READ", 1);
customPerMap.put("WRITE", 2);
customPerMap.put("DELETE", 4);
customPerMap.put("PUT", 8);
}
/**
*Use the function while saving/ getting permission code
**/
public static Integer getCode(String permName) {
return customPerMap.get(permName.toUpperCase());
}
If you need to authenticate urls based on admin users or role hierarchy, use tag in Spring Authentication not Authorization.
Rest, you are using correctly, #PreAuthorize and #PreFilter both are correct and used acco to requirements.
You can use the #PreFilter annotation.
So #PreFilter("hasPermission(filterTarget, '...')") will call your PermissionService for each element of the Collection.
public class PermissionService() {
public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
if (targetDomainObject instanceof TopAppEntity) {
if (((TopAppEntity) targetDomainObject).getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
}
}
}
Note: this will not prevent a call of your controller method. It only gets an empty Collection.
In some cases it's enough a default implementation of SecurityExpressionRoot.
If your permission evaluation is based on only analyzing, for example, an Owner of Product you could use the next expressions:
#GetMapping("")
#PostAuthorize("hasPermission(returnObject.![#this.owner],'ProductOwner','READ')")
public Collection<Product> getAllFiltering(<filters>) {...
#PostMapping("/collection")
#PreAuthorize("hasPermission(#products.![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> save(#RequestBody Collection<Product> products) {...
#PutMapping("/collection")
#PreAuthorize("hasPermission(#productRepository.findByIds(#products.![#this.id]).![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> update(#RequestBody Collection<Product> products) {...
In these cases your PermissionEvaluator must be able to process collection.You could also continue using your PermissionEvaluator for a single Product:
#GetMapping("/{id}")
#PostAuthorize("hasPermission({ returnObject.owner },'ProductOwner','READ')")
public Product getById(#PathVariable int id) {...
or make an implementation of PermissionEvaluator which analyzes whether an array or a single value was passed.
#products.![#this.owner] - see "6.5.17 Collection Projection"; { returnObject.owner } - see "6.5.3 Inline lists"
here: https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html

Spring AOP CGLIB proxy's field is null

Description
Using the vlcj component, the custom component appears as a result of the AOP proxy object null.
MediaList Class
public class MediaList {
private libvlc_media_list_t mediaListInstance;
public MediaList(LibVlc libvlc, libvlc_instance_t instance, libvlc_media_list_t mediaListInstance) {
this.libvlc = libvlc;
this.instance = instance;
createInstance(mediaListInstance);
}
private void createInstance(libvlc_media_list_t mediaListInstance) {
logger.debug("createInstance()");
if(mediaListInstance == null) {
mediaListInstance = libvlc.libvlc_media_list_new(instance);
}
else {
libvlc.libvlc_media_list_retain(mediaListInstance);
}
this.mediaListInstance = mediaListInstance; // <- assignment
logger.debug("mediaListInstance={}", mediaListInstance);
mediaListEventManager = libvlc.libvlc_media_list_event_manager(mediaListInstance);
logger.debug("mediaListEventManager={}", mediaListEventManager);
registerEventListener();
}
public final libvlc_media_list_t mediaListInstance() {
return mediaListInstance; // <- proxy object return null, if use aop
}
}
Custom MediaList Class
public class TestMediaList extends MediaList {
public TestMediaList(LibVlc libvlc, libvlc_instance_t instance) {
super(libvlc, instance);
}
public void xTest(String test){
System.out.println(test);
}
}
Spring Configuration Class
#Configuration
public class PlayerBeanConfig {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
#Resource
public TestMediaList testMediaList(LibVlc libvlc, libvlc_instance_t instance) {
return new TestMediaList(libvlc, instance);
}
}
AOP Configuration Class
#Aspect
public class MediaListAspect {
#Pointcut("execution(* TestMediaList.xTest(..))")
private void anyMethod() {
}
#Around("anyMethod()")
public Object lockAndUnlock(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
return object;
}
}
Test Code
public static void main(String[] args) {
boolean b = new NativeDiscovery().discover();
if (b) {
springContext = new AnnotationConfigApplicationContext(PlayerBeanConfig.class);
String[] kkk = new String[]{};
TestMediaList list = springContext.
getBean(TestMediaList.class, LibVlc.INSTANCE, LibVlc.INSTANCE.libvlc_new(kkk.length, kkk));
System.out.println(list.mediaListInstance()); // <- proxy object return null
} else {
logger.error("Cannot find vlc lib, exit application");
}
}
I try to single step tracking, when TestMediaList the build is complete. MediaListInstance () of the method to return to normal values, but when the spring returns to the proxy object, null is returned. At the same time, I also try to return the value correctly if you don't use AOP.
Therefore, I determine the basic problem in AOP dynamic proxy, but I don't know why, did not previously encountered such a situation.
Minimal example
all class in package : vod.demo
TargetClass
public class TargetClass {
private String returnValue;
public TargetClass() {
this.returnValue = "Hello World";
}
public final String test() {
System.out.println("TargetClass.test();");
return returnValue;
}
}
Aspect Class
#Aspect
public class AspectClass {
#Pointcut("execution(* vod.demo.TargetClass.*(..))")
private void targetMethod() {
}
#Around("targetMethod()")
public Object aroundTarget(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("AspectClass.aroundTarget();");
return joinPoint.proceed();
}
}
Spring Config Class
#Configuration
#EnableAspectJAutoProxy
#Import(AspectClass.class)
public class SpringConfig {
#Bean
public TargetClass target() {
return new TargetClass();
}
}
Client Class
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
TargetClass target = context.getBean(TargetClass.class);
System.out.println("Client invoke:" + target.test()); // <- output null
}
}
This is a combination of potentially unexpected behaviors. First, Spring uses CGLIB to proxy your beans for AOP. CGLIB proxies are instances of a dynamic subtype of your class that delegate all method calls to a real instance of your class. However, even though the proxy is of a subtype, its fields are not initialized (ie. your TargetClass super constructor is not invoked). A lengthier explanation can be found here.
Additionally, your method
public final libvlc_media_list_t mediaListInstance() {
return mediaListInstance; // <- proxy object return null, if use aop
}
or
public final String test() {
System.out.println("TargetClass.test();");
return returnValue;
}
are final. CGLIB therefore cannot override them to delegate to the real instance. This would be hinted at in Spring logs. For example, you would see
22:35:31.773 [main] INFO o.s.aop.framework.CglibAopProxy - Unable to proxy method [public final java.lang.String com.example.root.TargetClass.test()] because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
Put all of the above together and you get a proxy instance where the field is null and where the proxy cannot delegate to the real instance's method. So your code will actually invoke
public final String test() {
System.out.println("TargetClass.test();");
return returnValue;
}
for an instance where the returnValue field is null.
If you can, change your method, remove the final modifier. If you can't, you'll have to rethink your design.

Inject spring bean into custom logback filter

in order to reduce the amount of logging present in our application, we decided
to enable/disable logging of certain methods on a per-client basis.
public class LoggingFilter extends Filter<ILoggingEvent> {
#Autowired
private MethodNameValidator validator;
#Override
public FilterReply decide(ILoggingEvent event) {
Map<String, String> mdcProperties = event.getMDCPropertyMap();
if (mdcProperties.isEmpty()) {
return FilterReply.ACCEPT;
}
String accountId = mdcProperties.get(MDCUtil.ACCOUNT_ID);
String methodName = mdcProperties.get(MDCUtil.METHOD_NAME);
if (validator.isValidMethodName(accountId, methodName)) {
return FilterReply.ACCEPT;
}
return FilterReply.DENY;
}
}
The custom filter defined above has a validation component in which the method validation logic is implemented.
The validator is a Spring Bean (which is also exposed via JMX for external configuration).
I fail to inject the MethodNameValidator bean into the filter.
The filter is a bean also.
Is there a way to achieve this?
If I could set a logback filter dynamically
then I could initialize my filter as a bean, get the desired logger
by name and apply the filter.
Can't figure out how to do it via the provided Logback api.
This works for me.
#Component
public class DiscoveringPostProcessor implements BeanPostProcessor, ApplicationContextAware {
ApplicationContext applicationContext;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// TODO Auto-generated method stub
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof CustomLoggingFilter){
Map<String, TurboFilter> filterBeans = applicationContext.getBeansOfType(TurboFilter.class);
for (TurboFilter filter : filterBeans.values()) {
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.addTurboFilter(filter);
}
}
return bean;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
2.
#Named("customLoggingFilter")
public class CustomLoggingFilter extends TurboFilter {
#Autowired
private TestService ts;
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
System.out.println(ts.doTest());
return FilterReply.ACCEPT;
}
}
Thats how we nailed it. Turbo filters instead of event filters. Thx ;)
//filter definition
public class MethodLoggingFilter extends TurboFilter {
private MethodNameValidator methodNameValidator ;
public MethodLoggingFilter(MethodNameValidator methodNameValidator) {
this.methodNameValidator = methodNameValidator;
}
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) {
return shouldApplyFilter(level, MDCUtils.getSmsChannelId(), MDCUtils.getMethodName()) ? FilterReply.DENY : FilterReply.NEUTRAL;
}
private boolean shouldApplyFilter(Level level, String accountId, String methodName) {
if (level == Level.WARN || level == Level.ERROR) {
return false;//warn and error are logged for any method
}
if (methodName == null) {
return false;
}
Integer accountId = accountId != null ? Integer.valueOf(accountId) : null;
return !methodNameValidator .isLoggingAllowed(accountId, methodName);
}
}
//configuration
#Bean
public MethodLoggingFilter methodLoggingFilter(MethodNameValidator methodNameValidator) {
MethodLoggingFilter filter = new MethodLoggingFilter(methodNameValidator);
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.addTurboFilter(filter);
return filter;
}

Injecting a service via a InvocationHandler

Is there a clean way in Spring (with no XML) to have an interface wired to an invocation handler? Currently I have to do something like this:
#Inject
private ServiceProxyCreator services;
private MyServiceInterface service;
private MyServiceInterface getService() {
if ( service == null )
service = services.createProxy( MyServiceInterface.class );
return service;
}
Where #createProxy is simply an implementation of something like this:
#SuppressWarnings( "unchecked" )
public <T> T createProxy( Class<T> type ) {
JobRpcHandler handler = new JobRpcHandler();
handler.setServiceName( type.getSimpleName() );
return (T) Proxy.newProxyInstance(
type.getClassLoader(), new Class[]{type}, handler );
}
But with all this DI functionality in Spring it seems like I should be able to do this all automatically so that I can simply do the following:
#Inject
private MyService service;
With the injection customized in some way that I don't know to create the Proxy behind the scenes without having to call #createProxy.
Any suggestions on a more elegant approach?
Take a look at FactoryBean. You can write your own this way:
public class ServiceProxyFactoryBean implements FactoryBean<Object>
private Class<T> type;
public DutySetFactoryBean(Class<?> type) {
this.type = type;
}
#Override
public synchronized Object getObject() {
JobRpcHandler handler = new JobRpcHandler();
handler.setServiceName(type.getSimpleName());
return Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler);
}
#Override
public Class<?> getObjectType() {
return type;
}
#Override
public boolean isSingleton() {
return true;
}
}
and use it in your configuration file:
<bean class="package.name.ServiceProxyFactoryBean">
<constructor-arg>
<value type="java.lang.Class">package.name.MyServiceInterface</value>
</constructor-arg>
</bean>
or, using Java configuration, that way:
#Bean
public ServiceProxyFactoryBean myServiceFactoryBean() {
return new ServiceProxyFactoryBean(MyServiceInterface.class);
}
#Bean
public MyServiceInterface myService() {
return (MyServiceInterface)sessionFactoryBean().getObject();
}
If you want automatically create proxies for all the annotated interfaces in a classpath, you can define your own BeanDefinitionRegistryPostProcessor. Here you must scan your classpath with ResourceLoader using the following pattern:
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
Resource[] resources = patternResolver.getResources(
"classpath*:" + packageName.replace('.', '/') + "/**/*.class");
for (Resource resource : resources) {
MetadataReader reader = metadataReaderFactory.getMetadataReader(resource);
if (!reader.getAnnotationMetadata().isAnnotated(
MyProxyAnnotation.class.getName())) {
continue;
}
Class<?> cls = Class.forName(reader.getClassMetadata().getClassName(), true,
resourceLoader.getClassLoader());
String factoryBeanName = createNewName();
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(
ServiceProxyFactoryBean.class);
bdb.addConstructorArgValue(cls);
registry.registerBeanDefinition(factoryBeanName, bdb.getBeanDefinition());
bdb = BeanDefinitionBuilder.genericBeanDefinition(cls);
bdb.setFactoryBean(factoryBeanName, "getBean");
registry.registerBeanDefinition(createNewName(), bdb.getBeanDefinition());
}
Now, for all interfaces, annotated with MyProxyAnnotation, you have a proxy, which you can inject into your beans. For example:
#MyProxyAnnotation
public interface MyServiceInterface {
void foo();
}
And
#Component
public class MyBean {
#Autowired
private MyServiceInterface myService;
}
That's all. No configuration needed.
I am not sure this code works or even compiles. It not the final solution, just a general way you should move toward. So you should research and debug a little.

Categories

Resources