Spring Boot - make sure data belongs to current logged in user - java

I have a Spring Boot REST API that I'm building. Im slightly stuck on the correct way to design my API in a way that protects each individual users' data. For example, consider the following database relations:
User -> (Has Many) Projects -> (Has Many) Tasks. (A User has-many Projects, and a Project has-many tasks).
For example, if I design my endpoints in the following way:
GET /api/v1/projects/{projectId}
POST /api/v1/projects/{projectId}/tasks
Just as a simple example for the above, how can I make sure, when creating new tasks for a certain project, that the project belongs to the logged in user?
Currently, I am using JWT tokens via Spring Security as my authentication strategy, and included in the payload of the token I have my Users' id. So with every request I can retrieve the user, but surely that's incredibly inefficient to be making so many requests to the database and check if the user actually has a given project.
Some solution I was thinking about is to simply have endpoints designed like this:
/api/v1/users/{userId}/projects/{projectId}/tasks
And then I can use the user id in the JWT payload and compare it to the user id in the request parameter. But then that would mean with every new relation in my database, the length of the url is going to be massive :) Also I guess it would mean all the business logic would be inside the User service for the whole application, right? Which seems a little odd to me... but maybe I'm wrong.
Im not sure if thats an issue or not, but just trying to design the API to be as elegant as possible.
Thanks again!

Checking if the user has permissions to a project on every request is the correct solution. Consider cases when many other applications / users are calling your API. You want to make sure that your API is as secure as possible and cannot be manipulated from the frontend.
To make it more efficient you should have a way/query to check associations in your database like a simple query that returns true/false which should be quicker than retrieving all the data and comparing in Java code.
And when possible combine multiple database queries into one, like for one of your examples:
GET /api/v1/projects/{projectId}
in this case, don't run a query to get a user's information and a query for the requested project. Instead you could do a single query with a join between the user's table and the project table which should only return a project if the user is associated with it. The best way really depends on how your database is structured.
Adding a user id into the API URL is just redundant information. Just because the user id in the token matches the user id in the URL doesn't mean the user has any kind of permissions to any project.
Another solution to be avoided is to include the user's project ids in the JWT token which you can then compare without making a database request. This is bad for several reasons:
The token should only have required information for the user to access the API, it shouldn't have business logic
Depending on how much business logic you store in the token the token can become large in size. See this post for a discussion on size limits: What is the maximum size of JWT token?
If there is a way for the someone other than the user (like admin) to add/remove a user's association to a project then that change will not be reflected in the token until the token's data is refreshed
EDIT:
On the spring side I have used the #PreAuthorize annotation before to handle these types of method checks. Below is pseudo code as an example.
#RestController
public class MyController {
#PostMapping
#PreAuthorize("#mySecurityService.isAllowed(principal, #in)")
public SomeResponseType api1(SomeRequestType requestData) {
/* this is not reached unless mySecurityService.isAllowed
returns true, instead a user gets a 401/403 HTTP response
code (i don't remember the exact one) */
}
}
#Service
public class MySecurityService {
/*
Object principal - this is spring's UserDetails object that is
returned from the AuthenticationProvider. So basically a Java
representation of the JWT token which should have the
user's username.
SomeRequestType requestData - this is the request data that was
sent to the API. I'm sure there is a way to get the project ID
from the URL here as well.
*/
public boolean isAllowed(Object principal, SomeRequestType requestData) {
/*
take the user's username from the principal, take the
project ID from the request data and query the database
to check authorization, return true if authorized
make this check efficient
*/
return false;
}
}
The annotation and the security service can then be applied to multiple methods. You can have many different security services depending on what your are checking.
There are other ways available too https://www.baeldung.com/spring-security-method-security and this has to be enabled in spring's configuration (also explained in the link).

Hi so if I understood it correctly you want to automatically assign the task that is going to be created with "POST /api/v1/projects/{projectId}/tasks" to the current logged in user.
You could try to add a Parameter 'Principal principal' to your rest controller. The Principal is the user that is sending the request.
After you have your Prinicipal, you could write a simple convert method(for example: convertPrincipalToUser(Principal principal) which returns you the user. Finally you can add your user to the corresponding task)
Here is some more information about it:
https://www.baeldung.com/get-user-in-spring-security

Related

How to get more/optional data for users using keyclaok rest api?

