I have a Spring Boot application which uses Spring Security to Authenticate and Authorise requests using a JWT. However, some requests should only be able to be executed by a particular user. For example:
GET /users/{id}/orders should only return the list of orders if {id} is the current user or the current user is ADMIN
PUT /orders/{id} should only edit the order if the its payer is the current user
PUT /representation-requests/{id}/accept should only work if the current user is the target of the representation request
Because of the usage of JWTs, the way I get the current user's ID is by
String userId = ((DecodedJWT) SecurityContextHolder.getContext().getAuthentication().getDetails()).getSubject();
I've implemented this in the various methods of the services responsible for handling each API call. My question is if there is a more general way to do this using Spring Boot and Spring Security? Or is this not a standard use case?
I have looked at #PreAuthorize annotation in controllers, but it does not suite my needs as the URL parameters are not enough to check the nested entities. #PostAuthorize in controllers seems closer, but because of the JWT I couldn't get it to work (and it also seems a bit clunky to do long code in annotations, not sure it is better than doing it in the service itself). I appreciate a pointer in the right direction.
You'll have to play around with it a bit (I can't give you 100% specifics for JWT), but you basically could use SpEL to achieve what you want. (I'm unsure why you think #PostAuthorize could be a better fit, though)
Either like so, for simple checks (# denotes a method parameter)
#PreAuthorize("principal?.id == #id")
public List<Order> ordersForUsers(Integer id) {
// do someting
}
Or like so, delegating to a custom bean (# denotes a bean name, hash the method param, 'admin' the role you want, or any other parameter, really)
#PreAuthorize("#yourBean.hasPermission(principal, #id, 'admin')")
public List<Order> ordersForUsers(Integer id) {
// do someting
}
Related
I have an application where single user can work in contexts of multiple companies. We call such a connection (user<->company) a permit. Every one of this permits can have different sets of permissions/roles. We want user to login just once and then he can simply change permits within application without need to enter password again.
Till now we had only one application and kept this whole permission model in our own DB. Unfortunately now we have to support second application which should inherit those permits. I was wondering wether is possible to move that model to keycloak so we don't have to replicate it to every single db and keep it in sync manually.
I have searched keycloak documentation regarding this topic but have found no information att all, which seems quite odd, because I don't think we are the first one working with multiple context application.
So now I'm asking is it possible to configure our model in keycloak and if so, how to do it? Eventually are there different options? I guess that I can provided that model as a claim with json structure but that doesn't feel right to me. I was thinking about custom IDP which could provide such claims based on DB so there no spelling errors and less repetition but I feel there should be a better way.
You could try to write your own Keycloak provider (SPI). There is a built in mechanism that allows you to expose REST endpoint on the Keycloak: https://github.com/keycloak/keycloak/tree/master/examples/providers/domain-extension
That REST could be called with authorized context only for example by passing Access-Token (Authorization header with Bearer value). On the provider level (through implementation of: org.keycloak.services.resource.RealmResourceProviderFactory and org.keycloak.services.resource.RealmResourceProvider) you have access to user's Keycloak session and object UserModel like in the following code:
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(keycloakSession, keycloakSession.getContext().getRealm());
UserModel userModel = authResult.getUser();
UserModel class has methods for getting and setting attributes, so some information that indicates the current permit/company ID can be stored there. You can use REST methods exposed on the Keycloak to modify the model within the 'session' (represented by Access-Token).
The Github example shows also how to use another Keycloak provider (ex. built-in JPA provider) from you custom provider's level, so using that approach you could try to connect to the database with your permits/company informations. Of course the datasource representing you database should also be registered as Keycloak datasource.
I would like to know the right approach to defining a RESTful service for a generic use case.
I am writing a RESTful API to update the current user's profile. Should we send current user's ID within the request? This would require validation on server side such that user can only edit his own details. So I would need to add check on user ID and Principal object. Also, the client side has to maintain current user ID somewhere.
POST /user/{id}
Alternatively, I can just skip sending the user ID and fetch the user details from Principal object. As we know there is nothing like stateless secured API, would it be the right approach?
I am not aware of any feature in Spring that would validate the current user for me, as required in first approach. If it is present then please let me know.
Consider the case where an administrator, who is authorized to update other users, wants to send the update. In this case, it would be to POST /user/{id}. There's no reason that this shouldn't also be the case for an ordinary user.
With Spring Security, you can use an #PreAuthorize expression on your controller method. It would look something like this:
#PostMapping("/user/{id}")
#PreAuthorize("hasRole('ADMIN') || (principal.id == #id)")
public User updateUser(#RequestBody User newInfo, #PathVariable Long id) {
...
}
I have implemented a REST application with some complicated authorization requirements.
Here's a summary
My customers purchase a proxy device called Collector that enables their home automation control to be centralized. My customers also purchase multiple home automation devices (let's call them HADevices) that report their metrics through the collector to my REST application.
An admin(who is my customer service rep), with role ROLE_ADMIN, should be able to look at any data from any Collector or HADevice. A customer, with role ROLE_USER role, should only be able to view data about the Collector or an HADevice that s/he owns.
The Collector, with role ROLE_COLLECTOR is the only role authorized to insert data i.e. create or update a resource in my REST service. Let's call this url /deviceMetrics (POST). A Collector can insert metrics for any HADevice associated with the customer. HADevices have no role and do not interact with my REST application. A Collector can only insert records to HADevices that have the same customer as the Collector.
I am using spring security 4.0 for authentication and #Secured annotation for authorization. However, I find that my code is cluttered with repetitive permission validations which take up a majority of my logic. The basic insertions and retrievals are pretty straightforward.
I want to use a PermissionEvaluator to centralize Access Control. I have to secure the following urls
GET /collectors/{id}/deviceMetrics - I retrieve the user from the
Principal and verify that the Collector with id={id} in my spring
data repository belongs to the Principal and if not I send a 403
GET/hadevices/{id}/deviceMetrics - I retrieve the user from the
Principal and verify that the HADevice with id={id} in my spring data
repository belongs to the Principal and if not I send a 403.
POST /collectors/{id}/deviceMetrics - I retrieve the Collector uniqueId
from the Principal and make sure that the Collector's id matches the
{id} in the URL
POST /hadevice/{id}/deviceMetrics - I retrieve the
Collector uniqueId from the Principal and the associated Customer. I
also pull the Customer associated with HADevice with id={id} and
compare the two. If they are unequal, I send a 403.
My application is littered with such complex authorization requirements for each REST resource, and I want to use a custom org.springframework.security.access.PermissionEvaluator, specifically by implementing the following method.
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission)
I'm planning to use a combination of targetType and request.getUrl() to get a specialized Evaluator for each url and resource.
Is there a better way to do this?
Your question is quite broad, but I think you can get away with quite simple logic for most cases.
GET /collectors/{id}/deviceMetrics
Given that you have a DeviceMetrics class with suitable properties, you can annotate your data repository with something like:
#PostAuthorize("hasRole('ROLE_ADMIN') or (hasRole('ROLE_USER') and returnObject.collector.owner = authentication.name)")
public DeviceMetrics getDeviceMetrics(long deviceId);
(This assumes that DeviceMetrics class has a property collector which has a property owner which is the username.)
That doesn't need a PermissionEvaluator at all. Maybe you need one for more complex cases:
POST /collectors/{id}/deviceMetrics
#PreAuthorize("hasRole('ROLE_COLLECTOR') and hasPermission(#collectorId, 'com.example.Collector', 'WRITE')")
public void saveDeviceMetrics(long collectorId, DeviceMetrics deviceMetrics);
You only need one PermissionEvaluator since you get all the information you need as method arguments.
For those who are wondering what my solution looks like, I borrowed from this example.
It's old and it's based on xml configuration which I am not particularly fond of. But the idea is to create a Map and initialize the custom PermissionValidator and to store the authorization logic in the Permission interface implementations.
The biggest pain point was injecting an autowired HashMap of tuples, but that's an implementation detail that reasonably experienced spring users can figure out.
For our current project, we are integrating JSF and the Spring Framework. I'd like to use Spring Security to handle authentication and authorization. So far, I have implemented a custom PasswordEncoder and AccessDecisionVoter which are working fine. Now I'm trying to secure methods using the #Secured annotation (among others) but I can't get that to work as I would expect it to do.
It seems that the #Secured annotation works for bean methods called directly from the JSF layer, only. Here's a simplified example:
#Named("foobarBean")
#Scope("access")
public class FoobarBean
{
#Secured("PERMISSION_TWO")
public void dummy()
{
}
#Secured("PERMISSION_ONE")
public String save()
{
dummy();
}
}
The method save() is called from the JSF layer like this:
<h:commandButton id="save" action="#{foobarBean.save}" />
Our AccessDecisionVoter is then asked to vote on PERMISSION_ONE but not on PERMISSION_TWO. Is this working as designed (I hope not) or am I doing something wrong (what could that be?).
I'd post more code or config but I'm not sure which part is relevant, and I don't want to clutter this post.
It is a simple problem of Proxy AOP! If you use Proxy AOP for Security, then the Proxy can only intercept calles that go through the proxy. If one method invoke an other method of the same bean directly, then there is no proxy that can intercept this call. -- And this is the reason why only the the Security Annotation of save() is taken in account.
One solution would be using AspectJ AOP instead of Proxy AOP. (It is supported by Spring (Security) too.)
Yes, That is how the AccessDecisionVoter works. It takes all roles allowed on a resource(method in your case) and vote for those roles form the current authenticated user's role. If the Role is matched, then only the permission is granted.
In your case also, the only Role defined for the save method is PERMISSION_ONE so the security system will check against this role only. If logged in user has that role, this method will be executed.
I have a problem with populating a model content in Spring MVC application, based on user role managed by Spring Security.
In my application (simplified example) I've got two roles defined: ROLE_USER and ROLE_ADMIN. Also I've got a page that displays a two separate lists of objects: readers (only available for ADMIN) and books (for both, ADMIN and USER).
There is no problem in conditional displaying lists in JSP Page, but I need to prepare model first and I don't want to load readers list if current user isn't an ADMIN.
I've thought about using Spring EL in Java code to determine if user has specified role (hasRole('ROLE_ADMIN')), but I can't find a way to manually evaluate that code.
Is there some way to call Spring EL handler in controller source code, or maybe there is a better solution (on architectural or design pattern level) for conditionally populating model, than checking roles directly in java code.
The issue was tackled by #Chepech and #Boris Kirzner in Prevent Method call without Exception using #PreAuthorize Annotation. That way you can return null (or empty list in your case) when a AccessDeniedException occurs.
One approach would be to just return everything when you're generating your model then rely on #PostFilter to filter that result based on roles. If you don't want to go that route, you could have your controller call out to another bean to build up specific parts of your model, and that other bean could have the role annotations attached. That would effectively limit you from ever creating objects if they won't be used.
If conditionally displaying of page parts is controlled by JSP page, then a conditional populating of model should be done by a controller (because JSP page and Controller are tightly coupled in view layer).
If we use annotations in service layer it will put the responsibility in wrong layer of an application (in service layer instead of view layer).
My final solution is based on remembering a Set of user Roles as user session attribute:
/* This is executed once, after user successful login. */
Set<String> roles = new HashSet<String>();
for (GrantedAuthority authority : authentication.getAuthorities()) {
roles.add(authority.getAuthority());
}
session.setAttribute("userRoles", roles);
Then if I want to conditionally populate a model I only need to check if required Role is contained by that Set.
Set<String> roles = (Set<String>) session.getAttribute("userRoles");
if(roles.contains("ROLE_ADMIN")) {
putReadersInModel(model);
}
I think that solution is clean and keeps the responsibility of proper populating of model in Controller.