Best way to implement authorization per group in spring security - spring

I'm trying to find the best approach to authorize certain actions for a user based on the role they have in a group.
For instance Peter has admin role in group A so he can kick members in group A and member role in group B so he can only read content from group B.
this is what model of user group relationship looks like.
Is there a better way to implement authorization than loading group role in UserDetails like this:
(for (GroupUser groupuser: user.getGroupUsers()) {
authorities.add(new SimpleGrantedAuthority('Group_'+groupuser.getId().getGroupId()+'_'+groupuser.getRole()));}

This is the ABAC permission model which the security decision is not only determined by the user 's information , but also determined from the state/attribute of the protected resource too.
In term of spring security , it is best implemented using #PreAuthorize / #PostAuthorize / #PostFilter which allow you to use a SpEL expression to declaratively define the security logic which is an expression that will finally evaluate to true/false. In SpEL , you can access the input parameter and the return object of the method which performs that use-case that you want to apply security or even refer to a spring bean to call its method to do the evaluation.
For example, you could do something like :
#PreAuthorize("#authzService.isAllowToKickMember(principal , #userToKick)")
public void kickMemeber(User userToKick){
}
#Service
public class AuthzService{
public boolean isAllowToKickMember(User currentUser , User userToKick){
//check if current user can kick a given user
}
}
For pointers , please check out this.

Related

Keycloak Spring Implementation combining Roles

I have a Problem that i cannot solve after researching alot.
I have a Keyloak with Clients(Application) and Roles that secure the Application.
Inside my Application i check with .hasRole()-Method if the Role of the User or other Application matches with the defined Role. Everything works excepted.
The Problem is i want to combine Roles and check them in the Application.
To access my Application the user should have the role 'read' AND 'write'.
In Spring the hasRole()-Method checks only one Role at a Time.
The hasAnyRole()-Method checks if one of the Roles matches.
Is there any Method like say hasAllRoles? Which checks if all the Roles match?
One request is to solve that Problem only with Configuration but the implemented Method in the Application is hasRole() so i except that there is no possible way of solving this with only configuration on Keycloak or Application.properties inside the Application
The easiest solution is probably to find a way to explain to the person who decided "to solve that Problem only with Configuration" that using #PreAuthorize meta-data is a better solution :
controllers code is not "polluted" with access-control: annotation are not inside methods body and are evaluated only if enabled in spring security (which makes no difference with access-control in conf)
access-control rules are close to access-point definition, which makes it much more readable (easy to grasp what is applied to a specific route and HTTP verb)
SpEL is much more powerful than config request matchers. You can even define your own DSL to write expressions like #PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')") (this taken from the most advanced of my tutorials).
access-control can decorate any method of any #Component (including #Service and #Repository) and not only to public methods of #Controller decorated with #RequestMapping or one of its declination.
With SpEL, you can do what you need (read and write) plus define rules based on the accessed resource itself (who created it, what it is linked to,...)
To give a try:
add #EnableMethodSecurity to your security conf class
only define with request matchers what is accessible to anonymous and what requires authentication
add #PreAuthorize("hasRole('read') and hasRole('write')") just above your controller method
In addition to what #ch4mp explained, I'd like to offer a few other principles to keep in mind.
First, your question. You can do hasAllRoles in two ways. The first is with AuthorizationManagers.allOf and the second is with SpEL:
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/endpoint").access(
allOf(hasAuthority("read"), hasAuthority("write"))
)
)
and
#PreAuthorize("hasAuthority('read') and hasAuthority('write')")
Read on for some additional recommendations relative to your comment:
The Problem is i want to combine Roles and check them in the Application.
A Bean
A nice way to extract authorization logic into a component is to reference an authorization bean in your expression.
For example, you can do:
#Component("authz")
public final class MyAuthorizationDecider {
public boolean check(MethodSecurityExpressionOperations operations) {
// ... place authorization logic here
}
}
And then you can do:
#PreAuthorize("#authz.check(#root)")
(If I'm not mistaken, you can still use #ch4mp's library with this approach, simply calling the library's DSL from a Java method instead of within a SpEL expression.)
Hierarchies
It's also the case that some permissions imply others. It may be the case for you that message:write implies message:read. In such a case, your expressions can be simplified by codifying this relationship in a RoleHierarchy instance.
At Login Time
At times, it can be helpful to map authorities at login time. For example, the role of USER might translate into message:read and ADMIN into message:read and message:write. It might go the other way as well. If the client granted message:read and message:write, perhaps this translates into a single permission of message:redact.
If you perform this translation at login time, it can allow for fewer computations at request time and a single location to reason about authorities being granted.
For example, instead of doing
#PreAuthorize("hasAuthority('message:read') and hasAuthority('message:write')")
or
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/message/redact").access(
allOf(hasAuthority("message:read"), hasAuthority("message:write"))
)
)
you'd do:
#PreAuthorize("hasAuthority('message:redact')")
or
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/message/redact").hasAuthority("message:redact")
)
Since you are using Keycloak, in this case, you'd consider a custom JwtGrantedAuthoritiesConverter if you are a Resource Server or GrantedAuthoritiesMapper if you are a Client to map the granted authorities from the Jwt to authorities that map to what you are doing in your app.

