Can not get param from json request when using spring aop - spring

I am using spring AOP to check permission
#Component
#Aspect
public class PermissionManager {
#Around(value = "#annotation(requiredPermission) && args(id,..)", argNames = "id,requiredPermission")
public Object checkCanViewFile(ProceedingJoinPoint pjp, String id, RequiredPermission permission) throws Throwable {
...
}
}
Controller
#RequiredPermission(RequiredPermission.OperationType.editProject)
#RequestMapping("/searchFile")
public #ResponseBody
WebFile search(String id, String word) throws TokenExpiredException, FetchException {
...
}
It works on spring mvc test but can not working on real environment. the value of 'id' is null, I doubt spring AOP get this method before jackson objectmapper, is it right? How can fix it?

Related

Spring Boot #ControllerAdvice #ExceptionHandler content type via an annotation

I know e.g. status can be set via an annotation:
#ExceptionHandler(MyException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleMyException(MyException myException) {
return myException.getMessage();
}
I know I can make a ResponseEntity with the appropriate content type like this:
#ExceptionHandler(MyException.class)
public ResponseEntity<String> handleMyException(MyException myException) {
return ResponseEntity
.badRequest()
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
.body(myException.getMessage());
}
Is it possible to set the content type via annotation only, i.e. something like this:
#ExceptionHandler(MyException.class)
#ContentType(MediaType.TEX_PLAIN_VALUE)
public String handleMyException(MyException myException) {
return myException.getMessage();
}
I couldn't find an annotation that would do that.
According to spring documentation (same for boot as well), you can set only #ResponseStatus annotation with ExceptionHandler.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html

Inject Spring Bean in Jackson VirtualBeanPropertyWriter

