Spring Security combat (1) - authentication and authorization based on memory and database models

Table of contents

Introduction

1. Getting to know Spring Security (entry case)

(1) Create a new project

(2) Select dependency

(3) Write a HelloController

(4) Start the project and visit localhost:8080

(5) Customize user name and password

 2. Form authentication

1. Custom Form Login Page

2. Configure spring security

3. Restart the project

​edit

3. Authentication and Authorization

1. Resource preparation

(1) Create two new controllers

(2) Configuration of resource authorization

(3) Restart service access

 2. Memory-based multi-user support

(1) Configure users in memory

 (2) Access test

  3. Authentication and authorization based on the default database model

(1) Create a database table

(2) Introduce jdbc dependencies and configure the database

(3) Configure spring securiy authorization

 4. Authentication and authorization based on custom database model

(1) Database preparation

 (2) Write entity class User

(3) Write a custom UserDetailsServiceImpl

(4) Write Mapper

(5) spring security configuration

(6) Test


Introduction

General web applications require authentication and authorization .

​Authentication  : Verify that the user currently accessing the system is a user of this system, and confirm which user it is

​Authorization  : After authentication, determine whether the current user has permission to perform an operation

Authentication and authorization are also the core functions of Spring Security as a security framework.

(1) Login verification process for front-end and back-end separation projects:

 (2) The complete process of Spring Security

The principle of Spring Security is actually a filter chain, which contains filters that provide various functions, such as:

1. Getting to know Spring Security (entry case)

(1) Create a new project

(2) Select dependency

(3) Write a HelloController

@RestController
@RequestMapping("/")
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello spring security!";
    }
}

(4) Start the project and visit localhost:8080

Even without any configuration, after introducing Spring Security, accessing the corresponding URL resources requires HTTP basic authentication .

After startup, the console will print an initial password, as shown in the figure:

 Visit localhost:8080, fill in the login information (this is HTTP basic authentication), and fill in the user name user

(5) Customize user name and password

Configure the user name and password in the configuration file application.yml, and use the user name and password to log in after restarting the project.

spring:
  security:
    user:
      name: zy
      password: abc

Introductory case summary:

 

Authentication interface: Its implementation class represents the user currently accessing the system and encapsulates user-related information.

AuthenticationManager interface: defines the authentication authentication method

UserDetailsService interface: the core interface for loading user-specific data. It defines a method to query user information based on username.

UserDetails interface: Provides core user information. The user information obtained and processed through the UserDetailsService according to the username should be encapsulated into a UserDetails object and returned. Then encapsulate this information into the Authentication object.

step:

(1) After submitting the username and password, the username and password will be passed to UsernamePasswordAuthenticationFilter

        UsernamePasswordAuthenticationFilter is a very important filter in Spring Security. It is responsible for processing form-based authentication, that is, when a user submits a form containing a username and password, the filter extracts the username and password from the request and performs authentication. 

(2) UsernamePasswordAuthenticationFilter calls the authenticate() method for authentication

        When this method is executed, it gets the username and password parameters in the request, and then calls the authenticate() method of the AuthenticationManager object to authenticate. If authentication is successful, an Authentication object is created and passed into the SecurityContextHolder. If authentication fails, AuthenticationException is thrown.

 (3) AuthenticationManager will continue to call the authenticate() of DaoAuthenticationProvider for verification

        DaoAuthenticationProvider is an implementation class of AuthenticationProvider, which is used to handle the process of user name and password verification. When the AuthenticationManager calls the authenticate() method, it actually delegates to the DaoAuthenticationProvider to handle the authentication request.

(4) DaoAuthenticationProvider will also call the loadUserByUsername() method to query users

        In the authenticate() method of DaoAuthenticationProvider, the loadUserByUsername () method of UserDetailsService will be called first to obtain user information (this may be searched in memory or in the database according to the actual situation) and then the obtained user information and user input The passwords are compared to determine whether the user is authenticated. Therefore, in the authentication process, the loadUserByUsername() method is a very important link.

(5) Return the UserDetail object

        The loadUserByUsername() method mainly queries user information according to a given username and returns a UserDetails object . The specific implementation of this method may involve accessing a database or other storage device to obtain the user's detailed information. In Spring Security, this method is usually done by the implementation class of UserDetailsService. In the implementation class, the user information is usually queried according to the user name, and encapsulated into a UserDetails object for use in the subsequent authentication process. The UserDetails object contains the user's identity information, authorization information, and other details , such as password and whether it is enabled or not.

(6) Compare the UserDetail password with the submitted password through the PasswordEncoder object

        Encrypt the password entered by the user through the PasswordEncoder object, and then compare it with the encrypted password stored in UserDetails to verify the identity of the user. If the two are consistent, the user authentication is considered successful. PasswordEncoder is mainly used to encrypt passwords to improve security.

(7) If correct, set the permission information in UserDetails to the Authentication object

(8) Return the Authentication object

(9) Store the successfully authenticated Authentication objects SecurityContextHolder in

        SecurityContextHolder will use ThreadLocal to store authentication objects to ensure that each thread has its own SecurityContext instance. In subsequent requests, other filters can use the SecurityContextHolder.getContext().getAuthentication() method to obtain the authentication object.


 2. Form authentication

1. Custom Form Login Page

login.html, placed under static

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login Page</title>
    <style>
        /* 样式可以自行修改 */
        body {
            background-color: cadetblue;
        }

        .login-form {
            width: 350px;
            margin: 150px auto;
            background-color: #fff;
            padding: 20px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
        }

        h1 {
            font-size: 24px;
            text-align: center;
            margin-bottom: 30px;
        }

        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 2px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        button {
            background-color: darksalmon;
            color: white;
            padding: 12px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            width: 100%;
        }

        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
<div class="login-form">
    <h1>Login Page</h1>
    <form th:action="@{/login}" method="post">
        <label for="username">Username</label>
        <input type="text" id="username" name="username" placeholder="Enter username">

        <label for="password">Password</label>
        <input type="password" id="password" name="password" placeholder="Enter password">

        <button type="submit">Login</button>
    </form>
</div>
</body>
</html>

2. Configure spring security

Rewrite the configure method and receive an HttpSecurity object. HttpSecurity provides many configuration-related methods:

(1) authorizeRequests() is a configuration method in Spring Security, which is used to define which requests need to be authorized to be accessed. This method returns an ExpressionInterceptUrlRegistry object, which is used to configure the access authorization for the URL.

Through this method, we can use various methods to configure URL authorization, for example:

  • antMatchers()method is used to match URLs and set the required access permissions.
  • hasRole()and hasAuthority()methods are used to specify the required roles or permissions.
  • permitAll()method is used to specify that no access rights are required for access.

(2)formLogin()

formLogin()Is a configuration method in Spring Security that specifies the use of form login for authentication. By default, Spring Security automatically redirects to the default login page if no authentication is done.

Through this method, we can configure as follows:

  • loginPage()method is used to specify the URL of the login page.
  • loginProcessingUrl()method is used to specify the URL that handles the login request.
  • usernameParameter()The and passwordParameter()methods are used to specify the parameter names for username and password in the form.
  • successHandler()The and failureHandler()methods are used to specify the processing logic after login success and failure.
  • permitAll()method is used to specify access permissions for the login page.

(3) csrf()is a configuration method in Spring Security, which is used to configure the cross-site request forgery (Cross-Site Request Forgery, CSRF) protection function. CSRF attack is a malicious attack method. The attacker obtains the user's authorization information through some means, and then uses this information to send malicious requests to achieve the purpose of the attack.

In Spring Security, CSRF protection is enabled by default. If you want to turn it off, you can use csrf().disable()the method to disable it.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

3. Restart the project

Visit localhost:8080/hello, it will jump to: http://localhost:8080/login.html

3. Authentication and Authorization

1. Resource preparation

(1) Create two new controllers

One is accessible only to administrators, and the other is accessible to ordinary users.

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

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

    @GetMapping("/hello")
    public String helloAdmin() {
        return "hello User!";
    }
}

(2) Configuration of resource authorization

