preface

Spring Security supports method-level permission control. In this mechanism, we can add permission annotations to any method at any level. The annotated methods are automatically protected by Spring Security and only allowed to be accessed by certain users, thus providing permission control purposes. Of course, if the existing permission annotations do not meet the requirements, we can also customize them

Quick start

  1. Add the security dependency first as follows
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Copy the code

2. Create a security configuration class

Spring Security is disabled by default annotations, want to open the annotations, in succession WebSecurityConfigurerAdapter class add @ EnableMethodSecurity annotations, And define the AuthenticationManager as a Bean in this class.

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean(a) throws Exception {
        return super.authenticationManagerBean(); }}Copy the code

We saw a prePostEnabled @ EnableGlobalMethodSecurity respectively, securedEnabled, jsr250Enabled three fields, including an annotation support each field code, the default is false, true to open it. So let’s take a look at each of the three general annotation supports.

PrePostEnabled = true enables the @preauthorize and @postauthorize annotations of Spring Security.

SecuredEnabled = true enables the @secured annotation of Spring Security.

Jsr250Enabled = true enables the @roleallowed annotation

SpringBoot+SpringSecurity+JWT real RESTfulAPI permission control practice

Obtaining user rights of the Spring Security core interface and the implementation principle of authentication process

Set permission authentication on the method

JSR – 250 annotations

Compliant with jSR-250 standard annotations main annotations

  1. @DenyAll
  2. @RolesAllowed
  3. @PermitAll

In this case, @denyall and @Permitall believe that there is no need to say more to stand for reject and pass.

@rolesallowed Example

@RolesAllowed("ROLE_VIEWER")
public String getUsername2(a) {
    / /...
}
     
@RolesAllowed({ "USER", "ADMIN" })
public boolean isValidUsername2(String username) {
    / /...
}
Copy the code

Methods that represent annotations can be accessed by either USER or ADMIN. You can omit the prefix ROLE_, and the actual permission might be ROLE_ADMIN

Identical in functionality and usage to @secured

SecuredEnabled annotations

Main annotations

@Secured

  1. The @secured annotation for Spring Security. Annotations specify a list of roles for access methods, specifying at least one role in the list

  2. The @secured specifies security on methods that require roles/permissions, etc., that only users of the corresponding role/permissions can invoke these methods. If someone tries to call a method but does not have the required roles/permissions, access will be denied and an exception will be thrown.

Such as:

@Secured("ROLE_VIEWER")
public String getUsername(a) {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

@Secured({ "ROLE_DBA", "ROLE_ADMIN" })
public String getUsername2(a) {
    / /...
}
Copy the code

@secured (“ROLE_VIEWER”) indicates that only users with the ROLE_VIEWER role can access the getUsername() method.

@secured ({“ROLE_DBA”, “ROLE_ADMIN”}) indicates that the user has access to the getUsername2 method for either “ROLE_DBA” or “ROLE_ADMIN” roles.

Also, @secured does not support Spring EL expressions

PrePostEnabled annotations

The support for Spring EL expressions when enabled is pretty impressive. If you do not have access to the method, an AccessDeniedException is thrown.

Main annotations

  1. @preauthorize — Appropriate to validate authorization before entering a method

  2. PostAuthorize — Checks authorization methods before they are executed and can influence the return value of the execution method

3. @postfilter — Executes after the method executes, and here the return value of the method can be called and then filtered or processed or modified and returned

  1. @prefilter — executed before the method is executed, and here the method’s parameters can be called and then filtered or processed or modified for parameter values

@preauthorize Annotations are used

@PreAuthorize("hasRole('ROLE_VIEWER')")
public String getUsernameInUpperCase(a) {
    return getUsername().toUpperCase();
}
Copy the code

@preauthorize (“hasRole(‘ROLE_VIEWER’)”) is equivalent to @secured (” ROLE_VIEWER “).

Similarly, @secured ({” ROLE_VIEWER “, “ROLE_EDITOR”}) can be replaced with @preauthorize (” hasRole(‘ ROLE_VIEWER’) or hasRole(‘ ROLE_EDITOR’) “).

In addition, we can use expressions on method arguments:

Before the method is executed, the method’s parameters can be called and the parameter values can be obtained using JAVA8’s parameter name reflection feature. If JAVA8 is not available, you can also annotate parameters using @P of Spring Secuirty or @param of Spring Data.

/ / no java8
@ PreAuthorize (" # userId = = authentication. Principal. UserId or hasAuthority (" ADMIN ") ")
void changePassword(@P("userId") long userId ){}
/ / a java8
@ PreAuthorize (" # userId = = authentication. Principal. UserId or hasAuthority (" ADMIN ") ")
void changePassword(long userId ){}

Copy the code

Before the changePassword method is executed, check whether the value of userId is equal to the userId of the current user stored in principal, or whether the current user has ROLE_ADMIN permission.

@postauthorize Annotations are used

Execute after the method is executed to get the return value of the method, and use this method to determine the final authorization result (whether access is allowed or not):

@PostAuthorize
  ("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
    return userRoleRepository.loadUserByUserName(username);
}
Copy the code

In the above code, access is allowed only if the username in the return value of the loadUserDetail method is the same as the username of the currently logged in user

Note that if EL is false, the method has also finished executing and may be rolled back. The EL variable returnObject represents the object that is returned.

Use @prefilter and @postfilter annotations

Spring Security provides an @prefilter annotation to filter incoming parameters:

@PreFilter("filterObject ! = authentication.principal.username")
public String joinUsernames(List<String> usernames) {
    return usernames.stream().collect(Collectors.joining(";"));
}
Copy the code

If the subentries in userNames are different from the name of the current logon user, they are reserved. If the subentries in userNames are the same as the username of the current logged-in user, they are removed. For example, if the current username is zhangsan and the value of usernames is {“zhangsan”, “lisi”, “wangwu”}, the actual value of usernames passed by @prefilter is {“lisi”, “wangwu”}.

If the execution method contains more than one Collection parameter, filterObject does not know which Collection parameter is being filtered. Add the filterTarget attribute to specify the parameter name:

@PreFilter
  (value = "filterObject ! = authentication.principal.username",
  filterTarget = "usernames")
public String joinUsernamesAndRoles( List
       
         usernames, List
        
          roles)
        
        {
  
    return usernames.stream().collect(Collectors.joining(";")) 
      + ":" + roles.stream().collect(Collectors.joining(";"));
}
Copy the code

We can also use the @postfilter annotation to filter the returned Collection:

@PostFilter("filterObject ! = authentication.principal.username")
public List<String> getAllUsernamesExceptCurrent(a) {
    return userRoleRepository.getAllUsernames();
}

Copy the code

FilterObject represents the return value. If you follow the above code, this is done: remove subentries from the return value that are the same as the user name of the currently logged in user.

Custom meta annotations

If we need to use the same security annotations in multiple methods, we can improve the maintainability of the project by creating meta-annotations.

For example, create the following meta-annotation:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('ROLE_VIEWER')")
public @interface IsViewer {
}
Copy the code

You can then add the annotation directly to the corresponding method:

@IsViewer
public String getUsername4(a) {
    / /...
}
Copy the code

Meta-annotations are a good choice for production projects because they separate business logic from security frameworks.

Use security annotations on classes

If all methods in a class apply the same security annotation, then the security annotation should be raised to the class level:

@Service
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class SystemService {
  
    public String getSystemYear(a){
        / /...
    }
  
    public String getSystemDate(a){
        / /...}}Copy the code

The above code implements that access to getSystemYear and getSystemDate methods requires ROLE_ADMIN.

Apply multiple security annotations to the method

When one security annotation does not meet our requirements, we can also apply multiple security annotations:

@PreAuthorize("#username == authentication.principal.username")
@PostAuthorize("returnObject.username == authentication.principal.nickName")
public CustomUser securedLoadUserDetail(String username) {
    return userRoleRepository.loadUserByUserName(username);
}
Copy the code

At this point, Spring Security will execute the @preauthorize Security policy before executing the method and the @postauthorize Security policy after executing the method.

conclusion

In this combination with our experience, the following two tips are given:

  1. By default, the use of security annotations in methods is implemented by Spring AOP proxies, which means that if we call method 2 of the class using security annotations in method 1, the security annotations on method 2 will be invalidated.

  2. The Spring Security context is thread-bound, which means that the Security context will not be passed to child threads.

public boolean isValidUsername4(String username) {
    // The following method will skip security authentication
    this.getUsername();
    return true;
}
Copy the code