27

I have some rest api like this:

/users/{user_id}
/users/{user_id}/orders
/users/{user_id}/orders/{order_id}

How I must secure them? every user must see only her/his data, But admin can see all of them.

How & What I must implement in Spring Security that User by Id == 1 can't see data of user by Id == 2 and vice versa, expect users by role admin that can see all?

Do I check before every method User Id in session is equail with user_id param passed to api? is there a better way?

p.s: I use JWT by spring security.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
GLinBoy
  • 606
  • 1
  • 10
  • 20
  • 1
    I highly recommend Chapters 16 and 17 of _Spring Security in Action_ by Laurențiu Spilcă (2020). He also has a YouTube playlist [here](https://youtube.com/playlist?list=PLEocw3gLFc8XRaRBZkhBEZ_R3tmvfkWZz). – Leponzo Feb 24 '22 at 07:09

4 Answers4

38

In any @Controller, @RestController annotated bean you can use Principal directly as a method argument.

    @RequestMapping("/users/{user_id}")
    public String getUserInfo(@PathVariable("user_id") Long userId, Principal principal){
        // test if userId is current principal or principal is an ADMIN
        ....
    }

If you don't want the security checks in your Controllers you could use Spring EL expressions. You probably already use some build-in expressions like hasRole([role]).

And you can write your own expressions.

  1. Create a bean
    @Component("userSecurity")
    public class UserSecurity {
         public boolean hasUserId(Authentication authentication, Long userId) {
            // do your check(s) here
        }
    }
  1. Use your expression
    http
     .authorizeRequests()
     .antMatchers("/user/{userId}/**")
          .access("@userSecurity.hasUserId(authentication,#userId)")
        ...

The nice thing is that you can also combine expressions like:

    hasRole('admin') or @userSecurity.hasUserId(authentication,#userId)
George
  • 6,886
  • 3
  • 44
  • 56
Dirk Deyne
  • 6,048
  • 1
  • 15
  • 32
  • Can I do it with AOP? – GLinBoy Aug 07 '18 at 03:45
  • In case you are struggling with the custom expression, add "**@Component("userSecurity")**" above "**public class UserSecurity{**" line – Krishna Chikkala Apr 13 '20 at 18:34
  • But "In any @Controller, @RestController annotated bean you can use Principal directly as a method argument." is not working. Principal is just `null` – ACV Sep 04 '20 at 12:33
  • 1
    @ACV yes.... the Principal should be accessible ref: [principal-in-restcontroller](https://github.com/dirkdeyne/principal-in-restcontroller) – Dirk Deyne Sep 04 '20 at 13:22
  • Thank you @DirkDeyne you created the example specially for demonstration. Nice. So how do you request that endpoint so that the Principal is not null? Is it a request header or something else? How does Spring populate that object? – ACV Sep 04 '20 at 15:18
  • 2
    @ACV here is a good overview [spring.io guide: spring-security-architecture](https://spring.io/guides/topicals/spring-security-architecture), you should look at the `Web Security` part – Dirk Deyne Sep 04 '20 at 16:02
  • What is the `authentication` object? What it stores? – user123145152323 Jan 29 '23 at 21:40
  • @user123145152323 The Authentication object in the SecurityContext: `SecurityContextHolder.getContext().getAuthentication();` – Dirk Deyne Jan 30 '23 at 12:38
6

You can also use @PreAuthorize on the service interface. If you have a custom userdetails object then you can do it easily. In one of my projects I did it like this:

@PreAuthorize(value = "hasAuthority('ADMIN')"
        + "or authentication.principal.equals(#post.member) ")
void deletePost(Post post);

BTW this is in a service interface. You have to make sure to add the right annotations to get preauthorize to work.

2

You should first choose your security strategy, What you need names "Row Filtering", one of Authorization Concepts of 3A( Authentication, Authorization,Audit ) Concepts.

If you want to implement comprehensive solution, take a look at :

https://docs.spring.io/spring-security/site/docs/3.0.x/reference/domain-acls.html

Spring ACL completely covers concepts like "Row Filtering", "White-Black List", "Role Base Authorization", "ACL Inheritance", "Role Voter", ....

Otherwise you should save the owner per business case you want to be secured and filter them in your Service Layer.

Pasha
  • 1,534
  • 15
  • 27
0

We can create an annotation like @IsUser and use it with the controller method.

Example :

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize(value = "hasRole('ROLE_USER')" + "and authentication.principal.equals(#userId) ")
public @interface IsUser { }
shanwije
  • 600
  • 7
  • 17