Spring Security provides annotation-based access control.

turns on method annotation access control

Spring Security turns off method annotations by default, to turn it on just add the @EnableGlobalMethodSecurity annotation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * 开启方法安全注解
 *
 * @author  felord.cn
 */
@EnableGlobalMethodSecurity(prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true)
public class MethodSecurityConfig {
}

@EnableGlobalMethodSecurity provides prePostEnabled, securedEnabled and jsr250Enabled. You can choose to use one or more of these three methods as needed.

@EnableGlobalMethodSecurity

The @EnableGlobalMethodSecurity mind map can help you sort out the logic.

EnableGlobalMethodSecurity mind map

The yellow area is the basic knowledge of annotated access control and needs to be focused on, so let’s take a look at the basic usage.

@PreAuthorize and @PostAuthorize

Enabled when prePostEnabled is equal to true.

The SpEL expression is used to calculate whether the method can be invoked or whether the result can be returned after the invocation, either before or after the marked method is invoked. Some examples of common expressions are summarized.

SpEL expressions Descriptions
principal.username ne ‘felord’ The current principal’s username cannot be felord
authentication.authorities.contains(‘ROLE_ADMIN’) The authorities of the current Authentication contain ROLE_ADMIN
hasRole(‘ADMIN’) The current user must have the role ROLE_ADMIN, equivalent to the above
hasAnyRole(‘ADMIN’,‘USER’) Current user role must have ROLE_ADMIN or ROLE_USER
hasAuthority(‘ROLE_ADMIN’) Same as hasRole
hasAnyAuthority(‘ROLE_ADMIN’,‘ROLE_USER’) Same as hasAnyRole
#requestParam eq ‘felord’ The current request parameter requestParam (which can be an object, here is a string example) has a value equal to felord.

Other expressions can be found in the official SpEL documentation.

If the user felord accesses the following interface, the method will not only not execute but will also respond with a 403 exception status code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/**
    *  当前用户名不是felord 才能访问 否则403
    * @param req req
    * @return map
    */
@GetMapping("/prepost")
@PreAuthorize("authentication.principal.username ne 'felord'")
public  Map<String, String> prepost(String req){
    Map<String, String> map = Collections.singletonMap("req", req);
    System.out.println("map = " + map);
    return map;
}

If the annotation on the method is changed to @PostAuthorize("authentication.principal.username ne 'felord'") , the console will print the following.

PostAuthorize Access is denied

It is clear from the logs that the method did execute, but it still responded with a 403 status code.

@PreFilter and @PostFilter

are enabled when prePostEnabled is equal to true.

These two annotations can be seen as enhancements to @PreAuthorize and @PostAuthorize. They implement @PreAuthorize and @PostAuthorize in addition to the ability to filter request response data. The data types restricted to processing are Collection, Array, Map, Stream. As an example.

1
2
3
4
5
6
7
8
9
// ids =  ["Felordcn","felord","jetty"]
@PostMapping("/prefilter")
@PreFilter(value = "hasRole('ADMIN') and filterObject.startsWith('f')",
            filterTarget = "ids")
public Collection<String> preFilter(@RequestBody Collection<String> ids){
        // ids = ["felord"]
    System.out.println("ids = " + ids);
    return ids;
}

The above interface method has two levels of meaning.

  • The current user must hold the role ROLE_ADMIN, otherwise the method is not executed. The method must be executed if this condition does not exist.

  • If the method executes, any element in the set of input ids that does not start with f is removed and the return value is felord.

The underlying filter element is java.util.Collection#remove(Object); in addition multiple input parameters need to use filterTarget to specify the parameter name.

@PostFilter is also well understood, take the following method as an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@GetMapping("/postfilter")
@PostFilter("hasRole('ADMIN') and filterObject.startsWith('f')")
public Collection<String> postfilter(){
    List<String> list = new ArrayList<>();
    list.add("Felordcn");
    list.add("felord");
    list.add("jetty");
    // list = ["felord"]
    System.out.println("list = " + list);
    return list;
}

The method is executed regardless of whether the expression in the @PostFilter holds or not. If the condition holds, the list data is responded to. If the condition is not valid, the response is a 403 status code.

These two annotations are used to control the request, the collection in the response, and the data in the stream.

@Secured

Enabled when securedEnabled is equal to true.

This annotation is much simpler and by default can only make access control decisions based on a collection of roles (which by default requires the prefix ROLE_). The mechanism of this annotation is that it can be accessed as long as the set of roles it declares (value) contains any role held by the current user. That is, there must be a non-empty intersection of the user’s role set and the role set of the @Secured annotation. Decision making using SpEL expressions is not supported.

This is not demonstrated because it is too simple.

@Secured is equivalent to hasAnyAuthority.

JSR-250

Enabled when jsr250Enabled is equal to true.

Enables the JSR-250 security control annotation, which is part of the JavaEE security specification (now a jakarta project). The following three of the JavaEE security annotations are used in Spring Security.

  • @DenyAll deny all access.

  • @PermitAll agrees to all accesses.

  • @RolesAllowed is used in the same way as @Secured above.

Advantages and disadvantages of annotation control

The advantage of using annotations is that the interface methods are bound and the control granularity is very fine, and you can even do some data level access control. The disadvantage is that it is statically woven into the Java code and flexibility is difficult to grasp.

Reference https://mp.weixin.qq.com/s/4nZwhAQHKApzZ9ygsTlYrg