We are using Keycloak for SSO purpose, in particular we are able to use the REST API /admin/realms/{realm}/users to get the basic user details in a Keycloak realm, the response we get is UserRepresentation which seems to have provision for realmRoles and clientRoles as well but by default they are not required/false.
We have a new requirement to fetch the roles of all users, I see there are additional API exposed to get these roles: /auth/admin/realms/realm/users/user-id/role-mappings/realm/, but this means firing another request, and if we have 2k users that means 2k more request.
My question is as UserRepresentation also have properties realmRoles and clientRoles but seems to be optional by default, how can I enable these while firing the request /admin/realms/{realm}/users, and avoid additional request to get roles.
I'm afraid that getting the data you need in one request is not possible: just by looking at the source code of getting all users in UsersResource you can see that realmRoles and clientRoles are never populated.
Having that said, there is one thing that you can do - write your own REST Resource by implementing SPI. In fact, in the past I had a similar problem with groups resource and I ended up writing my own resource. In this case you will need to write custom resource with just one method - getting all users with roles. You can just copy-paste current keycloak logic and add extra bits or extend built-in UsersResource. This, however, is not a single bullet - on the long run you will be required to maintain your own code and upgrades to latest keycloak may not be that simple if some interface will change.

Data validation across different microservices

I've already read lots of topics about it, but still haven't found the better approach.
I have a User. One User may have many Posts. Users and Posts are different microservices. I'm using Spring Boot.
When the front-end call my Posts microservice sending a POST request to /posts/user/1, I need to check if the given userId (1) exists on my Users database. If no, throw an exception telling the front-end that the user doesn't exist. If yes, then insert the given request body as a Post.
The question is: how should I check this information at my backend? We don't want to let this responsibility with the front-end, since javascript is client-side and a malicious user could bypass this check.
Options:
REST communication between the microservices. (Posts microservice call Users microservice asking if the given id exists on his side)
Give Posts microservice access to Users microservice's database
I understand that communication between them will create coupling, but I'm not sure if giving Posts access to Users database is the best option.
Feel free to suggest any options.
You have an option to do interprocess communication between Post and User microservices through RESTful approach.
In case if you just want to check the existence of the resource and don't want any body in response then you should perfer using HEAD http method. Therefore your API endpoint hosted at User microservice will look like -
HEAD user/{userId}
Call this API from Post microservice.
Return 200 / OK if user exist
Return 404 / Not Found if user does not exist
Click here and here to get more details on HEAD method usage and use cases.
For this very particular use case, if you have a security layer, you can(should) make use of user access token, to ensure, that request is processed for the right user, which can be done by validating the token and relying on the fact that if user has token he exist. (As its just not about if user exist)
For any logic other than that, say you want to check if he is allowed to post or other such restrictions it is required to make a call to the user service.
Talking about giving access to the database, it will be against one basic guideline of microservices. Doing so will form a tight coupling between you and user. Its ok to call user service in this case which can decide how to serve this request.
User service on its part should provide ways to answer your queries within the SLA by caching or other mechanisms.
One more thing that you can explore is BFF (Backend for Frontend)
You rightly said you should not expose backend services to frontend or add any logic there, but often frontend pages may not be comfortable in accepting that content on same page is answered via n different back end services and there might be some logic to stitch such queries and thats where you can make use of BFF.
Backend server (in my case node) which take of things like these requiring frontend to make just one call(or less calls) for a given page and at the same time hiding your backend services within.
You're right, you must do a validation at the back end since, I suppose, it's a REST service and requests can be send not only from the UI.
Suppose you have a service implementation:
#Service
class UsersServiceImpl implements UsersService {
private final Users users;
public UsersServiceImpl(Users users) {
this.users = users;
}
#Override
public void addPost(long userId, Post post) {
User user = users.get(userId);
if (user == null) {
throw new UserNonExistent(userId);
}
user.addPost(post);
}
}
where Users is an interface representing a users database and UserNonExistent is a RuntimeException. Then in your controller you can do the following:
#RestController
class UsersController {
private final UsersService usersService;
public UsersController(UsersService usersService) {
this.usersService = usersService;
}
#PostMapping("/posts/user/{userId}")
public void addPostToUser(#PathVariable String userId, #RequestBody Post post) {
usersService.addPost(userId, post);
}
#ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "User does not exist")
#ExceptionHandler({UsersService.UserNonExistent.class})
public void handleUserNonExistentException() {
// Nothing to do
}
}
If the supplied user ID is invalid handleUserNonExistentException() method will be invoked and it will return a BAD REQUEST HTTP status code.

Get logged in userId from a 'me' relative id in /users/me spring boot api

