i'm adding lots of bold because someone downgraded my question which is think is strange...
i went from this which worked which means things are configured correctly for #PreAuthorize...
#RestController
#RequestMapping('/people')
public PersonController extends BaseController {
#PreAuthorize("#pesonId != principal.id")
#RequestMapping(value="updatePerson", method={RequestMethod.POST}, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody SimpleResponseStatus updatePerson(#RequestParam(personId) final Long personId, #RequestParam(value) final String value, final HttpServerRequest request, final HttpServletResponse response)
{
Person p = personRepo.findById(personId);
p.setValue(value);
personRepo.save(p);
}
}
and moved to this which doesn't work ... the #PreAuthorize in the Repository save()...
public interface PersonRepository extends JpaRepository<Person,Long> {
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("#p.id != principal.id")
Person save(person p);
}
and now i get a "Failed to evaluate expression '#p.id != principal.id'
One difference between when it was working on the Controller was i did #personId and not #p.id so i don't know if the object vs primitive in the expression is the problem or if Controller vs Repository (where i do the evaluation) is the problem.
So i have a few questions...
Do i have to do anything special to get the PreAuthorize working in the Repository?
Nothing to do with Spring security but why was i forced to add the SuppressWarnings? i can see if i was returning List<Person> maybe but i thought that was strange.
There is another instance where i will want to do an a PreAuthorize expression like "#p.group.id != 3" ... is there a limit to the levels that can be in an evaluation? i.e. level = obj.obj.obj.obj.value
Another interesting thing is that when i had it working with the Controller i didn't need curly braces "#{userId != 3}" but it worked with "#userId != 3" and i got that syntax from here.
Bottom line, i had it working in a Controller but without an object parameter and now i need it to work in a Repository and with an object parameter. And i've tried #person.id != 3 as well as #{person.id != 3} and neither work.
i found the answer to my own question: here
which is basically for the Repository you have to add a parameter name via annotation since the debug isn't compiled into the interface.
it took me a long time to finally find the answer as it took trial and error of me trying different EL syntax and finally one syntax i chose gave me a different (and better) error message and from there i found the link above.
Anyway, whoever downgraded my question should have just posted the link i just did above instead of downgrading me. that is just mean, really.
public interface PersonRepository extends JpaRepository<Person,Long> {
#SuppressWarnings("unchecked")
#Override
#PreAuthorize("#p.id != principal.id")
Person save(#Param("p") person p); //the #Param annotation is needed!
}
also, it is interesting how some places i see {} are needed and other places not. i did not need braces for this to work.
you must first:
- enable global method security
in your spring security config just add
#EnableGlobalMethodSecurity(prePostEnabled=true)
the you can safely use #PreAuthorize and #PostAuthorize
correct SPEL syntax should be
#{pesonId != principal.id}
but...before this you should ensure both parameter are present in SPringEvaluationContext.
I suggest to use Spring approach, which doesn't fit you question but gives you a different point of view of the problem.
Usage of expression bases access control
https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html
ex: #PreAuthorize("hasRole('ADMIN')")
Spring security accept any valid Spel inside annotation.
Take a look here:
http://www.baeldung.com/spring-security-expressions-basic
Related
I am stuck with a problem in spring boot. I am trying to give extra functionality to some RestControllers, and I am trying to achieve it with some custom annotations. Here is an example.
My annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation {
String someArg();
}
My aspect:
#Aspect
#Component
public class MyAspect {
#Around(
value = "#annotation(MyCustomAnnotation)",
argNames = "proceedingJoinPoint,someArg"
)
public Object addMyLogic(ProceedingJoinPoint proceedingJoinPoint, String someArg)
throws Throwable
{
System.out.println(someArg);
return proceedingJoinPoint.proceed();
}
}
My method:
#MyCustomAnnotation(someArg = "something")
#GetMapping("/whatever/route")
public SomeCustomResponse endpointAction(#RequestParam Long someId) {
SomeCustomResult result = someActionDoesNotMatter(someId);
return new SomeCustomResponse(result);
}
Mostly based on the docs (https://docs.spring.io/spring/docs/3.0.3.RELEASE/spring-framework-reference/html/aop.html - 7.2.4.6 Advice parameters) I am pretty sure, it should work.
I am here, because it does not...
What drives me crazy, is that even Intellij, when tries to help with argNames (empty string -> red underline -> alt+enter -> Correct argNames attribute) gives me this, and keeps it red...
Based on the docs, proceedingJoinPoint is not even needed (it does not work without it either): "If the first parameter is of the JoinPoint, ProceedingJoinPoint..."
With the current setup, it says "Unbound pointcut parameter 'someArg'"
At this point, I should also note, that without the args it is working fine.
I have two questions, actually:
Why does this does not work? (That was pretty obvious)
If I would like to give some extra functionality to some controllers, and I would like to parameterise it from the outside, is it the right pattern in spring boot? (With python, it was quite easy to do this with decorators - I am not quite sure, that I am not misguided by the similar syntax)
One example (the example above was pretty abtract):
I would like to create a #LogEndpointCall annotation, and the developer of a route can later just put it on the endpoint that he is developing
...however, it would be nice, if he could add a string (or more likely, an enum) as a parameter
#LogEndpointCall(EndpointCallLogEnum.NotVeryImportantCallWhoCares)
or
#LogEndpointCall(EndpointCallLogEnum.PrettySensitiveCallCheckItALot)
so that the same logic is triggered, but with a different param -> and a save to a different destination will be made.
You cannot directly bind an annotation property to an advice parameter. Just bind the annotation itself and access its parameter normally:
#Around("#annotation(myCustomAnnotation)")
public Object addMyLogic(
ProceedingJoinPoint thisJoinPoint,
MyCustomAnnotation myCustomAnnotation
)
throws Throwable
{
System.out.println(thisJoinPoint + " -> " + myCustomAnnotation.someArg());
return thisJoinPoint.proceed();
}
It will print the something like this with Spring AOP
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
and something like this with AspectJ (because AJ also knows call joinpoints, not just execution)
call(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
execution(SomeCustomResponse de.scrum_master.app.Application.endpointAction(Long)) -> something
If you want your method to intercept method that take on consideration args you must explicit mention that in you pointcut expression , to make it work here is what you should do :
#Around(
value = "#annotation(MyCustomAnnotation) && args(someArg)",
argNames = "someArg")
notice that i add && args(someArg), you can add as much arguments as you want, in argNames you can omit proceedingJoinPoint.
I have a CRUD repository from this example of Spring Data. I'm trying to add custom permission evaluation, but in my implementation of PermissionEvalutor, targetDomainObject is always null.
ItemRepository
#PreAuthorize("hasRole('ROLE_USER')")
public interface ItemRepository extends CrudRepository<Item, Long> {
#PreAuthorize("hasPermission(#entity, 'DELETE')")
<S extends Item> S save(S entity);
#PreAuthorize("hasRole('ROLE_ADMIN')")
void delete(Long id);
}
Following the suggestions in the answers to this question of making the interface and implementation parameter names to match, I've tried changing entity by item in both the expression and the method parameter. I'm not sure what implementation should match against what interface here, so I'm guessing is SimpleJpaRepository against ItemRepository/CrudRepository. Anyway, it doesn't work, targetDomainObject is always null. Same for targetId in the other method.
Debugging MethodSecurityEvaluationContext.lookupVariable shows that args.length = 0, inside the method addArgumentsAsVariables(), that then logs Unable to resolve method parameter names for method: public abstract xx.xxx.Item xx.xxx.ItemRepository.save(xx.xxx.Item). Debug symbol information is required if you are using parameter names in expressions.. At lookupVariable, everything is null.
Is the debug symbol not #? What am I doing wrong?
Haven't looked in the actual code, but judging from what you write about the debug information, Spring isn't able to find the parameter names, probably since the come from interfaces and those aren't included in the bytecode by default.
Try adding a -parameters compiler flag. Also see this answer for a probably similar problem: https://stackoverflow.com/a/40787280
Using spring-web, I am mapping a method to receive a request containing dots "." on the path:
#RequestMapping(value = "download/{id:.+}", method = RequestMethod.GET, produces = "application/xls")
public String download(#PathVariable(value = "id") String id) { ... }
For example, /download/file.xls should be a valid address. But when I try to access that address, Spring returns Could not find acceptable representation as if it was trying to find a resource named file.xls.
Spring shouldn't execute download method rather than try to find a resource named as the path variable?
Obs.: my application is a spring-boot application.
Your #RequestMapping says it produces "application/xls", but your return type is a String and you haven't annotated the return type with #ResponseBody.
If you want to return an Excel spreadsheet, you need to produce that spreadsheet on the server and return it as a byte[] from your request mapping. I'm not sure how or why you'd return a String, unless you're controller is a simple #Controller and you're returning the view name.
Have you tried configuring your RequestMappingHandlerMapping
handler.setUseSuffixPatternMatch( false )
(I was configuring my RequestMappingHandlerMapping anyway, so for me I just needed to add that line - chances are you may be letting Spring Boot autoconfig that class).
See https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.html#setUseRegisteredSuffixPatternMatch-boolean-
Possibly you may need to turn off content negotiation as well - I can't remember exactly what Spring Boot default content negotiation is, but it might be affecting your case.
#Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false)
}
Worth noting that if you are working on a wider/existing application then both these configurations have possible implications more widely, so if that is the case then tread carefully!
After moving from XML setup to a JavaConfig setup many of our RequestMappings have broken and now return ambiguous method errors. Our methods rely on #PathVariable's with regular expressions to determine which to call. For example:
#RequestMapping(value={"/{id:\\d+}/boats"})
public String getBoatsById(#PathVariable("id") Long id, Model model,
HttpServletRequest request) throws Exception {...}
#RequestMapping(value={"/{id}/boats"})
public String getBoatsByName(#PathVariable("id") String id, Model model,
HttpServletRequest request) throws Exception {...}
This use to work with out issue but using the new JavaConfig setup versus the XML setup it breaks with the ambiguous errors due to the mappings.
The JavaConfig class starts as such:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.example", excludeFilters = { #ComponentScan.Filter( Configuration.class ) })
public class WebConfig extends WebMvcConfigurationSupport
Would it have anything to do with the XML setup using the AnnotationMethodHandlerAdapter versus the JavaConfig class now using the recommended RequestMappingHandlerAdapter? Is there a setting I am missing?
Just browsed the source code, AnnotationMethodHandlerAdapter uses a different RequestMappingInfo than RequestMappingHandlerAdapter. The former ignores the pattern/path you have specified when checking for equality while the latter honors it. That's why you are having ambiguous mapping errors. Not sure if it is a bug or not, probably it's good to ask the people at the spring-contrib mailing list.
EDIT
Probably it's a good idea to change your mappings. The {name:reg_exp} syntax was introduced not to solve ambiguity. Excerpt from the official documentation:
Sometimes you need more precision in defining URI template variables.
Consider the URL "/spring-web/spring-web-3.0.5.jar". How do you break
it down into multiple parts?
The #RequestMapping annotation supports the use of regular expressions
in URI template variables. The syntax is {varName:regex} where the
first part defines the variable name and the second - the regular
expression.For example:
#RequestMapping("/spring-web/{symbolicName:[a-z-]+}-{version:\d\.\d\.\d}{extension:\.[a-z]+}")
public void handle(#PathVariable String version, #PathVariable String extension) {
// ...
}
}
Your handler methods were ambiguous from the start, you only provided a hackish way to solve the ambiguity.
I want to manage users projects through a JSON API and I'd like to use a relative path controller. Like this:
#RequestMapping(value="/users/{userId}/projects",
headers="Accept=application/json")
#Controller
public class UserProjectsController {
#Autowired
private UserRepository userRepository;
#RequestMapping(method=RequestMethod.GET)
public #ResponseBody Object getAllUserProjects(#PathVariable String userId) {
User user = userRepository.findById(userId);
if (user == null) {
return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
}
return user.getGivenProjects();
}
}
I'll add numerous methods and every time I'll have to check if the user exists. Instead of adding that piece of code:
User user = userRepository.findById(userId);
if (user == null) {
return new ResponseEntity<String>(HttpStatus.NOT_FOUND);
}
... at starting of every method, I'd like to create a custom annotation which will return a 404 if the user doesn't exist.
I found this tutorial to do that. Is this really as complicated as described? Do you know any other solution? (I'd like to avoid writing 2 classes and more than 50 lines of code only to annotate 4 lines.)
Thank you.
I firstly assume that this check has nothing to do with Security, does it?
I think WebArgumentResolver won't fit your needs. Returning a 404 status might be complicated.
Maybe a custom interceptor can be a better solution. It will be called in every request to your controllers. There you can examine the handler object in order to see if it has a parameter called userId annotated with #PathVariable, for example. Then, you can do the check and use response object for returning a 404 if it's necessary.
Another option would be create a custom #Aspect, but we maybe are overcomplicating the problem. So first, try the previous solution.