tldr; I want to add virtual fields while serializing the JPA entity into JSON using Jackson #JsonAppend. The value of the virtual fields must be determined via service managed by Spring. How do I inject my spring-managed service inside a Jackson class?
Technologies: Spring Boot 1.5.10, Spring Data Rest, JPA 2.1, Jackson 2.8.10
Details:
I have a Spring Data managed JPA entity:
#Entity
public class Stream {
...
}
I created a Custom Jackson module with a Mixin to add #JsonAppend virtual field as below:
#Bean
public Module customModule() {
return new CustomModule();
}
#Component
class CustomModule extends SimpleModule {
CustomModule() {
setMixInAnnotation(Stream.class, StreamMixin.class);
}
#JsonAppend(
props = {
#JsonAppend.Prop(name = "canEdit", value = ABACInspector.class)
}
)
abstract class StreamMixin {}
}
The ABACInspector class extends Jackson's VirtualBeanPropertyWriter to determine the value of the virtual field canEdit. If this class does not use a Spring service (sets hard-coded value for example), it works fine and the field shows up in REST API JSON response. But autowiring a Spring bean doesn't work and the object remains null.
#Component
class ABACInspector extends VirtualBeanPropertyWriter {
#Autowired
private PermissionEvaluator permissionEvaluator;
public ABACInspector() {
}
public ABACInspector(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType) {
super(propDef, contextAnnotations, declaredType);
}
#Override
protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean permission = permissionEvaluator.hasPermission(authentication, bean, Action.STREAM_VIEW);
System.out.println("evaluated permission is " + permission);
return permission;
}
#Override
public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type) {
return new ABACInspector(propDef, null, type);
}
}
Below is the NPE error (because permissionEvaluator is never injected):
{"status":"INTERNAL_SERVER_ERROR","message":"Could not write JSON:
(was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException:
(was java.lang.NullPointerException) (through reference chain: org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module$PersistentEntityResourceSerializer$1[\"content\"]->com.example.streammanagement.Stream[\"canView\"])"
I am aware of Spring Data Rest's HalHandlerInstantiator that contains the AutowireCapableBeanFactory but I am not sure how/if that can help here. Refer DATAREST-840
Jackson internally calls withConfig function of your component to build VirtualBeanPropertyWriter.
So if you use breakpoints, you can see that first a component with injected bean is created, then withConfig function is called and new VirtualBeanPropertyWriter object is created which is used by jackson and of course does not have the injected bin (since you called the constructor manually).
So you can change it by this way:
#Component
class ABACInspector extends VirtualBeanPropertyWriter {
private PermissionEvaluator permissionEvaluator;
#Autowired
public ABACInspector(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
}
public ABACInspector(BeanPropertyDefinition propDef, Annotations contextAnnotations, JavaType declaredType, PermissionEvaluator permissionEvaluator) {
super(propDef, contextAnnotations, declaredType);
this.permissionEvaluator = permissionEvaluator;
}
#Override
protected Object value(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean permission = permissionEvaluator.hasPermission(authentication, bean, Action.STREAM_VIEW);
System.out.println("evaluated permission is " + permission);
return permission;
}
#Override
public VirtualBeanPropertyWriter withConfig(MapperConfig<?> config, AnnotatedClass declaringClass, BeanPropertyDefinition propDef, JavaType type) {
return new ABACInspector(propDef, null, type, permissionEvaluator);
}
}

Java method introspection with Spring AOP

I use spring-aop to make some treatments on my services methods. The methods on wich the treatment must occur are annotated with #History. Moreover, the annotation can have some params like "comment". Here is on exemple :
#Service
public class MyServiceImpl implements IMyService {
#Override
#History(comment = "my comment")
public void myMethod() {...}
}
public interface IMyService {
void create();
}
And, I have a aspect defined like this :
#Aspect
#Component
public class MyHistoryAspect {
#AfterReturning(pointcut = "execution(* my.service.package.*(..)) && #annotation(history)", returning = "result")
public void myTreatment(JoinPoint joinPoint, History history, Object result) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
...
}
}
Now, my problem : when I use reflection to find out the value of "comment" in my aspect, I can't find it. The reason : the method is the method signature of IMyService, not the method signature of MyServiceImpl. And if I put my annotation on the interface instead of the service, my Aspect is never reached.
Am I missing something or is it the normal behavior of spring aop ?
Thank you

Injecting Custom Principal to Controllers by Spring Security

servletApi() support of Spring Security is great.
I want to inject custom Principal as this:
public interface UserPrincipal extends Principal {
public Integer getId();
}
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipal user){
// implementation
}
or
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(UserPrincipalImpl user){
// implementation
}
Spring has support for injecting Principal instances with the help of ServletRequestMethodArgumentResolver.
It is injecting principal as this:
else if (Principal.class.isAssignableFrom(paramType)) {
return request.getUserPrincipal();
}
Here is the place where the problem begins. request is here an instance of SecurityContextHolderAwareRequestWrapper. It has an implementation of:
#Override
public Principal getUserPrincipal() {
Authentication auth = getAuthentication();
if ((auth == null) || (auth.getPrincipal() == null)) {
return null;
}
return auth;
}
Because an Authentication is also an Principal. (The only part of spring security I did not like so far. I will ask this a separate question as well.)
This is causing a problem. Because Authentication is a Principal not a UserPrincipal.
How can I resolve this problem? Do I need to implement an authentication which is a UserPrincipal as well? Or should I change HandlerMethodArgumentResolver order a create a custom resolver? (This is not easy for Spring MVC because internal handlers has higher priority.)
As a extra information:
I am using Spring Security M2 and my configuration for AuthenticationManagerBuilder is simply:
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(detailsService);
}
Any help?
Fundamentally this seems like trouble integrating with Spring MVC and not a Spring Security issue. Spring Security has no way of knowing that Authentication#getPrinicpal() implements Principal since the API returns an Object.
I see a few options for you. Each has some pros and cons, but I think the best is using #ModelAttribute and #ControllerAdvice
#ModelAttribute and #ControllerAdvice
The easiest option is annotate a method with #ModelAttribute on custom #ControllerAdvice. You can find details in the Spring Reference.
#ControllerAdvice
public class SecurityControllerAdvice {
#ModelAttribute
public UserPrincipal customPrincipal(Authentication a) {
return (UserPrincipal) a == null ? null : a.getPrincipal();
}
}
Now in your controller you can do something like this:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(#ModelAttribute UserPrincipal user){
// implementation
}
Note that the #ModelAttribute is necessary only to ensure the #ModelAttribute is used over the HttpServletRequest#getPrincipal(). If it did not implement Principal, #ModelAttribute is not required.
#Value and ExpressionValueMethodArgumentResolver
You can also do something like this:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(
#Value("#{request.userPrincipal.principal}") UserPrincipal user){
// implementation
}
This works because the HttpServletRequest is available as an attribute to the ExpressionValueMethodArgumentResolver (added by default by Spring MVC) which allows accessing things via SpEL. I find this less attractive than #ModelAttribute due to the constant that must be in the #Value annotation. It will be nicer when SPR-10760 is resolved which would allow for your own custom annotation to be used like:
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Value("#{request.userPrincipal.principal}")
public #interface CurrentUser { }
#Autowire RequestMappingHandlerAdapter
This is a bit sloppy because the RequestMappingHandlerAdapter has already been initialized, but you can change the ordering of the HandlerMethodArgumentResolvers as shown here:
#EnableWebMvc
#Configuration
public class WebMvcConfiguration
extends WebMvcConfigurerAdapter {
...
#Autowired
public void setArgumentResolvers(RequestMappingHandlerAdapter adapter) {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new CustomPrincipalArgumentResolver());
resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
adapter.setArgumentResolvers(resolvers);
}
}
Subclass WebMvcConfigurationSupport
You can also extend WebMvcConfigurationSupport instead of using #EnableWebMvc to ensure your HandlerMethodArgumentResolver is used first. For example:
#Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
...
#Bean
#Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter()();
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
resolvers.add(new CustomPrincipalArgumentResolver());
resolvers.addAll(adapter.getArgumentResolvers().getResolvers());
adapter.setArgumentResolvers(resolvers);
return adapter;
}
}
I know this is an old question, but as it does come up on top on Google when searching for injecting a Principal, I'll post a 2020 update:
Since Spring Security 4.0 you can just simply inject an #AuthenticationPrincipal into your controller methods:
#RequestMapping(value = "/")
public ResponseEntity<List<Conversation>> listAfter(#AuthenticationPrincipal UserPrincipal user){
// implementation
}
This will work out of the box, no additional config required.

Spring 3 MVC Controller integration test - inject Principal into method

As part of Spring 3 MVC it is possible to inject the currently logged in user (Principle) object into a controller method.
E.g.
#Controller
public class MyController {
#RequestMapping(value="/update", method = RequestMethod.POST)
public String update(ModelMap model, Principal principal) {
String name = principal.getName();
... the rest here
}
}
This is documented as part of the Spring 3 documentation here:
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments.
This works in the production code. However I don't know how to test this.
When I create an integration test (having set up spring security context as well)
and call the controller handle method then the Principal is always null!
public class FareTypeControllerIntegrationTest extends SpringTestBase {
#Autowired
private MyController controller;
#Autowired
private AnnotationMethodHandlerAdapter handlerAdapter;
private final MockHttpServletRequest request = new MockHttpServletRequest();
private final MockHttpServletResponse response = new MockHttpServletResponse();
#Test
public void testUpdate() throws Exception {
request.setRequestURI("/update");
request.setMethod(HttpMethod.POST.name());
... setup rest of request
ModelAndView mav = handlerAdapter.handle(request, response, controller);
.. rest of assertions
}
The tests are running correctly and everything except the Principal is null.
Any ideas?
TIA
Ayub
After a quick look into Spring sources this should work:
request.setUserPrincipal(somePrincipal);
I've tried to do this some time ago, here is the method i used to set up authentication.
protected void setSecurityContext(String login){
userDetailsTest = userManager.loadUserByUsername(login);
TestingAuthenticationToken testingAuthenticationToken = new TestingAuthenticationToken(userDetailsTest, userDetailsTest.getAuthorities());
SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication((Authentication) testingAuthenticationToken);
SecurityContextHolder.setContext(securityContext);
}
Then i just call it in the #Before method of the test.
Hope it helps.
I do something like this in my tests prior to calling code using Spring Security (such as the Principal parameter resolver you are testing):
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("wiseau", "Love is blind"));

Resources