简体   繁体   中英

How can I enable request only for user's own endpoint

I have a rest-endpoint like this: /users/{userId}/something

I implemented authentification using oauth2. My WebSecurityConfig looks like this:

protected void configure(HttpSecurity http) throws Exception {
    http
    .authorizeRequests()
    .anyRequest().authenticated()
    .and()
    .formLogin()
    .loginPage("/login").permitAll();
}

How can I only allow users to access their own endpoint (eg. User with Id 100 can only access /users/100/something ) without beeing able to see another endpoint (like /users/200/something )?

Is this possible?

There are many ways to solve this problem, but i have picked out three solutions to apraoch this problem.

Custom Security Expression

I would recommend a custom security based annotation approach. This would involve implementing a custom security expression, the related expression handler and the method security configuration. The next appraoch is a little bit simpler if this is too much work for you.

public class UserIdentityMethodSecurityExpressionRoot 
    extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {

    public UserIdentityMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    public boolean userIdentity(Long userId) {
        User user = ((UserPrincipal) this.getPrincipal()).getUser();
        return user.getId() == userId;
    }
}

Rest endpoints or service methods can then be annotated with the newly created security expression:

@PreAuthorize("userIdentity(#userId)")
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
    return resourceService.findOne(id);
}

Please note that the userId must be provided somewhere , either as @PathVariable or @RequestParam . Spring security will then check if the current user has matches the provided userId and returns 403 otherwise.

The full example is available here and has been adapted in this question for your purposes.

SpEL

You can also use SpEL, which is a little bit simpler:

@PreAuthorize("#userId == principal.getId()")
@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
    return resourceService.findOne(id);
}

Other considerations

You can also do all the work yourself and get to a faster result without defining a custom expression using SecurityContextHolder .

public static void checkUserIdentity(Long userId) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    // user did not provide a token
    if(auth == null) {
        throw new AccessDeniedException(); 
    }      
    UserDetails details = (UserDetails) auth.getPrincipal();
    if(userId != details.getId()) {
        throw new AccessDeniedException(); 
    }
} 

And using it like:

@GetMapping
@ResponseBody
public Resource fineOne(@PathVariable Long userId) {
    SecurityUtils.checkUserIdentity(userId)
    return resourceService.findOne(id);
}

Why does this work? The SecurityContextHolder will inject the current principal if you have setup Spring security correctly. By default, an authentication is bound to the current thread of execution and will be reset if the request has been processed or encounters an exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM