Spring Security Basics--integrate SpringBoot, authentication process analysis, configure database, integrate MyBatis, password encryption, session authorization function

1. Integrate advantages

SpringBoot can save us a lot of configuration files and project construction, and quickly import dependency packages, and SpringBoot is also a commonly used framework on the market, which can integrate most other frameworks on the market, and the use and development efficiency is very high.

2. Create a project

We create an empty project and introduce maven dependencies to build the springboot framework.
(1) Create an empty maven project and introduce dependencies.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mcs.security</groupId>
    <artifactId>springboot-springsecurity</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.complier.source>1.8</maven.complier.source>
        <maven.complier.target>1.8</maven.complier.target>
    </properties>

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

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

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

        <!-- 数据库依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!-- jsp 依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>
    </dependencies>

    <build>
    	<!-- 改为你的项目名称 -->
        <finalName>springboot-springsecurity</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <!--<configuration>
                        <path>/shiro</path>
                        <port>8080</port>
                        <uriEncoding>UTF-8</uriEncoding>
                        <url>http://localhost:8080/shiro</url>
                        <server>Tomcat7</server>
                    </configuration>-->
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>

                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>utf-8</encoding>
                        <useDefaultDelimiters>true</useDefaultDelimiters>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                                <filtering>true</filtering>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                            <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                    <include>**/*.xml</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>


</project>

(2) Spring container configuration
The springboot project will automatically scan all beans under the package where the startup class is located, and load them into the spring container.
Create application.properties under resource
我这里默认把后面用到的配置也贴了上来

server.port=8080
server.servlet.context-path=/springboot-springsecurity

spring.application.name=springboot-springsecurity

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# 设置session超时时间
#server.servlet.session.timeout=10s

(3) SpringBoot startup class
The startup class should be created in the same directory as all subpackages
insert image description here

@SpringBootApplication
@Configuration
public class SecuritySpringBootApp {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SecuritySpringBootApp.class, args);
    }
}

(4) servlet Context configuration
is equivalent to the springMVC main configuration file when we configure the ssm framework.
The configuration here is not like the @EnableWebMvc and @ComponentScan annotations we needed last time. SpringBoot's automatic assembly mechanism will configure it for us.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    

    // 默认url根路径跳转到mvc解析地址下的login
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");
        registry.addViewController("/login-view").setViewName("login");
    }
}

It is much more convenient to configure the view resolver in the application.properties file than in the configuration file

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

(5) SpringSecurity configuration
saves @EnableWebSecurity configuration

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    // 配置模拟用户信息
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
        return manager;
    }
    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    // 配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated() // 拦截/r/**请求
                .anyRequest().permitAll() // 其他请求正常方行
                .and()
                .formLogin().successForwardUrl("/login-success");
    }
}

(6) Test
Add controller configuration

@RestController
public class loginController {
    
    

    @RequestMapping(value = "/login-success", produces = {
    
    "text/plain;charset=utf-8"})
    public String loginSuccess() {
    
    
        return "登录成功";
    }

    @RequestMapping(value = "/r/r1", produces = {
    
    "text/plain;charset=utf-8"})
    public String r1() {
    
    
        return "访问资源r1";
    }

    @RequestMapping(value = "/r/r2", produces = {
    
    "text/plain;charset=utf-8"})
    public String r2() {
    
    
        return "访问资源r2";
    }
}

3. Custom Authentication UserDetailService

springSecurity authentication process
insert image description here
1. The username and password submitted by the user will be UsernamePasswordAuthentionFilterobtained through the filter first, and encapsulated as Authentication.
2. Then submit the Authentication to the authentication manager for authentication AuthenticationMangere. Authentication is a process of identifying the user's identity. After the authentication is successful, an Authentication object will be returned, which contains identity information. This identity information is an Object and will be forced into an object UserDetails.
3. What is its certification process like? The authentication manager entrusts the authentication to AuthenticationProviderthe authentication, and the AuthenticationProvider stores a variety of authentication methods, such as SMS authentication, password authentication, etc., and the password authentication is to use the DaoAuthenticationProviderauthentication on the picture.
4. What does DaoAuthenticationProvider do internally? It is mentioned above that the last returned object is UserDetails. It can also be seen from the figure that this process is handed over to DaoAuthenticationProvider. UserDetailService is responsible for loading data from the database, querying data with its methods, UserDetailServiceand loadUserByUsernamereturning User Details.
5. When DaoAuthenticationProvider receives UserDetailService, it will perform password comparison, and finally encapsulate it into Authenticationan object and return it.
6. The last step in the figure is to save the logged-in user data.

After the above analysis, we probably know what UserDetailService is for. It simply obtains user data. Then we can implement this interface by customizing and rewrite the loadUserByUsername method to query users from the specified database.

We simulated user information in springSecurity before, we commented out the previous simulated data, we first implemented the interface, returned a user in loadUserByUsername (equivalent to already found), and then changed it to database query.

Comment out the simulated data in the springSecurity configuration file

// 配置模拟用户信息
   /* @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
        return manager;
    }*/

Implement the interface in the service package

@Service 
public class SpringDataUserDetailsService implements UserDetailsService {
    
     
    @Override 
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
     
        //登录账号 
        System.out.println("username="+username); 
        //根据账号去数据库查询... 
        //这里暂时使用静态数据 
        UserDetails userDetails = 
User.withUsername(username).password("123").authorities("p1").build(); 
        return userDetails; 
    } 
}

Test
Insert a breakpoint at return userDeatils and run in Debug mode.

4. Connect to the database

The first step to connect to the database is to have a database (talking is equivalent to farting)
1. Creating a table
is very simple, just build it yourself.
insert image description here
2. Introduce dependencies. The pom.xml above has introduced all the dependencies required by the project.
3. Configure the database information in application.properties.
The above configuration file has been configured and will not be repeated.
4. Define the entity class

@Data
public class UserDto {
    
    
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

5. Define the permanent layer Dao.
Because it is easy to query data, you can directly write methods in Dao. However, considering that Mybatis can be integrated in the future, I use a three-tier architecture to query. By the way, I also paste the entity classes and methods required by the authorization table. go in.
insert image description here
UserDao.java

@Repository
public interface UserDao {
    
    
    // 根据账户查询
    public UserDto getUserByUsername(String username);

    // 根据id查询权限
    public List<String> findPermissionByUserId(String userId);
}

UserDaoImpl.java

@Repository
public class UserDaoImpl implements UserDao {
    
    
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public UserDto getUserByUsername(String username) {
    
    
        String sql = "select id,username,password,fullname from t_user where username = ?";
        List<UserDto> list = jdbcTemplate.query(sql, new Object[]{
    
    username}, new BeanPropertyRowMapper<>(UserDto.class));
        if (list == null) {
    
    
            return null;
        }
        return list.get(0);
    }

    @Override
    public List<String> findPermissionByUserId(String userId) {
    
    
        // 根据用户id查询到其所拥有的权限信息
        String sql = "select * from t_permission where id in \n" +
                "(select permission_id from t_role_permission where role_id in \n" +
                "(select role_id from t_user_role where user_id = ?))";
        List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{
    
    userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
        // 从权限信息中剥离出权限字段
        List<String> permissions = new ArrayList<String>(list.size());
        list.iterator().forEachRemaining(c->permissions.add(c.getCode()));
        return permissions;
    }
}

UserDaoService.java

@Service
public interface UserDaoService {
    
    
    // 根据账户查询
    public UserDto getUserByUsername(String username);

    // 根据id查询权限
    public List<String> findPermissionByUserId(String userId);
}

UserDaoSerivceImpl.java

@Service
public class UserDaoServiceImpl implements UserDaoService {
    
    
    @Autowired
    UserDao userDao;

    @Override
    public UserDto getUserByUsername(String username) {
    
    
        return userDao.getUserByUsername(username);
    }

    @Override
    public List<String> findPermissionByUserId(String userId) {
    
    
        return userDao.findPermissionByUserId(userId);
    }
}

6. Modify the custom UserDetailService
to delete the original simulated data, and access the data directly from the database. Note that we have not established related tables in the database because of the permission information, so the permission temporarily uses static data, that is,.authorities("p1")

@Service 
public class SpringDataUserDetailsService implements UserDetailsService {
    
     
 
    @Autowired
    UserDaoService userDaoService; 
 
    @Override 
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        //根据账号去数据库查询
        UserDto userByUsername = userDaoService.getUserByUsername(s); 
        if(userByUsername == null){
    
    
            return null; 
        }
        UserDetails userDetails =	User.withUsername(userByUsername .getFullname()).password(userByUsername .getPassword()).authorities("p1").build(); 
        return userDetails; 
    }
}

7. Test
Try to log in with data information, there are too many contents and no maps.

5. Password encryption PasswordEncoder

DaoAuthenticationiProvider performs password comparison through the matches method in the PasswordEncoder interface. SpringSecurity provides many built-in PasswordEncoders, which can be declared directly in its configuration file.
In the previous springSecurity configuration, the following password encryption method is used

    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }

This is a string matching method for comparison without encryption, just a simple comparison of passwords.

Use BCrypePasswordEncoderfor password encryption
1. Generally, the password is submitted to the background during registration and then encrypted and stored in the database, but we do not have the registration function, so we add a test class to convert the stored password into BCrypePasswordEncoder format, and then manually save it to the database .

Create a test method in test

@RunWith(SpringRunner.class)
public class BcryTest {
    
    
    @Test
    public void test1() {
    
    
        // 对原始密码进行加密
        String password = BCrypt.hashpw("123", BCrypt.gensalt());
        System.out.println(password);
    }
}

insert image description here
Just save the above password to our data.
insert image description here
2. Configure the encryption method
and configure it in the security configuration file as follows

@Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }

3. Test whether you can log in

6. Customize the authentication page

In the past, we used the login and exit interfaces that come with springSecurity. Next, we customize the login interface.
1. Create a login page
Created under the specified address configured by the view resolver, webapp is the same level directory as resource.
insert image description here

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户登录</title>
</head>
<body>
    <form action="login" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

2. Configure the address in webConfig.java,
which should have been pasted above

// 默认url根路径跳转到mvc解析地址下的login
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");
        registry.addViewController("/login-view").setViewName("login");
    }

3. Configure security.
In order to prevent the occurrence of CSRF (Cross-siterequestforgery) cross-site request forgery, springsecurity restricts most methods except get. We can block CSRF.

// 配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable() //屏蔽CSRF控制,即springsecurity不再限制CSRF
                .authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated() // 拦截/r/**请求
                .anyRequest().permitAll() // 其他请求正常方行
                .and()
                .formLogin()  // 允许表单登录
                .loginPage("/login-view") // 指定自己的登录页面,已经将 ‘/’ 路径跳到试图解析器配置路径下的 ‘login’
                .loginProcessingUrl("/login") // 用户名密码提交的目的地址
                .successForwardUrl("/login-success") // 指定登录成功后跳转的url
                .permitAll() // .formLogin().permitAll()允许所有用户访问登录页面
                .and()
                .sessionManagement()  // 会话控制
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login-view?logout");
    }

7. Conversation function

The session function is to show who is accessing the resource when we access the resource. We define two resources in the controller, r1 and r2.
As mentioned in the analysis of the authentication process, the last step will store the logged-in user data in the SecurityContextHolder context, through which we can access user information, showing that the user has accessed the resource.

The modified controller

@RestController
public class loginController {
    
    

    @RequestMapping(value = "/login-success", produces = {
    
    "text/plain;charset=utf-8"})
    public String loginSuccess() {
    
    
        // 获取用户信息显示出来
        return getUsername()+"登录成功";
    }

    @RequestMapping(value = "/r/r1", produces = {
    
    "text/plain;charset=utf-8"})
    public String r1() {
    
    
        return getUsername()+"访问资源r1";
    }

    @RequestMapping(value = "/r/r2", produces = {
    
    "text/plain;charset=utf-8"})
    public String r2() {
    
    
        return getUsername()+"访问资源r2";
    }

    // 获取用户姓名
    private String getUsername() {
    
    
        // Spring Security在认证完成后通过
        // SecurityContextHolder.getContext().setAuthentication()方法将Authentication保存在上下文
        // 所以我们可以通过访问上下文中的Authentication来获取用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        String username = null;
        if(principal instanceof org.springframework.security.core.userdetails.UserDetails) {
    
    
            username = ((UserDetails) principal).getUsername();
        } else {
    
    
            username = principal.toString();
        }
        return username;
    }
}

Relevant user information will be displayed when accessing resources again.
Test
Log in to Zhangsan's account and access resource r1
insert image description here
insert image description here

8. Authorization

1. Create a table
The authorization function needs to access the user's permission information. The first step is to filter out which role the current user belongs to from the table, and then which permissions the role has.
角色表t_role
insert image description here
用户角色表t_user_role
insert image description here
权限表t_permission
insert image description here
角色权限表t_role_permission
insert image description here
2. Create a query permission method in the dao interface.
The relevant method code has been pasted in the fourth part when connecting data to create a three-tier architecture (too lazy)
3. Change UserDetailService to dynamically obtain permission

@Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        UserDto userByUsername = userDaoService.getUserByUsername(s);
        if (userByUsername  == null) {
    
    
            return null;
        }
        // 查询权限字段信息
        String userId = userByUsername.getId();
        List<String> permissions = userDaoService.findPermissionByUserId(userId);
        // 因为返回的UserDetails创建权限是变长度的String数组,转换一下
        String[] per = new String[permissions.size()];
        permissions.toArray(per);
        // 模拟查询数据库
        UserDetails p1 = User.withUsername(userByUsername.getUsername()).password(userByUsername.getPassword()).authorities(per).build();
        return p1;
    }

9. Integrate Mybatis

Haven't written yet, will definitely next time

The blog was written when I was learning the dark horse programmer SpringSecurity, if you don’t know it, you can leave a message in the comment area to communicate (manually bared teeth)

Dark horse programmer SpringSecurity

Guess you like

Origin blog.csdn.net/qq_44660367/article/details/109551481