To request resources under /admin/api/**, you need to check whether there is an ADMIN role

To request resources under /user/api/**, you need to check whether there is a USER role

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
}

(3) Restart service access

It can be seen that accessing  http://localhost:8080/hello  can be successful, but accessing http://localhost:8080/admin/api/hello will be denied access (403 error code)

 2. Memory-based multi-user support

(1) Configure users in memory

The two configure(HttpSecurity http)  and configure(AuthenticationManagerBuilder auth) methods         in the following configuration are two key methods in WebSecurityConfigurerAdapter, which are used to configure Spring Security authentication and authorization.

  configure(HttpSecurity http)The method is used to configure the authorization rules for requests, that is, which requests require which permissions to access.

  configure(AuthenticationManagerBuilder auth)method is used to configure the authentication method, that is, how to verify the identity of the user. In this example, we use in-memory authentication by calling .inMemoryAuthentication()the method, then use .withUser()the method to specify the username and password, use {noop}the prefix to indicate that the password is not encrypted, and finally use .roles()the method to specify the user's role.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("{noop}abcd").roles("ADMIN")
                .and()
                .withUser("zy").password("{noop}abc").roles("USER");
    }
}

 (2) Access test

Use the username admin and the password abcd to log in, and the following content can be displayed:

  3. Authentication and authorization based on the default database model

(1) Create a database table

Two users are created in the database, namely:

The role corresponding to user is ROLE_USER;

The role corresponding to admin is ROLE_ADMIN.

CREATE TABLE users (
   username VARCHAR(50) NOT NULL PRIMARY KEY,
   password VARCHAR(100) NOT NULL,
   enabled BOOLEAN NOT NULL
);

CREATE TABLE authorities (
   username VARCHAR(50) NOT NULL,
   authority VARCHAR(50) NOT NULL,
   FOREIGN KEY (username) REFERENCES users(username)
);


INSERT INTO users (username, password, enabled) VALUES ('user', '12345', true);
INSERT INTO users (username, password, enabled) VALUES ('admin', '12345', true);

INSERT INTO authorities (username, authority) VALUES ('user', 'ROLE_USER');
INSERT INTO authorities (username, authority) VALUES ('admin', 'ROLE_ADMIN');

(2) Introduce jdbc dependencies and configure the database

Introduce jdbc and MySQL dependencies

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

 Configure the database connection:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security-db?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

(3) Configure spring securiy authorization

        The jdbcAuthentication() method is used here to enable JDBC-based user storage, and the dataSource() method is used to set the data source, which is the DataSource connected to the database.

        Next, the usersByUsernameQuery() method is used to set the SQL statement for querying the user name, password and enabled status. This statement will be executed when the user logs in, and the user information in the database is queried according to the entered user name, and the queried password and The enabled state is used for authentication. The authoritiesByUsernameQuery() method is used to set the SQL statement for querying user roles. Finally, the passwordEncoder() method is used to set the password encryption method, and the NoOpPasswordEncoder.getInstance() method is used here to disable password encryption, that is, the password queried from the database is directly compared as plaintext.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .usersByUsernameQuery("SELECT username, password, enabled FROM users WHERE username = ?")
                .authoritiesByUsernameQuery("SELECT username, authority FROM authorities WHERE username = ?")
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }
}

 4. Authentication and authorization based on custom database model

Above we used two UserDetailsService implementation classes, InMemoryUserDetailsManager and JdbcUserDetailsManager. Below we use a custom database model and a custom UserDetails implementation class.

(1) Database preparation

CREATE TABLE my_users (
  id INT(11) NOT NULL AUTO_INCREMENT,
  username VARCHAR(50) NOT NULL,
  password VARCHAR(100) NOT NULL,
  enabled TINYINT(1) NOT NULL DEFAULT '1',
  roles VARCHAR(200) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY username_UNIQUE (username)
);

Insert two pieces of data:

 (2) Write entity class User

        This entity class has a one-to-one correspondence Userwith the previous table my_users, and each attribute corresponds to a field in the table:

  • idCorresponds to the field in the table id, which is used to uniquely identify each user
  • usernameCorresponds to the field in the table username, indicating the user's login name
  • passwordCorresponds to the field in the table password, indicating the user's password
  • enabledCorresponds to the field in the table enabled, indicating whether the user enables
  • rolesCorresponds to the field in the table roles, indicating the role owned by the user

        It should be noted that there is also an attribute Userin this entity class , which is used to save the user's permission information, and it does not correspond to any field in the table. This attribute will be set as the user's permission information in the method of the class for authentication and authorization. In order to achieve this function, a method is also defined in the class , which is used to parse the field into a collection of type, and perform lazy loading when needed.authoritiesUserDetailsServiceImplloadUserByUsernameUsergetAuthoritiesrolesList<GrantedAuthority>

@Data
public class User implements UserDetails {

    private Long id;
    private String username;
    private String password;
    private boolean enabled;
    private String roles;
    private List<GrantedAuthority> authorities;


    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    //用于将 roles 字段解析成 List<GrantedAuthority> 类型的集合
    public List<GrantedAuthority> getAuthorities() {
        if (authorities == null) {
            authorities = new ArrayList<>();
            for (String role : roles.split(",")) {
                authorities.add(new SimpleGrantedAuthority(role.trim()));
            }
        }
        return authorities;
    }
}

(3) Write a custom UserDetailsServiceImpl

        This UserDetailsServiceImplclass implements UserDetailsServicethe interface and is a service class for loading user information. In loadUserByUsernamethe method, by UserMapperquerying the corresponding object from the database , and then constructing the list Usercorresponding to the user , and finally returning the object.GrantedAuthorityUser

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库中查询用户信息
        User user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 构建用户权限信息
        List<GrantedAuthority> authorities = user.getAuthorities();
        user.setAuthorities(authorities);
        return user;
    }
}

(4) Write Mapper

@Mapper
public interface UserMapper {

    @Select("SELECT * FROM my_users WHERE username = #{username}")
    User findByUsername(@Param("username") String username);
}

(5) spring security configuration

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private DataSource dataSource;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/api/**").hasRole("ADMIN")
                .antMatchers("/user/api/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login.html")
                .permitAll()
                .and()
                .csrf().disable();
    }
//使用自定义的数据库模型进行认证和授权
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

}

(6) Test

Break point debugging, you can see that two roles can be obtained when logging in with the admin user.

 

Guess you like

Origin blog.csdn.net/weixin_49561506/article/details/130140438