I'm wondering if it's possible to make an AJAX call without the path being available to anyone who knows the URL. For example lets say I've the following code:
#GetMapping("/responsebody")
#ResponseBody
public UserAccount testingResponseBody(Principal principal) {
if(principal != null) {
UserAccount currentUser = userRepo.findByUserName(principal.getName());
return currentUser;
}else {
return null;
}
}
As far as I'm aware, this allows anyone who types in "/responsebody" in the search bar to be able to see the corresponding JSON.
But what if I don't want the corresponding data to be available to the public what do I do then. I just want to use it internally in the application.
I'm new to Spring boot and all this so i'd really appreciate if sombody could clear things up for me.
Thanks
Related
Scenario:
A community webapp where people can form communities about certain topics with a Spring REST backend.
Today I was wondering how one would implement a setting akin "Who can see your email adress".
When a User enters a community and a REST call to e.g. /api/community/1/users is being made, how would it be possible to stop the CrudRepository from serializing a field such as email of User B if the user A making the API call to the backend is not a friend / does not fulfill certain criteria of user B's settings, such as only showing emails to approved contacts. The resulting JSON should contain a list of users with some having a email field and some not.
While searching I was not able to find anything that matches my question. Following are some things I have discovered but don't feel like they are of much help.
Annotating Controller methods / Repository methods with #PreAuthorize, passing in the Principal.
Why I think this might not help: This seems to be a good solution if I want to block someone based on their ID from viewing a ressource completely. Such as Users only being able to see their own data but not others because the Principal ID does not match the requested ressource's id.
Using JsonFilter as described here: https://www.baeldung.com/jackson-serialize-field-custom-criteria
With this approach I don't see a way of checking WHO is making a request for e.g. my email.
This approach seems to fit well for a scenario such as having a boolean flag set to show email or not, for all cases and any requesters.
Creating a new domain object such as "Friend" extending "User", which is only there for overwriting the #JsonIgnore property of User. While a normal User would not have their Email field serialized due to #JsonIgnore, a friend would set #JsonIgnore(false) over email.
I dont like this approach because I feel like it must somehow be possible to implement this functionality without creating new classes only to overwrite Jackson annotations.
Sorry if there isn't any code to show. So far I have only been creating simple entities and mostly theorycrafting how it would be possible to accomplish the above when I saw that the repository exposes everything. I'm usually more home at the Frontend side of things but I want to learn backend with Spring as well, for private as well as professional reasons. I hope the question isn't too basic.
Thank you in advance.
You can use #JsonView from Jackson for it.
First, create a DTO with the fields you want to return and annotate them with #JsonView:
public class UserDto {
#JsonView(NoFriend.class)
private String name;
#JsonView(Friend.class);
private String email;
public static class NoFriend {}
public static class Friend extends NoFriend {}
}
The NoFriend and Friend inner classes are just markers to define what fields should be returned in what case.
Now in your controller, instead of returning a UserDto, you wrap the UserDto in a MappingJacksonValue:
public class UserController {
#GetMapping("/api/community/1/users")
public List<MappingJacksonValue> getUsers(#AuthenticationPrincipal Principal principal) {
List<User> users = service.getUsers();
return users.stream()
.map( user -> {
MappingJacksonValue value = new MappingJacksonValue(UserDto.fromUser(user));
value.setSerializationView(getView(principal, user));
})
.collectors(toList());
}
private Class getView(Principal princapl, User user) {
// return UserDto.Friend.class or UserDto.NoFriend.class, depending the relation of the authentication principal with the user
}
Probably, not the simplest way to implement it. But maybe it will help you to decompose a problem and find an appropriate solution.
I assume that you just want to clear fields on API level, but still gonna fill it in your Objects.
Let's define a model with some security metadata on it:
class UserDTO {
Long id;
String name;
#AllowOnly("hasRole('FRIEND')") // SPeL/any your custom defined language, or simpler:
//#AllowOnly(Role.FRIEND)
String email;
}
Then define a controller class
#RestController
class SomeController {
#GetMapping("/api/community/{id}/users")
public List<UserDTO> getUsers() {
return List.of(
new UserDTO(1, "Bob", "email-you#gonna.see"),
new UserDTO(2, "Jack", "email-you-NOT#gonna.see"))
}
}
So what i propose is to create an aspect, which is gonna clear fields based on your permission model.
#AfterReturning("within(#org.springframework.web.bind.annotation.RestController *)
&& execution(* *(..))", returning="objectToClear")
public void applyFieldPermissions(Object objectToClear) {
// Here i would parse annotations on object fields
// and if found #AllowOnly, check your role to a user.
// and clean up field, if necessary
}
Logic of the aspect is totally dependent on your cases, but for this simple example, need only to implement some method to check your role for specific object
boolean hasRoleOn(UserDto dto, Role role, Authentication currentUser)
I am facing a very weird problem.
I used MyBatis generator automatically generate mappers and xml from my MySQL databases.And used the selectByExample method of the mapper to pass criteria trying to verify the user. Below is the code.
#Service
public class EmployeeServiceImpl implements EmployeeService {
#Autowired
private EmployeeMapper employeeMapper;
#Autowired
private EmployeeExample employeeExample;
#Override
public boolean verify(String username,String password) {
EmployeeExample.Criteria criteria = employeeExample.createCriteria();
criteria.andUsernameEqualTo(username);
criteria.andPasswordEqualTo(password);
List<Employee> list = employeeMapper.selectByExample(employeeExample);
if(list.size()>0) {
return true;
}else {
return false;
}
}
}
When I use SpringMVC controller passing the username and password to the mapper, it just returns only one result. When I pass correct information it will return true, but after that, every incorrect information gets true also.
I'm not sure if is it the MyBatis problem or Spring MVC?
Could anyone help me with that?
Really appreciate!
I just fixed the problem with not injecting the EmployeeExample and "new" one instead. But is it violating the decoupling principle? And how that problem happens? Could anyone explain it a little bit? Appreciate again!
I am working on spring mvc project. Here in each controller i have multiple method assigned to specific url. e.g.
#RequestMapping(value = "/accountDetails")
public String home(HttpServletRequest request){
Book book = (Book) request.getSession().getAttribute("Book");
if (book == null) return "redirect:/";
//other things to do here
return "home";
}
Here I want to check session variable Book is empty or not at the beginning of each method. if it is return to / otherwise do some operation.
Is there any other way to check this null and return rather that the i have done it in the above code. I don't want write the same code at the beginning to each controller method.
So please suggest me an alternative way
These are several solutions. As pointed out by #chrylis, you can use #ControllerAdvice, HandlerInterceptor or even a plain Filter (or it's DelegatingFilterProxy Spring flavour) for a generic, cross-cutting solution. Depending on your current project setup and your requirements one may be easier to implement than the other and it may or may not fit your needs, so make sure to just read the docs and decide if it fits your purpose.
Another approach with a fully programmatic solution would be to use a utility method with Java 8 lambda for the code block that you want to be executed in case book is available.
public static String withBook(Function<Book, String> bookOperation) {
Book book = (Book) RequestContextHolder
.currentRequestAttributes()
.getAttribute("Book", RequestAttributes.SCOPE_SESSION);
if (book == null) {
return "redirect:/";
} else {
return bookOperation.apply(book);
}
}
RequestContextHolder gives you access to the attributes of the current request and session.
You can use the utility method like this:
#RequestMapping(value = "/accountDetails")
public String home() {
return withBook(book -> {
// just implement the part where book is not null
return "home";
});
}
In a Spring 4.2 webapp, I have a rights object in the session from which I can query whether the logged in user has access to a specific page. I can check the access rights from a controller and redirect to another page with a message. The problem is I have to repeat this for every request method in every controller.
I can move almost all the code to an interceptor but I have no access to RedirectAttributes from there, so I don't know how I could add the error message. Is there a way?
#Controller
#SessionAttributes("rights")
#RequestMapping("/f")
public class FController {
...
#RequestMapping(value="/edit/{id}", method=RequestMethod.GET)
public String edit(#PathVariable("id") int id,
#ModelAttribute("rights") UserRights rights,
RedirectAttributes redA, ModelMap model) throws SQLException {
if (!rights.canAccess("/f/edit")) {
redA.addFlashAttribute("errormessage", messages.getMessage("error.noright", null, Locale.getDefault()));
return "redirect:/f/list";
}
... // set up model
return "fEdit";
}
...
}
Yes you can! It is not as well integrated as what you get in controller methods, but you can get access to the output flash map in an interceptor. You can find references for that in the chapter on Using flash attributes in Spring Framework ReferenceManual. And thanks to the ModelAndViewDefiningException, you can still ask Spring to do the redirect and process the output flash map
You could just put something like this in an interceptor:
if (!rights.canAccess("/f/edit")) {
// get access to the output flash map
FlashMap redA = RequestContextUtils.getOutputFlashMap(HttpServletRequest request);
// add the redirect attributes
redA.put("errormessage", messages.getMessage("error.noright", null, Locale.getDefault()));
// prepare the redirection
ModelAndView redirMav = new ModelAndView("redirect:/f/list");
// ask the spring machinery to process the redirection
throw new ModelAndViewDefiningException(redirMav);
}
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.