Article Directory
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 UserDetailService
interface, any object that implements this interface can be used as authentication data source.
Thus we can rewrite WebSecurityConfigurerAdapter
of userDetailsService
providing a method for UserDetailService
example 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
/hello
is an interface that can be accessed by anyone
(2) It/admin/hello
is an interface that can only be accessed by a person with an admin identity
(3)/user/hello
An 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:
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:
This is semantically well understood, anyRequest
other requests are already included, and it doesn't make any sense if other requests are configured after it.
Semantically understood, anyRequest
it 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:
After logging in successfully, visit the three interfaces /hello, /admin/hello and /user/hello respectively, among which:
(1)
/hello
Because you can access after logging in, this interface is successfully accessed.
(2) The/admin/hello
admin identity is required, so the access fails.
(3) The/user/hello
user 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_admin
automatically have ROLE_user
permission.
After configuration is complete, restart the project, this time we found yolo can also access /user/hello
this interface up