Skip to content

Instantly share code, notes, and snippets.

@gervaisb
Last active August 29, 2015 14:20
Show Gist options
  • Save gervaisb/f606a53d6c4517042946 to your computer and use it in GitHub Desktop.
Save gervaisb/f606a53d6c4517042946 to your computer and use it in GitHub Desktop.
Custom sping-boot module who set the `Principal` on all public methods with a parameter of type `Principal`
org.springframework.boot.autoconfigure.EnableAutoConfiguration=mvc.traits.WithConfigurer
package mvc.traits;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
/**
* Authentication is a trait who inject a Principal on each public with a parameter of type Principal
*/
public class Authentication implements Trait {
@Override
public Object configure(Object bean) {
AspectJProxyFactory factory = new AspectJProxyFactory(bean);
factory.addAspect(AuthenticationAspect.class);
return factory.getProxy();
}
}
package mvc.traits;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import java.security.Principal;
@Aspect
public class AuthenticationAspect {
@Around("execution(public * *(..))")
public void authenticate(ProceedingJoinPoint pjp) throws Throwable {
final InterceptedMethod method = new InterceptedMethod(pjp);
if ( method.hasParameterFor(Principal.class) ) {
method.setParameter(Principal.class, getCurrentPrincipal());
}
method.proceed();
}
private Principal getCurrentPrincipal() {
return new Principal() {
@Override
public String getName() {
return "Principal from "+getClass().getSimpleName();
}
};
}
}
package mvc.traits;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.util.Arrays;
import java.util.LinkedList;
/**
* Wrapper hover a ProceedingJoinPoint who ease the manipulation of method parameters.
*/
public class InterceptedMethod {
private final LinkedList<Object> parameters = new LinkedList<>();
private final MethodSignature signature;
private final ProceedingJoinPoint joinpoint;
/**
* Create a new InterceptedMethod
* @param joinpoint ^null
*/
public InterceptedMethod(ProceedingJoinPoint joinpoint) {
this.joinpoint = joinpoint;
this.signature = (MethodSignature) joinpoint.getSignature();
this.parameters.addAll(Arrays.asList(joinpoint.getArgs()));
}
/**
* Verify if the method has a parameter for a type assignable to the received `type`
* @param type
*/
public boolean hasParameterFor(Class<?> type) {
return indexOfParameter(type)>=0;
}
/**
* @effects set or replace all parameter of given `type` with the received `value`
* @modifies this
* @param type ^null the parameter type
* @param value the parameter value
* @param <T> parameter type
*/
public <T> void setParameter(Class<T> type, T value) {
final Class[] types = signature.getMethod().getParameterTypes();
int from = -1;
while ( (from = indexOfParameter(type, from+1)) >= 0 ) {
parameters.set(from, value);
}
}
public void proceed() throws Throwable {
joinpoint.proceed(parameters.toArray());
}
private int indexOfParameter(Class<?> type) {
return indexOfParameter(type, 0);
}
private int indexOfParameter(Class<?> type, int from) {
Class<?>[] types = signature.getMethod().getParameterTypes();
for (int i=from; i< types.length; i++) {
if ( types[i].isAssignableFrom(type) ) {
return i;
}
}
return -1;
}
}
package mvc.traits;
public interface Trait {
Object configure(Object o);
}
package mvc.traits;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Import;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@AutoConfigureAfter
@Retention(RetentionPolicy.RUNTIME)
@Import(WithConfigurer.class)
public @interface With{
Class<? extends Trait>[] value();
}
package mvc.traits;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import java.util.Arrays;
@Configuration
class WithConfigurer implements BeanPostProcessor, ApplicationContextAware {
private static final Log LOG = LogFactory.getLog(WithConfigurer.class);
private ApplicationContext ctxt;
@Override
public void setApplicationContext(ApplicationContext ctxt) throws BeansException {
this.ctxt = ctxt;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (bean == null || AnnotationUtils.findAnnotation(bean.getClass(), With.class) == null)
return bean;
final With with = AnnotationUtils.findAnnotation(bean.getClass(), With.class);
if ( LOG.isDebugEnabled() )
LOG.debug("Adding traits "+ Arrays.toString(with.value())+" on bean "+name+" : "+bean+".");
for (final Class<? extends Trait> type: with.value()) {
Trait trait = (Trait) ctxt.getAutowireCapableBeanFactory().createBean(type, 0, true);
bean = trait.configure(bean);
assert bean!=null : "Trait ["+type+"] cannot nullify bean ["+name+"]";
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object o, String s) throws BeansException {
return o;
}
}
@RestController @With({Authentication.class})
public class SampleCtrl {
@RequestMapping("/prove")
public String withPrincipal(Principal principal) {
return principal.getName();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment