Spring Security source code analysis from entry to proficiency. Follow up with Silicon Valley (1)

Spring Security source code analysis from entry to mastery. Follow Xueshang Silicon Valley

learning target

insert image description here

1. Introduction to Spring Security Framework

1.1 Overview

Spring is a very popular and successful Java application development framework, and Spring Security is a member of the Spring family. Based on the Spring framework, Spring Security provides a complete solution for web application security.

As you probably know the two main areas of security are " authentication " and " authorization " (or access control), in general, web application security consists of both user authentication and user authorization. Part, these two points are also important core functions of Spring Security.

(1) User authentication refers to: verifying whether a user is a legitimate subject in the system, that is to say, whether the user can access the system. User authentication generally requires the user to provide a user name and password. The system completes the authentication process by verifying the user name and password. In layman's terms, the system thinks whether the user can log in .
(2) User authorization refers to verifying whether a user has permission to perform an operation. In a system, different users have different permissions. For example, for a file, some users can only read it, while others can modify it. Generally speaking, the system assigns different roles to different users, and each role corresponds to a series of permissions. In layman's .

1.2 History

"Spring Security started late 2003," "spring's acegi security system". The cause was a question on the Spring developers mailing list, someone asked whether to consider providing a spring-based security implementation.

Spring Security started in late 2013 under the name "The Acegi Secutity System for Spring". A question was submitted to the Spring developers mailing list asking if there had been an opportunity for Spring's security community to consider an implementation. The Spring community was relatively small back then (compared to now). In fact Spring itself was only a project that existed on CourseForge in 2013, and the answer to this question is an area worthy of research, although the current lack of time prevents our exploration of it.

With this in mind, a simple security implementation was built but not released. A few weeks later, other members of the Spring community asked about security, and this time the code was sent to them. Several other requests followed. About 200,000 people had used the code by January 2014. Some of these entrepreneurs came up with a SourceForge project, and it was officially launched in March 2004.

In the early days, this project did not have any authentication module of its own, and the authentication process relied on container-managed security and Acegi security. Instead of focusing on authorization. This worked fine at first, but more and more users requested additional container support. The fundamental limitations of container-specific authentication domain interfaces become clear. There is also a related issue adding paths to new containers, which is a common source of end-user confusion and misconfiguration.

Introduction to Acegi security-specific authentication services. About a year later, Acegi Security officially became a sub-project of the Spring Framework. The 1.0.0 final version was published in 2006, after more than two and a half years of high-volume production software projects and hundreds of improvements and active use from community contributions.

Acegi Security officially became a Spring portfolio project at the end of 2007, renamed "Spring Security".

1.3 Comparison of the same product

1.3.1 Spring Security

Part of the Spring technology stack.
insert image description here
Secure your applications by providing full and scalable authentication and authorization support.
Spring Security official website documentation

Spring Security features:

  • Integrate seamlessly with Spring.
  • Comprehensive permission control.
  • Specifically designed for web development.
    • Older versions cannot be used outside of the web environment.
    • The new version extracts the entire framework hierarchically and divides it into core modules and Web modules. Introducing the core module alone can break away from the Web environment.
  • heavyweight.

1.3.2 Shiro

Apache's lightweight permission control framework.
insert image description here
Features:

  • lightweight. The philosophy advocated by Shiro is to make complex things simple. Better performance for Internet applications with higher performance requirements.
  • Versatility.
    • Benefits: It is not limited to the Web environment, and can be used without the Web environment.
    • Defect: Some specific requirements in the Web environment require manual code customization.

Spring Security is a security management framework in the Spring family. In fact, Spring Security has been developed for many years before Spring Boot appeared, but it is not used much. The field of security management has always been dominated by Shiro.

Compared with Shiro, it is more troublesome to integrate Spring Security in SSM. Therefore, although Spring Security is more powerful than Shiro, it is not used as much as Shiro (although Shiro does not have as many functions as Spring Security, but for most projects, Shiro will also suffice).

Since Spring Boot is available, Spring Boot provides an automatic configuration solution for Spring Security, and Spring Security can be used with less configuration.

Therefore, in general, the combination of common security management technology stacks is as follows:

  • SSM + Shiro
  • Spring Boot/Spring Cloud + Spring Security

The above is just a recommended combination. From a purely technical point of view, no matter how the combination is, it can be run.

1.4 Module division

insert image description here

2. Spring Security entry case

insert image description here

2.1 Create a project

(1). Create a Spring Boot project

Group:com.atguigu
Artifact:securitydemo1
insert image description here
version choose one at random, follow-up adjustment
insert image description here

(2). Change project dependency files

change pom.xml, change version

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

insert image description here
Change the configuration file as follows:
1. Add -web to the suffix
2. Add the following dependencies

<!-- spring security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

insert image description here

(3).Add Controller

insert image description here

@RestController
@RequestMapping("/test")
public class TestController {
    
    
    @GetMapping("hello")
    public String add() {
    
    
        return "hello security";
    }
}

Change the port number:

server.port=8111

insert image description here

2.2 Running the project

access

http://localhost:8111/login

The interface is like this, indicating that the spring security access is successful
insert image description here

Default username: user

The password will be printed on the console when the project is started. Note that the password will change every time it is started!

insert image description here
After entering the account number and password, click sign in and find that this is the introductory case interface we want to see.
insert image description here

2.3 Related concepts in rights management

2.3.1 Subject

English words: principal
users or devices that use the system or users who log in remotely from other systems, etc. Simply put, whoever uses the system is the subject.

2.3.2 Authentication

English word: authentication
authority management system confirms the identity of a subject and allows the subject to enter the system. Simply put, the "subject" proves who he is.
Generally speaking, it is the login operation done before.

2.3.3 Authorization

English word: authorization "
grants" the "power" of the operating system to the "subject", so that the subject has the ability to perform specific functions in the operating system.
So in simple terms, authorization is to assign permissions to users.

2.4 Add a controller for access

New class IndexController.java
insert image description here

@Controller
public class IndexController {
    
    
    @GetMapping("index")
    @ResponseBody
    public String index() {
    
    
        return "success";
    }
}

access:

http://localhost:8111/index

insert image description here

2.5 Basic Principles of Spring Security

Spring Security is essentially a filter chain:
the filter chain can be obtained from startup:

org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFil
ter
org.springframework.security.web.context.SecurityContextPersistenceFilter 
org.springframework.security.web.header.HeaderWriterFilter
org.springframework.security.web.csrf.CsrfFilter
org.springframework.security.web.authentication.logout.LogoutFilter 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
org.springframework.security.web.authentication.AnonymousAuthenticationFilter 
org.springframework.security.web.session.SessionManagementFilter 
org.springframework.security.web.access.ExceptionTranslationFilter 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor

2.5.1 Three basic filters

The underlying process of the code: focus on three filters:
insert image description here

FilterSecurityInterceptor : It is a method-level permission filter , basically located at the bottom of the filter chain.
insert image description here
super.beforeInvocation(fi) means to check whether the previous filter is passed.
fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); indicates the real call to the background service.
insert image description here

ExceptionTranslationFilter : It is an **exception filter,** used to handle exceptions thrown during the authentication and authorization process
insert image description here
insert image description here

UsernamePasswordAuthenticationFilter : Intercept the POST request of /login, and verify the username and password in the form.
insert image description here
insert image description here

2.5.2 Loading process of filter chain

Here we use Spring Boot integration, and the bottom layer helps us with automatic configuration. If it is not Spring Boot, we need to use the doFilter method of the DelegatingFilterProxy
doFilter method
insert image description here
initDelegate method
insert image description here
of the FilterChainProxy class.
insert image description here
The doFilterInternal method of the FilterChainProxy class
insert image description here

2.6 Explanation of UserDetailsService interface

When nothing is configured, the username and password are generated from Spring Security definitions. In the actual project, the account number and password are all queried from the database. So we need to control the authentication logic through custom logic.
In layman's terms, the user name and password for querying the database need to rewrite the interface

Query the user name and password of the database:
1. Create a class to inherit UsernamePasswordAuthenticationFilter, rewrite successfulAuthentication, unsuccessfulAuthentication, attemptAuthentication
2. Create a class to implement the UserDetailsService interface, write the process of querying data, and return the User object, which is an object provided by the security framework

If you need custom logic, you only need to implement the UserDetailsService interface. The interface is defined as follows:

  • return valueUserDetails

This class is the system default user " principal "

// 表示获取登录用户所有权限
Collection<? extends GrantedAuthority> getAuthorities();
// 表示获取密码
String getPassword();
// 表示获取用户名
String getUsername();
// 表示判断账户是否过期
boolean isAccountNonExpired();
// 表示判断账户是否被锁定
boolean isAccountNonLocked();
// 表示凭证{密码}是否过期
boolean isCredentialsNonExpired();
// 表示当前用户是否可用
boolean isEnabled();

The following is the UserDetails implementation class
org.springframework.security.core.userdetails.User
org.springframework.security.provisioning.MutableUser
insert image description here

In the future, we only need to use the User entity class!
insert image description here

  • The method parameter username
    represents the user name. This value is the data passed from the client form. By default, it must be called username, otherwise it cannot be received.

2.7 PasswordEncoder Interface Explanation

// 表示把参数按照特定的解析规则进行解析
String encode(CharSequence rawPassword);
// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
boolean matches(CharSequence rawPassword, String encodedPassword);
// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
false。默认返回 falsedefault boolean upgradeEncoding(String encodedPassword) {
    
    
return false;
}

The interface implementation class
insert image description here
BCryptPasswordEncoder is the password parser officially recommended by Spring Security, and this parser is usually used more often.
BCryptPasswordEncoder is a concrete implementation of the bcrypt strong hashing method. It is a one-way encryption based on the Hash algorithm. The encryption strength can be controlled by strength, the default is 10.

  • Demonstration of common methods
@Test
public void test01(){
    
    
// 创建密码解析器
BCryptPasswordEncoder bCryptPasswordEncoder = new 
BCryptPasswordEncoder();
// 对密码进行加密
String atguigu = bCryptPasswordEncoder.encode("atguigu");
// 打印加密之后的数据
System.out.println("加密之后数据:\t"+atguigu);
//判断原字符加密后和加密之前是否匹配
boolean result = bCryptPasswordEncoder.matches("atguigu", atguigu);
// 打印比较结果
System.out.println("比较结果:\t"+result);
}

output result:

加密之后数据:	$2a$10$HnfETFGYndTq05fxZDIK9uxm7tpPKPEZautjgDIseR0Suk6yeKQ2O
比较结果:	true

2.8 SpringBoot's automatic configuration of Security

3. Spring Security Web permission scheme

insert image description here

3.1 Set the account number and password for logging in to the system

Method 1: in application.properties

spring.security.user.name=atguigu
spring.security.user.password=atguigu

insert image description here
At this time, the IDEA console will not generate an account and password, so you can log in directly with the account and password in the configuration file

Method 2: Write a configuration class

Add new class SecurityConfig.java
insert image description here

The code is as follows: SecurityConfig

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    // 注入 PasswordEncoder 类到 spring 容器中
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String password = bCryptPasswordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("hsp").password(password).roles("");
    }

}

If you just write like this, you will report an error when you log in.
insert image description here
We need to add Bean
insert image description here

The complete code of SecurityConfig is as follows

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    // 注入 PasswordEncoder 类到 spring 容器中
    @Bean
    PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String password = bCryptPasswordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("hsp").password(password).roles("");
    }

}

Login is successful this time
insert image description here

Method 3: Custom writing implementation class

Custom implementation class setting:
the first step is to create a configuration class and set which userDetailsService implementation class to use.
The second step is to write the implementation class and return the User object. The User object has a username, password and operation authority

New class SecurityConfigCustomize.java

@Configuration
public class SecurityConfigCustomize extends WebSecurityConfigurerAdapter {
    
    
    // 注入 PasswordEncoder 类到 spring 容器中
    @Bean
    PasswordEncoder password() {
    
    
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService).passwordEncoder(password());
    }

}

Create a new MyUserNameDetailsService class

@Service("UserDetailsService")
public class MyUserNameDetailsService implements UserDetailsService {
    
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 设置密码
        String password = bCryptPasswordEncoder.encode("123");
        List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 返回用户
        return new User("heima", password, role);
    }
}

Try it and find that the login is successful
insert image description here

3.2 Implement database authentication to complete user login

Complete custom login
insert image description here

3.2.1 Prepare SQL

CREATE DATABASE spring_security;
USE spring_security;

CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR (20) UNIQUE NOT NULL,
  PASSWORD VARCHAR (100)
) ;

INSERT INTO users VALUES(3,'lucy','123');
INSERT INTO users VALUES(4,'mary','456');

3.2.2 Add dependencies

add dependencies

<!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--lombok 用来简化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3.2.3 Making Entity Classes

insert image description here
Users

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users {
    
    
    private Integer id;
    private String username;
    private String password;
}

3.2.4 Integrate MybatisPlus to make mapper

insert image description here
UserMapper

@Mapper
public interface UserMapper extends BaseMapper<Users> {
    
    
}

3.2.5 Make login implementation class

Modify MyUserNameDetailsService.java

@Service("UserDetailsService")
public class MyUserNameDetailsService implements UserDetailsService {
    
    
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        // 调用userMapper方法查询数据库的密码
        QueryWrapper<Users> usersQueryWrapper = new QueryWrapper<>();
        usersQueryWrapper.lambda().eq(Users::getUsername, username);
        // 查询到users对象
        Users users = userMapper.selectOne(usersQueryWrapper);
        // 判断
        if (null == users) {
    
    
            // 表示数据库没有用户名,认证失败
            throw new UsernameNotFoundException("用户名不存在!");
        }

        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 设置密码
        String password = bCryptPasswordEncoder.encode(users.getPassword());
        List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
        // 返回用户
        return new User(username, password, role);
    }
}

3.2.6 Add annotations to the startup class

Securitydemo1Application.java plus @MapperScan annotation

@SpringBootApplication
@MapperScan(basePackages = "com.atguigu.securitydemo1.mapper")
public class Securitydemo1Application {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(Securitydemo1Application.class, args);
    }

}

3.2.7 Configure the database in the configuration file

application.properties

#mysql 数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver 
spring.datasource.url=jdbc:mysql://localhost:3306/spring_security?serverTimezone=GMT%2B8 
spring.datasource.username=root 
spring.datasource.password=root

3.2.8 Test access

insert image description here

Enter username: lucy, password: 123

3.3 Unauthenticated requests jump to the login page

insert image description here

3.3.1 Introducing front-end template dependencies

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

3.3.2 Introducing the login page

Import the prepared login page into the project
and write a
insert image description here
login.html by hand

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    用户名: <input type="text" name="username"/>
    <br/>
    密码: <input type="password" name="password"/>
    <br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>

Note: The page submission method must be a post request
Note: username and password must be username,password
Reason:
A filter UsernamePasswordAuthenticationFilter will be used when performing login.
insert image description here

3.3.3 Write the controller

Modify TestController.java to add

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

3.3.4 Write configuration class release login page and static resources


insert image description here
Main idea: Rewrite the configure(HttpSecurity http) method SecurityConfigCustomize.java in WebSecurityConfigurerAdapter.java

@Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        // 自定义自己登录的表单页面
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/user/login")// 登录访问的路径
                .defaultSuccessUrl("/test/index") // 登录成功后要跳转的路径
                .permitAll()
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll() // 访问以下路径后无需登录认证
                .anyRequest().authenticated() // 所有请求都可以访问
                .and().csrf().disable() // 关闭csrf的防护
        ;
    }

3.3.5 Testing

access

http://localhost:8111/test/hello


insert image description here
can be accessed directly

http://localhost:8111/test/index

insert image description here
after login
insert image description here

3.3.6 Set the unauthorized request to jump to the login page

Above, if the parameters of the login interface are different, instead of username and password,
you can call the usernameParameter() and passwordParameter() methods to modify the configuration.
Modify
SecurityConfigCustomize.java

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        // 自定义自己登录的表单页面
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/user/login")// 登录访问的路径
                .defaultSuccessUrl("/test/index") // 登录成功后要跳转的路径
                .permitAll()
                .usernameParameter("loginAcct")
                .passwordParameter("userPswd")
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll() // 访问以下路径后无需登录认证
                .anyRequest().authenticated() // 所有请求都可以访问
                .and().csrf().disable() // 关闭csrf的防护
        ;
    }

Modify login.html
to change the username and password name

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    用户名: <input type="text" name="loginAcct"/>
    <br/>
    密码: <input type="password" name="userPswd"/>
    <br/>
    <input type="submit" value="登录"/>
</form>
</body>
</html>

Found that you can log in successfully

http://localhost:8111/test/index

insert image description here

3.4 Access control based on roles or permissions

insert image description here

3.4.1 hasAuthority method

Returns true if the current principal has the specified permissions, otherwise returns false
Only for one role, or one permission assignment

  • Modify the configuration class
    SecurityConfigCustomize.jav to add
    insert image description here
               .antMatchers("/test/index").hasAuthority("admins") // 当前路径具有admins权限才可以访问
  • Grant permissions to the user login subject
    Modify MyUserNameDetailsService.java
    insert image description here
    MyUserNameDetailsService.java
        List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("admins");

  • Test:
    visit
http://localhost:8111/test/index

insert image description here

After the authentication is completed, the login success is returned
insert image description here

3.4.2 hasAnyAuthority method

Returns true if the current principal has any of the provided roles (given as a comma-separated list of strings)
Only for multiple roles, or multiple permission assignments

If we also add multiple roles according to hasAuthority
insert image description here

code show as below:

@Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        // 自定义自己登录的表单页面
        http.formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/user/login")// 登录访问的路径
                .defaultSuccessUrl("/test/index") // 登录成功后要跳转的路径
                .permitAll()
                //.usernameParameter("loginAcct")
                //.passwordParameter("userPswd")
                .and().authorizeRequests()
                .antMatchers("/", "/test/hello", "/user/login").permitAll() // 访问以下路径后无需登录认证
                //.antMatchers("/test/index").hasAnyAuthority("admins") // 当前路径具有admins权限才可以访问
                .antMatchers("/test/index").hasAuthority("admins,manager") // 当前路径具有admins、manage权限才可以访问
                .anyRequest().authenticated() // 所有请求都可以访问
                .and().csrf().disable() // 关闭csrf的防护
        ;
    }

access

http://localhost:8111/test/index

Found an error 403
insert image description here
and we changed SecurityConfigCustomize.java
insert image description here

                .antMatchers("/test/index").hasAnyAuthority("admins,manager") // 当前路径具有admins、manage权限才可以访问

visit again

http://localhost:8111/test/index

correct
insert image description here

3.4.3 hasRole method

Access is allowed if the user has the given role, otherwise a 403 occurs.
Returns true if the current principal has the specified role.
The underlying source code:
insert image description here

Modify SecurityConfigCustomize.java
insert image description here

.antMatchers("/test/index").hasRole("sale") // 当前路径只有sale该角色,才能访问

Add roles to users:
Modify MyUserNameDetailsService.java
insert image description here

        List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");

Modify the configuration file:
Note that there is no need to add "ROLE_" in the configuration file, because the above-mentioned underlying code will be automatically added to match it.

access

http://localhost:8111/test/index

correct
insert image description here

3.4.4 hasAnyRole

Indicates that the user can access any one of the conditions.
Modify MyUserNameDetailsService.java
to add roles to users:
insert image description here

        List<GrantedAuthority> role = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale,ROLE_random");

Modify SecurityConfigCustomize.java
insert image description here


                .antMatchers("/test/index").hasAnyRole("sale") // 当前路径中满足括号内任意角色,都能访问

access

http://localhost:8111/test/index

Access successful
insert image description here

3.5 Realize permission authentication based on database

First of all, we quoted the dependency of mybatis-plus, and there is no need to quote the dependency of mybatis here

3.5.1 Executing database scripts

CREATE TABLE menu(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
url VARCHAR(100),
parentid BIGINT,
permission VARCHAR(20)
);

INSERT INTO menu VALUES(1,'系统管理','',0,'menu:system');
INSERT INTO menu VALUES(2,'用户管理','',0,'menu:user');

CREATE TABLE role(
id BIGINT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);

INSERT INTO role VALUES(1,'admin');
INSERT INTO role VALUES(2,'common');

CREATE TABLE role_user(
uid BIGINT,
rid BIGINT
);

INSERT INTO role_user VALUES(1,1);
INSERT INTO role_user VALUES(2,2);

CREATE TABLE role_menu(
MID BIGINT,
rid BIGINT
);

INSERT INTO role_menu VALUES(1,1);
INSERT INTO role_menu VALUES(2,1);
INSERT INTO role_menu VALUES(2,2);

3.5.2 Add entity class

Menu.java
insert image description here
code is as follows:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Menu {
    
    
    private Long id;
    private String name;
    private String url;
    private Long parentId;
    private String permission;
}

Add entity class Role
insert image description here

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    
    
    private Long id;
    private String name;
}

3.5.3 Write interface and implementation class

UsersMapper.java

@Mapper
public interface UsersMapper extends BaseMapper<Users> {
    
    
    /**
     * 根据用户Id查询用户角色
     *
     * @param userId
     * @return
     */
    List<Role> selectRoleByUserid(Long userId);

    /**
     * 根据用户Id查询菜单
     *
     * @param userId
     * @return
     */
    List<Menu> selectMenuByUserid(Long userId);
}

The above interface needs to perform multi-table management query:
you need to customize UsersMapper.xml in the resource/mapper directory.
The two names must be consistent
insert image description here

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.securitydemo1.mapper.UsersMapper">
    <!--
        mapper接口和映射文件要保证两个一致
        1.mapper接口的权和类名和映射文件的namespace一致
        2.mapper接口中的方法的方法名要和映射文件中的sql的id保持一致
     -->

    <!--    List<Role> selectRoleByUserid(Long userId);-->
    <select id="selectRoleByUserid" resultType="Role">
        SELECT b.`id`,
               b.`name`
        FROM role_user a
                 LEFT JOIN role b
                           ON a.`rid` = b.`id`
        WHERE a.`uid` = #{userId}

    </select>

    <!--    List<Menu> selectMenuByUserid(Long userId);-->
    <select id="selectMenuByUserid" resultType="Menu">
        SELECT m.id,
               m.name,
               m.url,
               m.parentid,
               m.permission
        FROM menu m
                 INNER JOIN role_menu rm
                            ON m.id = rm.mid
                 INNER JOIN role r
                            ON r.id = rm.rid
                 INNER JOIN role_user ru
                            ON r.id = ru.rid
        WHERE ru.uid = #{userId}
    </select>
</mapper>

MyUserNameDetailsService.java

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    
        // 调用userMapper方法查询数据库的密码
        QueryWrapper<Users> usersQueryWrapper = new QueryWrapper<>();
        usersQueryWrapper.lambda().eq(Users::getUsername, username);
        // 查询到users对象
        Users users = usersMapper.selectOne(usersQueryWrapper);
        // 判断
        if (null == users) {
    
    
            // 表示数据库没有用户名,认证失败
            throw new UsernameNotFoundException("用户名不存在!");
        }
        // 给该用户设置密码
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        // 设置密码
        String password = bCryptPasswordEncoder.encode(users.getPassword());

        // 获取到用户角色,菜单列表
        List<Role> roleList = usersMapper.selectRoleByUserid((long) users.getId());
        List<Menu> menuList = usersMapper.selectMenuByUserid((long) users.getId());

        // 声明一个集合List
        ArrayList<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();

        // 处理角色
        for (Role role : roleList) {
    
    
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getName());
            grantedAuthoritiesList.add(simpleGrantedAuthority);
        }

        // 处理权限
        for (Menu menu : menuList) {
    
    
            grantedAuthoritiesList.add(new SimpleGrantedAuthority(menu.getPermission()));
        }


        // 返回用户
        return new User(username, password, grantedAuthoritiesList);
    }

3.5.4 Add mappings to the configuration file

In the configuration file application.properties add

