Spring Security Authentication and Authorization (1)

        In the previous article, we followed the default security mechanism of Spring Security ; there is only one user and only one role. In actual development, this naturally cannot meet the demand. This article will configure Spring Security in more depth , and initially use the authorization mechanism.

1. Authentication and authorization of the default database model:

1. Resource preparation:

        First, create a new controller package, create three controllers, and create some test routes in the controllers respectively. The codes of the three classes are as follows:

@RestController
@RequestMapping("/admin/api")
public class AdminController {

	@GetMapping("/hello")
	public String hello() {
		return "hello admin";
	}
}
@RestController
@RequestMapping("/app/api")
public class AppController {

	@GetMapping("/hello")
	public String hello() {
		return "hello app";
	}
}
@RestController
@RequestMapping("/user/api")
public class UserController {

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

        Assume that the content under /admin/api is the API related to the background management of the system , the content under /app/api is the API for public access to the client , and the content under /user/api/ is related to the user's own data operation API ; obviously, /admin/api must have administrator privileges to operate, and /user/api must be operated after the user logs in.

2. Resource authorization configuration:

        In order to access the previous route normally, we need to modify the configuration class  WebSecurityConfig , the code is as follows:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests().
		antMatchers("/admin/api/**").hasRole("ADMIN").
		antMatchers("/user/api/**").hasRole("USER").
		antMatchers("/app/api/**").permitAll().
		anyRequest().authenticated().
		and().formLogin();
	}
}

        antMatchers() is a URL matcher that takes ANT patterns . ANT mode to use? Match any single character, use * to match 0 or any number of characters, and use ** to match 0 or more directories. antMatchers("/admin/api/**") is equivalent to matching all APIs under /admin/api/ . Here we specify that it can only be accessed when it must be in the ADMIN role, and /user/api/ is the same. The API under /app/api/ will call permitAll() to disclose its permissions.

        Authorization related configuration doesn't look complicated, but something seems to be missing? Ignore it for now.

        Restart the service, try to access localhost:8080/app/api/hello , and the page prints "hello app" , which verifies that the service under /app/api/ is indeed open to the public. Then visit localhost:8080/user/api/hello , this time you need to log in. We try to enter the user name and password defined in application.properties earlier, after logging in, the page prints "hello user" . However, we don't have a user user, why can we successfully access the route? In order to verify that there is no problem with the authorization link, we try to access localhost:8080/admin/api/hello , and the content that appears is as follows:

A 403 error          is displayed on the page , indicating that the user authorization failed ( 401 means that the user authentication failed). In other words, this visit has already passed the authentication process, but was rejected during authorization. There is no problem in the authentication link, because the default user role of Spring Security is user .

        HTTP Status Code ( HTTP Status Code ) is a specification defined by RFC 2616 to indicate the status of an HTTP request response, consisting of 3 digits. Usually, 2XX is used to indicate that the operation is successful, 4XX is used to indicate that the failure is caused by the client, and 5XX is used to indicate that the error is caused by the server.

3. Memory-based multi-user support:

        So far, we still have only one user who can log in, how to introduce multiple users? It's very simple, we only need to implement a custom UserDetailsService , the code is as follows:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests().
		antMatchers("/admin/api/**").hasRole("ADMIN").
		antMatchers("/user/api/**").hasRole("USER").
		antMatchers("/app/api/**").permitAll().
		anyRequest().authenticated().
		and().formLogin();
	}
	@Bean
	public UserDetailsService userDetailsService() {
		InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
		manager.createUser(User.withUsername("user").password("123").roles("USER").build());
		manager.createUser(User.withUsername("admin").password("123").roles("USER","ADMIN").build());
		return manager;
	}
}

        Add a @bean annotation to it, and it can be discovered and used by Spring Security . Spring Security supports user data from various sources, including memory, databases, LDAP , etc. They are abstracted as a UserDetailsService interface, and any object that implements the UserDetailsService interface can be used as an authentication data source. In this design mode, Spring Security is particularly flexible.

        InMemoryUserDetailsManager is an implementation class in the UserDetailsService interface. It stores user data sources in memory, which is very helpful in some systems that do not need to introduce heavy data sources such as databases. Here just call createUser() to generate two users and assign corresponding roles. It will work just fine and restart the service multiple times without issue. Why emphasize restarting the service multiple times? The answer will be revealed later.

4. Authentication and authorization based on the default database model:

        In addition to InMemoryUserDetailsManager , Spring Security also provides another UserDetailsService implementation class: JdbcUserDetailsManager .

        JdbcUserDetailsManager helps us connect databases and Spring Security in the JDBC way . It sets a default database model. As long as this model is followed, JdbcUserDetailsManager is even comparable to InMemoryUserDetailsManager in terms of simplicity .

4.1. Database preparation:

Introduce the database dependencies of jdbc and  mysql         in pom.xml , as follows:

        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

        Then configure the database connection parameters in application.properties .