We have a bunch of secured web services (using spring security) defined like this:
#GetMapping("/users/{userId}")
public User getUserInfo(#PathVariable String userId) {
...
#GetMapping("/users/{userId}/addresses")
public User getUserInfo(#PathVariable String userId) {
If I want to get the logged in user, I can add the Principal principal or HttpServletRequest request to the method and spring will support the data for the logged in user, or also I can use SecurityContextHolder.getContext().getAuthentication().
So far so good, if we want to get the logged in user for the api /users/1234 we have no problem. However we got a requirement to support a relative userId value being me where the api would be /users/me.
We could resolve this by adding in each api just an if statement and replacing the userId me with the real logged in userId. We don't like this implementation since we have a boilerplate code in 20 apis. So, another solution we tried was by just adding an aspect to replace the me by the real userId but I don't like using reflection for solving this problem.
So, i was wondering if there is a "spring boot" way of doing this? I haven't found any feature supporting this behavior.
I think the best solution would be to change that URL. If statement is ver explicit too though. There are interceptors as well you could use but I don't see them as being much different to using aspects, pretty evil stuff. But if you do use aspects I would suggest that you have an annotation such as #Me which you could annotated method inputs such as userId with, which would replace them with the id of the principal if the parameter is equal to me.

Reference an object using an attribute other than its ID in the URL

I am trying to build a simple register/log-in system for an Android app, using Spring boot. I have a MySQL database in a virtual machine that contains a table with columns id, username, password. The Spring boot application contains the following classes; Account (the entity), AccountRepository (the repository), AppConfig (the configuration class) and Application (main class). My endpoint is /accounts. If I want to see what accounts are currently registered I simply go to http://<ip address>:8080/accounts for a JSON representation of the database.
I want to modify my Android program so that if a user tries to register an account, it first checks to see if the username is already registered. The problem is that I'm not able to reference a username in the web repository - I have to use the account ID (e.g. http://<ip address>:8080/accounts/2).
Is there a way to modify my Spring boot application to allow me to reference usernames directly rather than through their IDs? So rather than having my Android app looping over each database entry I can send a request to a URL such as http://<ip address>:8080/accounts/johnstone01 to see what response I get (if the response is 200 for instance then I know that the username already exists and consequently make the user choose a different username).
Highly appreciate any advice.
I suggest create POST request http://<ip address>:8080/accounts for creating account and make username checking inside. It might be MySQL-level checking using constraints.
As for separate checking of username existence I suggest to modify GET request by adding query parameter "username":
http://<ip address>:8080/accounts?username=johnstone01
in this case response will return array with only one record or will return empty array if such username does not exist.
I understand that you're making a REST API for your server. In this case the right way to do is to create an endpoint that will accept a POST request (e.g. POST http://:8080/accounts the post request will contain user informations).
From your app if someone tries to register a new account you'll send a POST request to the endpoint you defined with the information to create the new account. All the rest will happend in your service layer. It seems that you don't have one, it's ok for a small project, I guess the service code is in your controller. This mean your controller have the responsibility to check if the entity can be persisted and if yes, will do so.
In your repository just add a method that will accept as a parameter the username and return the entry for that username or null. This way your controller can request it, if it's null then it can persist the new entity and return a HTTP code 200 if not it should return a 409 Conflict code.
Your app then only have to check the return code to know what happend.

Spring Access Control

I working on Spring MVC app. The app funcionality is accessible through ReST API which jsp containing ajax logic consume. I am using spring security with defined roles (USER, COMPANY, ADMIN). Methods use requestMapping with responseBody such as:
www.app.com/auth/{userId}/request/{requestId}
It, of course, support GET for obtaining resource and POST for its creating or updating.
The problem is that after succesful login with, for example, userId = 1 I want GET request with requestId = 99. But when I run WebDev client for Chrome, I can also access another resource with easy request in format
www.app.com/auth/5/request/{requestId}
So basically, I can access resources, which I am not allowed to see. I hope you got the idea, where I am heading.
My question is - What is the best approach to secure this?
I was thinking about storing logged user Id (Integer) in session and comparing it everytime request for resource is made, but it seems to me that I am pulling the wrong end of rope :)
Thank you for any advice
You should have a look into the Expression-Based Access Control section of the spring security documentation.
Example copied from the documentation:
#PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact) {
..
}
This would check if name of the contact is equal to the name of the currently logged in user.
Using this this feature you can build much more sophisticated access rules than you could do with simple roles. However, this does not exclude using roles. You can still keep roles for basic security checks.

Categories

Resources