mybatis-plus.type-aliases-package=com.atguigu.securitydemo1.pojo
mybatis-plus.mapper-locations=classpath:com/atguigu/securitydemo1/mapper/*.xml

3.5.5 Modify access configuration class

                .antMatchers("/test/index").hasAnyRole("admin") // 当前路径中满足括号内任意角色,都能访问

3.5.6 Testing with administrators and non-administrators

Login as administrator

http://localhost:8111/test/index

Login as follows
insert image description here

If the non-administrator test will prompt 403 no permission
insert image description here

3.6 Customize 403 page

insert image description here

3.6.1 Modify access configuration class

Modify SecurityConfigCustomize.java
insert image description here

http.exceptionHandling().accessDeniedPage("/unauth.html");

3.6.2 Add corresponding controller

Here you jump directly to the static interface, so you don’t need to add a controller

3.6.3 Add corresponding static page

New static interface
insert image description here

unauth.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Unauth</title>
</head>
<body>
<h1>没有访问权限!</h1>

</body>
</html>

3.6.4 Testing

For testing, you can remove permissions
insert image description here

access

http://localhost:8111/test/index

Jump successfully
insert image description here

3.7 Annotation use

insert image description here

3.7.1 @Secured

Determine whether the user has a role and can access methods. Another thing to note is that the matched string here needs to be prefixed with "ROLE_". To use annotations, you must first enable the annotation function!
@EnableGlobalMethodSecurity(securedEnabled=true)

Securitydemo1Application.java

@SpringBootApplication
@MapperScan(basePackages = "com.atguigu.securitydemo1.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true)
public class Securitydemo1Application {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(Securitydemo1Application.class, args);
    }

}

Annotate
TestController.java on the controller method

@GetMapping("update")
    @Secured({
    
    "ROLE_sale", "ROLE_manager"})
    public String update() {
    
    
        return "hello update";
    }

Modify MyUserNameDetailsService.java, add ROLE_sale role
insert image description here

Direct access after login: Controller

http://localhost:8111/test/update

insert image description here

Change the above role to @Secured({"ROLE_normal", "ROLE_manager"}) and you will not be able to access it
insert image description here

3.7.2 @PreAuthorize

First enable the annotation function:
@EnableGlobalMethodSecurity(prePostEnabled = true)
@PreAuthorize: Annotation is suitable for permission verification before entering the method. @PreAuthorize can pass the roles/permissions parameters of the logged-in user to the method.

Securitydemo1Application.java

@SpringBootApplication
@MapperScan(basePackages = "com.atguigu.securitydemo1.mapper")
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class Securitydemo1Application {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(Securitydemo1Application.class, args);
    }

}

Modify TestController.java

@GetMapping("update")
    @PreAuthorize("hasAnyAuthority('admin')")
    public String update() {
    
    
        return "hello update";
    }

Make sure that MyUserNameDetailsService.java has the admin role
insert image description here
. Make sure

Use lucy to log in and test:
insert image description here

3.7.3 @PostAuthorize

First enable the annotation function:
@EnableGlobalMethodSecurity(prePostEnabled = true)
has been enabled before

The @PostAuthorize annotation is not used much, and the permission verification is performed after the method is executed, which is suitable for verifying the permission with the return value.

Modify TestController.java

    @GetMapping("update")
    @PostAuthorize("hasAnyAuthority('admin')")
    public String update() {
    
    
        System.out.println("update……");
        return "hello update";
    }

Modify MyUserNameDetailsService.java
insert image description here
and use lucy to log in to test:
insert image description here
it is found that there is no access right, but IDEA background output, indicating that it is verified after the method is executed.
insert image description here

3.7.4 @PostFilter

@PostFilter : Filter the data after authorization verification, leaving the data whose user name is admin1.
The filterObject in the expression refers to an element in the method return value List.
Modify TestController.java

@GetMapping("getAll")
    @PreAuthorize("hasAnyAuthority('admin')")
    @PostFilter("filterObject.username == 'lucy'")
    @ResponseBody
    public List<Users> getAllUser() {
    
    
        ArrayList<Users> list = new ArrayList<>();
        list.add(new Users(1, "lucy", "123"));
        list.add(new Users(2, "admin2", "888"));
        System.out.println("list:" + list);
        return list;
    }

access

http://localhost:8111/test/getAll

username lucy
insert image description here

IDEA background printing

list:[Users(id=1, username=lucy, password=123), 
Users(id=2, username=admin2, password=888)]

3.7.5 @PreFilter

@PreFilter: Filter data before entering the controller
Modify TestController.java

 @GetMapping("getTestPreFilter")
    @PreAuthorize("hasAnyAuthority('admin')")
    @PreFilter(value = "filterObject.id%2==0")
    @ResponseBody
    public List<Users> getTestPreFilter(@RequestBody List<Users> list) {
    
    
        list.forEach(t -> {
    
    
            System.out.println(t.getId() + "\t" + t.getUsername());
        });
        return list;
    }

Login first, then use postman to test
insert image description here

http://localhost:8111/test/getTestPreFilter

Tested Json data:

[{
    
    
"id": "1",
"username": "lucy",
"password": "123"
},
{
    
    
"id": "2",
"username": "mary",
"password": "456"
},
{
    
    
"id": "3",
"username": "admins11",
"password": "11888"
},
{
    
    
"id": "4",
"username": "admins22",
"password": "22888"
}]

After clicking send on my side, it jumps to the login page. I am very confused. If you have tested the effect here, you can explain how to test
insert image description here
SecurityConfigCustomize.java on my side. I deliberately added no login verification and it has no effect.
insert image description here

3.7.6 Permission expressions

Mastering is not done here. You can view the official website:
permission expression

3.8 Remember the account number and password when logging in (based on the database)

The cookie-based method
insert image description here
is based on the principle of Spring Security
insert image description here
: onLoginSuccess in loginSuccess in
the parent class of UsernamePasswordAuthenticationFilter
insert image description here
successfulAuthentication method rememberMeServices class AbstractRememberMeServices
insert image description here

insert image description here
The onLoginSuccess in the PersistentTokenBasedRememberMeServices class
insert image description here
creates a table and writes the logic, and writes the Token into the Cookie.
This class is the process of creating a table and writing it. The doFilter method of the
JdbcTokenRepositoryImpl.java RememberMeAuthenticationFilter.
insert image description here

insert image description here

insert image description here

3.8.1 Create table

Don't create it here, it is created by Spring Security itself, the table named persistent_logins
The specific creation process is in JdbcTokenRepositoryImpl.
insert image description here

3.8.2 Add the configuration file of the database

This one was added before
insert image description here

3.8.3 Writing configuration classes

Create a configuration class
insert image description here
BrowserSecurityConfig

This code, open after the first startup, then comment out this code jdbcTokenRepository.setCreateTableOnStartup(true);

@Configuration
public class BrowserSecurityConfig {
    
    
    @Autowired
    private DataSource dataSource;

    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
    
    
        JdbcTokenRepositoryImpl jdbcTokenRepository = new
                JdbcTokenRepositoryImpl();
        // 赋值数据源
        jdbcTokenRepository.setDataSource(dataSource);
        // 自动创建表,第一次执行会创建,以后要执行就要删除掉!
        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
}

3.8.4 Modify security configuration class

Add SecurityConfigCustomize.java
insert image description here
insert image description here

@Autowired
    private PersistentTokenRepository tokenRepository;

// 开启记住我功能
        http.rememberMe()
                .tokenRepository(tokenRepository)
                .userDetailsService(userDetailsService);

3.8.5 Add a remember me checkbox to the page

login.html add

记住我:<input type="checkbox" name="remember-me" title="记住密码"/>

Here: the value of the name attribute must be remember-me. It cannot be changed to other values

3.8.6 Login Test

access:

http://localhost:8111/test/index

insert image description here
Press F12 for developer mode
insert image description here

Successful login
insert image description here
After successful login, close the browser and visit again, and find that it can still be used!
insert image description here
Note here, it is best to use the goole browser to see the effect

3.8.7 Set validity period

The default is 2 weeks. However, by setting the valid time of the status, you can log in normally even if the project is restarted next time. Set SecurityConfigCustomize.java
in the configuration file

 // 开启记住我功能
        http.rememberMe()
                .tokenRepository(tokenRepository)
                .userDetailsService(myUserNameDetailsService)
                .tokenValiditySeconds(1800) // 单位是秒
        ;

3.9 User Logout

insert image description here

3.9.1 Add a logout link to the login page

success.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登录成功!
<a href="/logout">退出</a>
</body>
</html>

3.9.2 Add the exit mapping address in the configuration class

SecurityConfigCustomize.java
added

// 退出
        http.logout().logoutUrl("/logout").logoutSuccessUrl("/test/quit").permitAll();

Revise
insert image description here

.defaultSuccessUrl("/success.html") // 登录成功后要跳转的路径

Modify TestController.java
to add exit method

@GetMapping("quit")
    public String quit() {
    
    
        return "quit";
    }

3.9.3 Testing

access

http://localhost:8111/login.html

insert image description here
After logging in, jump to success.html
insert image description here
and click to exit
insert image description here

After logging out, it is impossible to access the controller that needs to be accessed when logging in!
For example, we visit:

http://localhost:8111/test/index

need to log in again
insert image description here

3.10 CSRF

3.10.1 CSRF understanding

Cross-site request forgery (English: Cross-site request forgery), also known as one-click attack or session riding, usually abbreviated as CSRF or XSRF, is a method that coerces users to execute unintentional The attack method of the operation. Compared with cross-site scripting (XSS), which uses the user's trust in the specified website, CSRF uses the website's trust in the user's web browser.

Cross-site request attack, simply put, is that the attacker uses some technical means to deceive the user's browser to visit a website that he has authenticated and perform some operations (such as sending emails, sending messages, and even property operations such as transferring money and purchasing goods) ). Since the browser has been authenticated, the visited website will be considered to be a real user operation and run. This exploits a vulnerability in user authentication on the web: simple authentication can only guarantee that the request is from a user's browser, but not that the request itself is voluntary.

Starting with Spring Security 4.0, CSRF protection is enabled by default to prevent CSRF from attacking applications, and Spring Security CSRF protects against PATCH, POST, PUT and DELETE methods.

3.10.2 Case

Add a hidden field to the login page:
insert image description here
login.html

<input type="hidden" th:if="${_csrf}!=null" th:value="${_csrf.token}" name="_csrf"/>


csrf SecurityConfigCustomize.java in the class that closes the security configuration
insert image description here

// http.csrf().disable();

Add dependencies in pom.xml

<!-- 对Thymeleaf添加Spring Security标签支持 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

Here is a brief introduction to the principle and configuration of the implementation. For the specific implementation, this method does not work and needs to be skipped. We can just use the information provided by the teacher to have a look.
Import course case: insert image description here
Modify SecurityConfig.java, turn off csrf protection
insert image description here
Modify csrfTest.html, comment CSRF logic
insert image description here

Modify csrf_token.html, comment CSRF logic
insert image description here

Start the service, access

http://localhost:8112/toupdate

insert image description here
After clicking to modify (actually log in), access correctly
insert image description here
Then, we annotate
SecurityConfig.java, which means to enable CSRF protection

// http.csrf().disable();

insert image description here
Visit again after restarting the service

http://localhost:8112/toupdate

insert image description here
After clicking Login, I found that I was redirected.
insert image description here
The reason is that the SecurityConfig.java class is configured with a failure jump.
insert image description here

Solution: We remove the comments of csrfTest.html and csrf_token.html and
insert image description here
visit again after restarting

http://localhost:8112/toupdate

insert image description here
After logging in, it is as follows:
insert image description here
We have selected the F12 developer and looked at the page elements, and the token has been brought here
insert image description here

3.10.3 The principle of Spring Security implementing CSRF:

Simple schematic:
insert image description here

  1. Generate csrfToken and save it in HttpSession or Cookie.
    The class diagram is as follows:
    insert image description here
    The SaveOnAccessCsrfToken class has an interface CsrfTokenRepository.
    insert image description here
    The current interface implementation class: HttpSessionCsrfTokenRepository, CookieCsrfTokenRepository
    insert image description here
    insert image description here

  2. When the request comes, extract the csrfToken from the request, compare it with the saved csrfToken, and then judge whether the current request is legal. Mainly through the CsrfFilter filter to complete.

insert image description here
The doFilterInternal method of the CsrfFilter.java class
insert image description here
where the requireCsrfProtectionMatcher.matches method

insert image description here
View the DefaultRequiresCsrfMatcher method
where these requests "GET", "HEAD", "TRACE", "OPTIONS" CSRF are not protected
insert image description here

Guess you like

Origin blog.csdn.net/sinat_38316216/article/details/129998468