An article takes you through the authorization operation in Spring Security

1. Authorization

The so-called authorization means that if a user wants to access a certain resource, we have to check whether the user has such a permission, if it does, then it is allowed to access, if not, it is not allowed.

Two, prepare to test users

Because we have not connected to the database yet, the test user is still configured based on memory.

Based on the memory configuration test users, we have two methods, the first is the configuration method we used in the previous articles in this series, as follows:

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        //在内存中进行配置
        auth.inMemoryAuthentication()
                .withUser("yolo")
                .password("123").roles("admin")
                .and()
                .withUser("nlcs")
                .password("123")
                .roles("user");
    }

This is a configuration method.

Since Spring Security supports a variety of data sources, such as memory, databases, LDAP, etc., these different sources of data are encapsulated into a common UserDetailServiceinterface, any object that implements this interface can be used as authentication data source.

Thus we can rewrite WebSecurityConfigurerAdapterof userDetailsServiceproviding a method for UserDetailServiceexample further arranged a plurality of users:

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("yolo").password("123").roles("admin").build());
        manager.createUser(User.withUsername("nlcs").password("123").roles("user").build());
        return manager;
    }

Three, prepare to test the interface

The test user is ready, and then we prepare three test interfaces. as follows:

@RestController
public class HelloController {
    
    
    @GetMapping("/hello")
    public String hello() {
    
    
        return "hello";
    }

    @GetMapping("/admin/hello")
    public String admin() {
    
    
        return "admin";
    }

    @GetMapping("/user/hello")
    public String user() {
    
    
        return "user";
    }
}

For these three test interfaces, our plan is like this:

(1) It /hellois an interface that can be accessed by anyone
(2) It /admin/hellois an interface that can only be accessed by a person with an admin identity
(3) /user/helloAn interface that can be accessed by a person with a user identity
(4) All users can access resources, admin can access

Note that the fourth specification means that all people with admin identity automatically have user identity

Four, configuration

Next we configure the permissions of blocking rules, in Spring Security's configure(HttpSecurity http)method, the code is as follows:

http.authorizeRequests()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .anyRequest().authenticated()
        .and()
        ...
        ...

For the matching rules here, we use Ant-style path matching symbols. Ant-style path matching symbols are widely used in the Spring family, and its matching rules are also very simple:

Insert picture description here
The meaning of the above configuration is:

(1) If the request satisfies a path /admin/**format, the user needs to have admin role.
(2) satisfied if the request is a path /user/**format, the user needs to have the user role.
(3) The remaining request paths in other formats can be accessed only after authentication (login).

Note that the order of the three rules configured in the code is very important. Similar to Shiro, Spring Security also matches from top to bottom when matching. Once matched, it will not continue to match.So the order of interception rules cannot be written wrong

On the other hand, if you force anyRequest to be placed before antMatchers, like this:

http.authorizeRequests()
        .anyRequest().authenticated()
        .antMatchers("/admin/**").hasRole("admin")
        .antMatchers("/user/**").hasRole("user")
        .and()

At this point, when the project is started, an error will be reported, and it will be prompted that antMatchers cannot be added after anyRequest:

Insert picture description here
This is semantically well understood, anyRequestother requests are already included, and it doesn't make any sense if other requests are configured after it.

Semantically understood, anyRequestit should be placed at the end, indicating how to handle the remaining requests in addition to the previous interception rules.

In the configuration class blocking rules AbstractRequestMatcherRegistry, we can see some code as follows (Source section):

public abstract class AbstractRequestMatcherRegistry<C> {
    
    
 private boolean anyRequestConfigured = false;
 public C anyRequest() {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure anyRequest after itself");
  this.anyRequestConfigured = true;
  return configurer;
 }
 public C antMatchers(HttpMethod method, String... antPatterns) {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
  return chainRequestMatchers(RequestMatchers.antMatchers(method, antPatterns));
 }
 public C antMatchers(String... antPatterns) {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure antMatchers after anyRequest");
  return chainRequestMatchers(RequestMatchers.antMatchers(antPatterns));
 }
 protected final List<MvcRequestMatcher> createMvcMatchers(HttpMethod method,
   String... mvcPatterns) {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure mvcMatchers after anyRequest");
  return matchers;
 }
 public C regexMatchers(HttpMethod method, String... regexPatterns) {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
  return chainRequestMatchers(RequestMatchers.regexMatchers(method, regexPatterns));
 }
 public C regexMatchers(String... regexPatterns) {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure regexMatchers after anyRequest");
  return chainRequestMatchers(RequestMatchers.regexMatchers(regexPatterns));
 }
 public C requestMatchers(RequestMatcher... requestMatchers) {
    
    
  Assert.state(!this.anyRequestConfigured, "Can't configure requestMatchers after anyRequest");
  return chainRequestMatchers(Arrays.asList(requestMatchers));
 }
}

From this source code, we can see that before any interception rules (including anyRequest itself), it will first determine whether anyRequest has been configured. If it is configured, an exception will be thrown and the system will fail to start.

So everyone understands why anyRequest must be placed last

Five, start the test

Next, we start the project for testing.

After the project is successfully launched, we first log in as yolo:

Insert picture description here
After logging in successfully, visit the three interfaces /hello, /admin/hello and /user/hello respectively, among which:

(1) /helloBecause you can access after logging in, this interface is successfully accessed.
(2) The /admin/helloadmin identity is required, so the access fails.
(3) The /user/hellouser identity is required, so the access is successful.

Log in to nlcs in the same way.

Six, role inheritance

As mentioned earlier, all the resources that the user can access can be accessed by the admin. Obviously, our current code does not yet have such a function.

To implement all the resources that the user can access, the admin can access it. This involves another knowledge point, calledRole inheritance

This is very useful in actual development.

The upper level may have all the permissions of the lower level. If role inheritance is used, this function is very easy to implement. We only need to add the following code to SecurityConfig to configure the role inheritance relationship:

@Bean
RoleHierarchy roleHierarchy() {
    
    
    RoleHierarchyImpl hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_admin > ROLE_user");
    return hierarchy;
}

Note that in the configuration, you need to manually add character ROLE_prefix. The above configuration means ROLE_adminautomatically have ROLE_userpermission.

After configuration is complete, restart the project, this time we found yolo can also access /user/hellothis interface up

Insert picture description here

Guess you like

Origin blog.csdn.net/nanhuaibeian/article/details/108596231