I've been trying to implement a generic interface to support multiple access controls.
Currently, I have the following controller:
#RequestMapping(path = "/api")
public interface MyController {
#GetMapping(value = {"/public/workers/name/{workerString}"})
ResponseEntity<T> searchByName(#PathVariable String workerString);
#GetMapping("/hr/worker/general/{id}")
ResponseEntity<T> general(#PathVariable int id);
}
I have an interceptor which catches the request and checks wheather the user has the "hr" permissions.
Now I have the need to add another scenario, where the user can have "hd" permission and he can execute the same procedure.
The easy solution is to do the following:
#GetMapping(value={"/hr/worker/general/{id}","/hd/worker/general/{id}"})
ResponseEntity<T> general(#PathVariable int id);
and my interceptor will check in a generic manner whether the user has the permission depends on the url.
My issue with this is scalability. what if tomorrow I have to add another type of permission? "hm" "hs" and so on.. I don't want to add more urls to the mapping.
Is that possible to do something like that? :
#GetMapping("/{permission}/worker/general/{id}")
ResponseEntity<T> general(#PathVariable int id);
Obviously this doesn't work since I have to consume the declared path variable.
what are the alternatives here and what would be considered a good practice?
I would appreciate any thought or help here. Thanks.
Related
I have the following two API-methods:
#PatchMapping("/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<Project> updateProjectInactivity(#PathVariable long id, #RequestBody InactivityDTO inactivityDTO)
throws ProjectNotFoundException {
return projectService.updateProjectInactivity(id, inactivityDTO);
}
#PatchMapping("/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<Project> updateProjectStatus(#PathVariable long id, #RequestBody StatusDTO statusDTO)
throws ProjectNotFoundException {
return projectService.updateProjectStatus(id, statusDTO);
}
The two methods have a different #RequestBody, but im currently getting an error because both of them have the same mapping.
Is there a way to have the same mapping for different methods with different RequestBodies? If not, whats the best workaround solution to achieve what i want? I could think of giving them a different #RequestParameter, but that would be ugly, because i dont need that parameter. It would be only used to achieve different mapping.
It is because of the #PatchMapping("/{id}") in both methods.
You can have the same url with different request mappings. like below
#DeleteMapping("/{id}")
#PatchMapping("/{id}")
You should start using different request path since both the methods are responsible for different actions or changing different sub domain. (As per rest convention)
for editing the status
#PostMapping({"{id}/status"}) - It represents that you are editing the status of an Object.
similarly, you should use different request path for inactivity
This question already has answers here:
Design RESTful query API with a long list of query parameters [closed]
(4 answers)
Closed 3 years ago.
I am designing a rest API, to get a resource based on some parameters but in some cases these parameters are between 15-20 in number.
I am thinking of using a POST request to get the resource based on these 15 parameters. I know that POST request should not be used in case of getting the resource.
I want to know if there is a better option to handle this then sending POST request?
You can use Get service by using Map. It will accept all param.
/test?A=ABC&B=123
#RequestMapping(value="/test",method = RequestMethod.GET)
public String testUrl(#RequestParam Map<String, String> parameters)
{
println(parameters.get("A"));
println(parameters.get("B"));
return parameters.get("A");
}
Out Put Will Be
ABC
123
GET doesn't restrict the number of parameters
the only restriction is the length of the URL (which contains these parameters)
So if you're expecting that the parameters and values would cause a long URL, you can use POST instead
Standard says that URL length should be not more than 2,083 characters
even if some browsers/servers allow more, it's better to stick on this value for a wide-range support for all browsers/servers/gateways
In order to make your #Controller code more concise (e.g. get rid of 15x #RequestParam) you can use #ModelAttribute annotation.
#GetMapping(value="/things")
public List<Thing> findAllThings(#ModelAttribute ThingDTO thing) {
// logic
}
and your ThingDTO class like that:
public class ThingDTO {
private String name;
private ThingType type;
[...] //getters, setters etc.
}
This is going to map your class attributes to the #RequestParams. The code looks a bit cleaner imho.
When it comes to the URL length you should check the topic here: What is the maximum length of a URL in different browsers? and decide if there's possibility of exceeding the limit.
What should you use? For data retrieval I'd in 99% cases go with GET so if the above is not a blocker for you, go with GET. If there's a chance of exceeding the limit, go with POST.
The parameter length shouldn't be a problem to handle for the server.
You should read about how to design a rest api. I would recommend you to build it like this:
/api/{version}/{entity}/{id}
If you are using Spring Boot this is very simple to build.
Just write a Controller like this
#RestController
#RequestMapping("/api/users")
public class UsersService {
#Autowired
UsersRepository userRepo;
#RequestMapping(value="/find-all", method=RequestMethod.GET)
public Page<User> getAllUsers(#RequestParam(value="page", defaultValue = "0", required=false)Integer page,
#RequestParam(value="size", defaultValue= "20", required=false)Integer size){
...
}
#RequestMapping(value="/find/{id}")
public ResponseEntity<User> getUserById(#PathVariable(name="id")Long id){
...
}
#RequestMapping(value="/save", method=RequestMethod.POST)
public ResponseEntity<User> createUser(#RequestBody User user){
...
}
#RequestMapping(value="/delete/{id}", method = RequestMethod.GET)
public ResponseEntity<Void> deleteUser(#PathVariable(name="id")Long id){
...
}
}
Hope this sample code helps you to build your api. Just pass your ID as a PathVariable like shown in deleteUser() or findUser().
If you need more Parameters, you can extend the list or use RequestParam like used in getAllUsers().
The parameters are optional but need a default value.
In short:
I'd like to return different JSONs, with say less attributes, when a request comes from a phone than when it comes from a desktop pc.
I want to build a REST service.
The service will serve data based on JPA entities.
The service is declared with #Path.
Depending on the User-Agent header, I want to serve a richer JSON for desktop than for mobile devices. Selection to be done serverside.
Is there a better way than to build a second serializer and use a condition (if(request useragent ) to call them (in every method) and be forced to return some String instead of any Object (making #Produces annotation unused).
Thank you
One way it to add a PathParam or QueryParam to the Path to tell the device type in the request, so the service can be able to understand the type of device, from which the request is and create the appropriate JSON.
Please check the most voted SO answer to find out whether the request is from mobile or desktop and add the parameter accordingly
You can use jax-rs resource selector, which will use different sub-resource depending on user-agent string.
#Path("api")
public UserResource getResourceByUserAgent() {
//the if statement will be more sophisticated obviously :-)
if(userAgent.contains("GT-I9505") {
return new HighEndUserResource();
} else {
return new LowEndUserResource();
}
}
interface UserResource {User doSomeProcessing()}
class HighEndUserResource implements UserResource {
#Path("process")
public User doSomeProcessing() {
//serve
}
}
class LowEndUserResource implements UserResource {
#Path("process")
public User doSomeProcessing() {
//serve content for low end
}
}
By invoking "/api/process" resource the response will depend on userAgent. You can also easily extend the solution for other devices, and implement MiddleEndUserResource for example.
You can read more information about sub-resources here:
Apologies as I am fairly new to Jersey. I've been trying to find a way to have instance-level access authorization using Jersey resources, but the most granularity I'm seeing is Role or static instance-level permissions. I'm a little puzzled because it
To describe better what I mean: suppose an User owns a group of Post resources - presumably this user has the role Author. I don't want every User who is an Author to be able to modify every Post, though. What is the easiest way to control this?
Is this the kind of authorization that's dealt with within the resource class method? Should I be creating a custom Authorization filter? If so, are there any examples of such a thing out there? I'm a little puzzled as it seems like such a common use case.
Thanks!
The reason there isn't much out there in terms of examples is that it's probably down to your own data model as to how you handle this.
Taking a simple example, if each Post has an owner then your resource would probably look something like this:
#PUT
#Path("{id: [A-Fa-f0-9]+}")
#Consumes(MediaType.APPLICATION_JSON)
public T update(#Context HttpServletRequest request, final T item, #PathParam("id") final String id)
{
final Post post = getPostbyId(id);
if (!post.allowedToUpdate(request.getUserPrincipal())
{
throw new UnauthorizedException();
}
// Authorized, carry on
}
There are no end of variations on this theme, but if you're doing resource-level authorization you probably want to do it in something like this way, where you obtain the resource given its ID and then decide if the user is authorized to carry out the requested operation.
I have a simple Controller that looks like this:-
#Controller
#RequestMapping(value = "/groups")
public class GroupsController {
// mapping #1
#RequestMapping(method = RequestMethod.GET)
public String main(#ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #2
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public String changeGroup(#PathVariable Long id, #ModelAttribute GroupForm groupForm, Model model) {
...
}
// mapping #3
#RequestMapping(method = RequestMethod.POST)
public String save(#Valid #ModelAttribute GroupForm groupForm, BindingResult bindingResult, Model model) {
...
}
}
Basically, this page has the following functionalities:-
User visits main page (/groups GET).
User creates a new group (/groups POST) or selects a specific group (/groups/1 GET).
User edits an existing group (/groups/1 POST).
I understand how both GET request mappings work here. Mapping #2 is defined, otherwise (/groups/1 GET) will cause a "No mapping found" exception.
What I'm trying to understand here is why mapping #3 handles both (/groups POST) and (/groups/1 POST)? It makes sense that it should handle (/groups POST) here since the request mapping matches the URI. Why (/groups/1 POST) isn't causing a "No mapping found" exception being thrown here? In fact, it almost seems like any POST with URI beginning with /groups (ex: /groups/bla/1 POST) will also be handled by mapping #3.
Can someone provide a clear explanation of this to me? Thanks much.
CLARIFICATION
I understand the fact that I can use more appropriate methods (like GET, POST, PUT or DELETE)... or I can create yet another request mapping to handle /groups/{id} POST.
However, what I want to really know is...
.... "Why does mapping #3 handle /groups/1 POST too?"
The "closest match" reasoning don't seem to hold true because if I remove mapping #2, then I would think mapping #1 will handle /groups/1 GET, but it doesn't and it causes a "No mapping found" exception.
I'm just a little stumped here.
This is complicated, I think it is better to read the code.
In Spring 3.0 The magic is done by method public Method resolveHandlerMethod(HttpServletRequest request) of the inner class ServletHandlerMethodResolver of org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.
An instance of this class exists for every Request Controller Class, and has a field handlerMethods that contains a list of all the request methods.
But let me summarize how I understand it
Spring first checks if at least one handler method matches (this can contain false negatives)
Then it creates a map of all really matching handler methods
Then it sorts the map by request path: RequestSpecificMappingInfoComparator
and takes the first one
The sorting works this way: the RequestSpecificMappingInfoComparator first compares the path with the help of an AntPathMatcher, if two methods are equal according to this, then other metrics (like number of parameters, number of headers, etc.) are taken into account with respect to the request.
Spring tries to find the mapping which matches the closest. Hence, in your case of any POST request, the only map found for the request type is Mapping# 3.
Neither of Mapping 1 or Mapping 2 matches your request type, and hence are ignored.
May be you can try removing the Mapping #3, and see that Spring throws a runtime error since it does not find a match!
I would add a PUT mapping for /groups/{id}. I guess POST would work too but not strictly correct from a HTTP perspective.
adding #RequestMapping("/{id}", POST) should cover it?
add #PathVariable to the Long id parameter in mapping #2