-1

We have a SpringBoot based module, we have REST APIs which allow creating resources with Params like

Request

POST /resources

{
  "resourceName": "Res1",
  "admins": ["john.doe@company.com", "jane.doe@company.com"]
}

Response

{
  "id": "R1"
  "resourceName": "Res1",
  "admins": ["john.doe@company.com", "jane.doe@company.com"]
}

Request

POST /resources

{
  "resourceName": "Res2",
  "admins": ["alice@company.com", "bob@company.com"]
}

Response

{
  "id": "R2"
  "resourceName": "Res2",
  "admins": ["alice@company.com", "bob@company.com"]
}

For R1 update API should only be accesible by John/Jane

Request

PUT /resources/R1

{
  "resourceName": "Resource1",
  "admins": ["john.doe@company.com", "jane.doe@company.com", "jacob@company.com"]
}

Response

For John / Jane the response should be:

{
  "id": "R1"
  "resourceName": "Resource1",
  "admins": ["john.doe@company.com", "jane.doe@company.com", "jacob@company.com"]
}

When Alice / Bob user are updating R1 this response should be 403 Forbidden

Similarly For R2 update API should only be accesible by Alice / Bob.

When John / Jane are updating R2 this response should be 403 Forbidden

Please suggest which framework can be used to achieve this, preferably with less boiler plate

Currently we have a system where resource access is in for of RoleBasedAccessControl. We achieve restriction by storing permissions. The RBAC config is saved in DB.

But now we need more fine grained control per resource which can be managed directly by existing admins

Shubham
  • 58
  • 6
  • Assigning static roles that are applicable on all resources will not be helpful. I can have John and Jane as admins for Resource1, and Alice and Bob as admins for Resource2. And there can be N Number of these resources – Shubham Feb 06 '23 at 19:14
  • Currently we using a request filter to intercept call there we check the user, also in a database we are storing user -> urls mapping. Like john -> /resources/R1. I am looking for a better and straight forward way to achieve this. – Shubham Feb 06 '23 at 20:27

2 Answers2

0

You could use Spring Security for this use case. As a general practice it is recommended to assign roles for users for maintability. For example assign admin role to - john.doe@company.com, jane.doe@company.com then manage the access using the role.

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    // Roles for users
    private static final String ROLE_1 = "ADMIN";
    private static final String ROLE_2 = "USER";
    
    // In-memory users with roles
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {      
        auth.inMemoryAuthentication()
                .withUser("john.doe@company.com")
                .password(passwordEncoder().encode("admin@123"))
                .roles(ROLE_1)
                .and()
                .withUser("jane.doe@company.com")
                .password(passwordEncoder().encode("admin@12345"))
                .roles(ROLE_1)
                .and()
                .withUser("user")
                .password(passwordEncoder().encode("user@123"))
                .roles(ROLE_2);
    }
    
    // Password encoding
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // Authorized the request based on role
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/resources/**").hasRole(ROLE_1)
                .antMatchers("/all").permitAll()
                .and().formLogin();
    }
}
krishnan
  • 132
  • 5
  • Assigning static roles that are applicable on all resources will not be helpful. I can have John and Jane as admins for Resource1, and Alice and Bob as admins for Resource2. And there can be N Number of these resources – Shubham Feb 06 '23 at 19:13
  • Agreed. The above example was provided for refernece, you could manage the user role with a database. – krishnan Feb 07 '23 at 09:44
  • But for the `antMatchers` URLs the list is hard coded. Based on the example in description http.authorizeRequests() .antMatchers("/resources/r1").hasRole("r1_admin") .antMatchers("/resources/r2").hasRole("r2_admin") .and().formLogin(); But as new resources are being created new antMatcher paths need to be added. So is this method called once to declare antMatcher on startup or would this be called again and again? I'll check this and get back, let me know if you already know – Shubham Feb 07 '23 at 09:53
  • I check and this approach doesn't work for Dynamic Rest Resources as the configure method is called only during startup any resources created after startup won't be protected – Shubham Feb 27 '23 at 07:14
  • Here you have to go for the access control via database configuartion - since you are using apache shiro. – krishnan Feb 28 '23 at 16:02
0

This can be achieved using a method level security from SpringBoot.

First enable Method Level Security by annotating a Config class like -

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodConfig extends GlobalMethodSecurityConfiguration

Then provide an implementation of PermissionEvaluator class like -

@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, Object permission) {
  if (permission.equals("RESOURCE_WRITE_AUTHORITY")) {
    Resource res = resourceRepository.getResourceById((String) targetId);
    if(env != null) {
      return env.getAdmins().contains(authentication.getName());
    }
  }
  return false;
}

After which you can annotate your PUT method in controller or the corresponding method in the service, to use PermissionEvaluator, like

@PreAuthorize("hasPermission(#resourceName, 'RESOURCE_WRITE_AUTHORITY')")
public Environment updateResource(String resourceName)

References - https://docs.spring.io/spring-security/site/docs/3.0.x/reference/el-access.html#:~:text=15.3.1%C2%A0%40Pre%20and%20%40Post%20Annotations