spring.datasource.url = jdbc:mysql://localhost:3306/springDemo?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username = root
spring.datasource.password = Rfid123456

        The name of the database connected here is springDemo ( there will be no problem if driverClassName is not configured , because SpringBoot will automatically infer it based on the URL ). The user name and password are root and Rfid123456 respectively . Readers can modify it according to the actual situation. As mentioned earlier, JdbcUserDetailsManager sets a default database model, which SpringSecurity defines in /org/springframework/security/core/userdetails/jdbc/users.ddl .

        Next is the table creation statement, as follows:

create database springDemo;
use springDemo;
create table users(
	username varchar(50) not null primary key,
	password varchar(500) not null,
	enabled boolean not null
);

create table authorities(
	username varchar(50) not null,
	authority varchar(50) not null,
	constraint fk_authorities_user foreign key(username) references users(username)
);
create unique index ix_auth_username on authorities(username,authority);

        JdbcUserDetailsManager needs two tables, among which the users table is used to store the user name, password and availability information, and the authorities table is used to store the corresponding relationship between the user name and its authority.

4.2. Coding implementation:

        Let's build a JdbcUserDetailsManager instance to let Spring Security use the database to manage users.

import org.apache.tomcat.jdbc.pool.DataSource;

@Autowired
private DataSource dataSource;

@Bean
public UserDetailsService userDetailsService() {
	JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
	manager.setDataSource(dataSource);
	manager.createUser(User.withUsername("user").password("123").roles("USER").build());
    manager.createUser(User.withUsername("admin").password("123").
        roles("USER","ADMIN").build());
	return manager;
}

        There is not much difference between JdbcUserDetailsManager and InMemoryUserDetailsManager in terms of usage, except that there is an additional link to set DataSource . Spring Security executes the set commands through DataSource . For example, the createUser function here actually executes the following SQL statement:

insert into users(username,password,enabled) values(?,?,?)

        Check the source code of JdbcUserDetailsManager to see more well-defined SQL statements, such as deleteUserSql , updateUserSql , etc. These are the forms of actual interaction between JdbcUserDetailsManager and the database. Of course, JdbcUserDetailsManager also allows us to customize these SQL statements in special cases. If necessary, just call the corresponding setXxxSql method.

Now restart the service and see what data is generated by Spring Security         in the database , as shown in the figure below.

        The authority field of the authorities table stores the previously set roles, but will be prefixed with "ROLE_" . Let's try to create a test account through the SQL command.

insert into users values("test","123",1);
insert into authorities values("test","ROLE_USER");

        Clear the cache and use the test account to access the system. It is found that the user route can be accessed , but the admin route cannot be accessed, which is consistent with the expected behavior.

        So far, everything is working fine, but as soon as we restart the service, the application throws an error. This is because when the users table creates a statement, the username field is the primary key, and the primary key is unique and unique. However, after restarting the service, admin and user will be created again , causing the database to report an error (this problem does not occur on memory data sources, because After restarting the service, the content in the username field will be cleared). Therefore, if you need to generate some users when the service starts, it is recommended to first determine whether the user name exists. As follows:

import org.apache.tomcat.jdbc.pool.DataSource;

@Autowired
private DataSource dataSource;

@Bean
public UserDetailsService userDetailsService() {
	JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
	manager.setDataSource(dataSource);
    if(!manager.userExists("user")){
        manager.createUser(User.withUsername("user").password("123").
            roles("USER").build());
    }
    if(!manager.userExists("admin")) {
        manager.createUser(User.withUsername("admin").password("123").
            roles("USER","ADMIN").build());
    }
	return manager;
}

        In the custom form login page, the WebSecurityConfigurerAdapter class defines three configure() methods.

protected void configure(AuthenticationManagerBuilder auth) throws Exception {
	this.disableLocalConfigureAuthenticationBldr = true;
}

public void configure(WebSecurity web) throws Exception {

}

protected void configure(HttpSecurity http) throws Exception {
	http
		.authorizeRequests()
			.anyRequest().authenticated()
				.and()
		.formLogin().and()
		.httpBasic();
}

        We only used one http parameter to receive the configuration method of the HttpSecurity object. The other two parameters also have their own purposes. Among them, the configure of AuthenticationManagerBuilder also allows us to configure authentication users.

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
	
	protected void configure(HttpSecurity http) throws Exception{
		http.authorizeRequests().
		antMatchers("/admin/api/**").hasRole("ADMIN").
		antMatchers("/user/api/**").hasRole("USER").
		antMatchers("/app/api/**").permitAll().
		anyRequest().authenticated().
		and().formLogin();
	}
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
			.withUser("user").password("123").roles("user")
			.and()
			.withUser("admin").password("123").roles("admin");
	}
}

        The usage methods are similar, so I won’t go into details here.

        When using the Spring Security default database model to deal with various user systems, it is inevitable that the flexibility is not good. Especially when embedding Spring Security into an existing system , the original user data has been fixed, and it is obviously not worth the candle to modify it at the database level in order to adapt to Spring Security . The powerful and flexible Spring Security improves on this.

Guess you like

Origin blog.csdn.net/xhf852963/article/details/121946119