Springboot allow access to endpoint if userId matches

I am following up from this question:
How to configure Spring Boot Security so that a user is only allowed to update their own profile
Imagine I had an end-point /user/edit/{id}, I want this to be accessible if the user either tries to edit themslves (eg: a user with ID 1 accessing /user/edit/1 but not being able to access user/edit/2) or, if they are an admin, to be able to edit any user.
Is there any way I can achieve this in the security configuration?
.antMatchers("/user/edit/**").hasRole("ADMIN")
Would restrict this to admin users, I want either admin or the id matching the user's id.
The only thing I can think of is inside the controller having something like
#GetMapping("/edit/{id}")
public void edit(#PathVariable("id") int id, Principal principal) {
User u = (User) userDetailsService.loadUserByUsername(principal.getName());
if(u.getId() == id || u.getRoles().contains("ADMIN")) {
//accept uer
}
}
But I was under the impression we shouldn't encode access logic in our controller?
It is possible to use Spring Security's Method Security Expressions to do this. Example copied from the docs:
#PreAuthorize("#c.name == authentication.name")
public void doSomething(#P("c") Contact contact);
Read the sections preceding, as there is some configuration needed. Also note that if an expression is used repeatedly you can define your own security annotations.
I was under the impression we shouldn't encode access logic in our
controller?
"Should" is maybe too strong a word, IMHO. Security expressions are powerful, and in theory would allow you to keep all security checks separate from the controller logic. Easier to spot when a check is wrong, or missing. Easier to compare with the Swagger annotations too, if you are using those to document your endpoints.
But it can get trickier when you have to do something like filter rows returned so that the user only sees some of the results. Spring Security can do that using #PostFilter. But sometimes it isn't optimal. For example, if you know that certain rows aren't going to be returned you may be able to run a faster query, rather than filter out rows after the fact.
My first Spring Security project had queries like that, so ever since I have tended to use controller logic instead of security annotations. But that's not a good reason to never use annotations! So by all means use security expressions when you can, but if you have trouble with them or other considerations arise, integrating security with your controller logic isn't so bad IMHO.
To control role access in your controller you can use annotations like #Secured or #PreAuthorize.
To use the #Secured, put in you security config class:
#EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
And now you can use it in your controller:
#Secured("ROLE_ADMIN")
#PostMapping
public Account post(Account account, double amount){
// ...
}
To use the #PreAuthorize, put in you security config class:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}
And now you can use it in your controller:
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
#PostMapping
public Account post(Account account, double amount){
// ...
}
For more information you can check here the spring docs.

Relax Security for a Spring Data REST Projection

I have a User class and I want to authorize access such that only a user gets to see what he is entitled to.
This was easily achievable using Spring Security in conjunction with Spring Data Rest where in JPA Repository I did below -
public interface UserRepository extends JPARepository<User,Integer> {
#PreAuthorize("hasRole('LOGGED_IN') and principal.user.id == #id")
User findOne(#Param("id") Integer id);
}
In this way, a user when visits to Spring Data REST scaffolded URLs like -
/users/{id}
/users/{id}/userPosts
Only those logged in with {id} get to see these and everyone else gets 401 like I would have wanted.
My problem is that I have one of Projections which is a public view of each user and I am crating it using Spring Data Rest projections as below which I want to be accessible for every {id}
#Projection(name = "details", types = User.class)
public interface UserDetailsProjection {
..
}
So, /users/{id1}?projection=details as well as /users/{id2}?projection=details should give 200 OK and show data even though user is logged in by {id1}
I began implementing this by marking projection with #PreAuthorize("permitAll") but that won't work since Repository has harder security check. Can we have this functionality where for a projection we can relax security ?
I am using latest Spring Data Rest and Spring Security distributions
Seems reasonable to add a custom controller for this use-case.
Please also consider:
Evaluate access in projections using #Value annotations
Add another entity for the same database data but with different field set for read-only operations, e.g. using inheritance (be careful with caching, etc.) - depends on your data storage type
Modify model to split User entity into two different entities (profile, account) since they seem to have different access and possibly even operations
You can also add a ResourceProcessor<UserSummaryProjection> to evaluate access programmatically and replace resource content (projection) with a DTO
Example of evaluating access in projections with #Value annotations:
#Projection(types = User.class, name = "summary")
public interface UserSummaryProjection {
#Value("#{#userSecurity.canReadEmail(target) ? target.email: null}")
String getEmail();
}
Added spring security code in the data access layer is not a good idea. I would suggest you to add the #PreAuthorize annotation to the controller/service method. Since you have a query parameter, ?projection=details, you can have separate controller/service method for the details projection.
Add following to your details projection method:
#RequestMapping("/url", params = {"projection"})
#PreAuthorize("hasRole('LOGGED_IN') and principal.user.id == #id")

Setting user roles based on some kind of ownership in Spring Security

In my Spring-based application, I currently have basic roles such as ADMIN, and USER.
Is it possible to define a user role such as PHOTO_UPLOADER, which inherits from USER, but also adds a check whether the user making the call is actually the owner of the photo?
I am tired of writing the same if (currentUser.id == photo.uploader.id) in my controller actions over and over again. It applies to other entities as well.
You can handle it with ACLs like Tomasz Nurkiewicz suggested. But Spring Securitz ACLs are complex and poor documented. (The best resource I know for it is this Book: Spring Security 3 - by the authors of Spring Security)
But If you really need only this simple if (currentUser.id == photo.uploader.id) test, then I would recommend an other technique.
It is possible to enhance the method security expressions you can use them in #PreAuthorize annotations. Like:
#PreAuthorize("isPhotoOwner(#photo)")
public void doSomething(final Photo photo) {
To implement such an expression isPhotoOwner the core is really simple:
public class ExtendedMethodSecurityExpressionRoot extends MethodSecurityExpressionRoot {
public ExtendedMethodSecurityExpressionRoot(final Authentication a) {
super(a);
}
/**
*
*/
public boolean isPhotoOwner(final Photo photoObject) {
if (photoObject == null) {
return false;
}
Photo photo = (photo) photoObject;
return photo.getCreator().getLogin().equals(authentication.getName());
}
}
Unfortunaly there is some addtional work to to register the ExtendedMethodSecurityExpressionRoot. --- I have no time at the moment, if you are willing to try this approach, then leave a commment, and I will descripe the rest
i don't know what types of data accessing technology you are using. i know you can write interceptor or event listener to do the security checking for hibernate. i think ibatis is also the same way. in my project, i wrote CRUD enable interface methods in the parent model/entity class, and doing security check in some events, such as before entity loading. spring security acl is a bit complex. implementing your security solution is more better.
Welcome in the world of ACLs - access control list. This tutorial is rather old but pretty comprehensive.

Does Spring Security have a 'hasAllRole' that performs the AND version of 'hasAnyRole'

I'm using Spring Security in Grails to restrict access to my controllers. I have a use case where I want to check that a user has multiple roles assigned. I realize I could just make another role that is synonymous with 'person has these two roles', but it would require much more changes than I'd like.
Spring Security has an OR version expression to check if a user has any of a list of roles:
//allow user to access if he has role ROLE_ADMIN -OR- ROLE_USER
//note this is shortcut notation for hasAnyRole(["ROLE_ADMIN","ROLE_USER"])
#Secured(["ROLE_ADMIN","ROLE_USER"])
def index = {}
Is there a method, or just way using Spring Expression Language (SpEL) to do the following:
//allow user to access if he has role ROLE_ADMIN -AND- ROLE_USER
#Secured("hasAllRole(['ROLE_ADMIN','ROLE_USER']")
def index = {}
Note: SpringSecurityUtils class does have the method
public static boolean ifAllGranted(final String roles)
hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')
Couldn't be much simpler
If you use #PreFilter instead of #Secured then you can write:
#PreFilter("hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')")
could you not do hasRole('ROLE_ADMIN') and hasRole('ROLE_USER')?

Resources