Spring 系列 (4) - 在 Spring Boot 项目里使用 Spring Security、Druid 和 MyBatis


Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。Spring Boot 给 Spring Security 提供了自动化配置方案 (spring-boot-starter-security),可以零配置使用 Spring Security。

Druid 是阿里巴巴推出的一款开源的高性能数据源产品,Druid 支持所有 JDBC 兼容的数据库,包括 Oracle、MySQL、SQL Server 和 H2 等等。Druid 不仅结合了 C3P0、DBCP 和 PROXOOL 等数据源产品的优点,同时还加入了强大的监控功能。通过 Druid 的监控功能,可以实时观察数据库连接池和 SQL 的运行情况,帮助用户及时排查出系统中存在的问题。

MyBatis 是一个半自动化的 ORM 框架,所谓半自动化是指 MyBatis 只支持将数据库查出的数据映射到 POJO 实体类上,而实体到数据库的映射则需要我们自己编写 SQL 语句实现,相较于Hibernate 这种完全自动化的框架,Mybatis 更加灵活,我们可以根据自身的需求编写 SQL 语句来实现复杂的数据库操作。

MyBatis 也开发了一套基于 Spring Boot 模式的 starter:mybatis-spring-boot-starter。


1. 开发环境

    Windows版本:Windows 10 Home (20H2)   
    IntelliJ IDEA (https://www.jetbrains.com/idea/download/):Community Edition for Windows 2020.1.4
    Apache Maven (https://maven.apache.org/):3.8.1

    注:Spring 开发环境的搭建,可以参考 “ ”。


2. 创建 Spring Boot 基础项目

    项目实例名称:SpringbootExample04
    Spring Boot 版本:2.6.6

    创建步骤:

        (1) 创建 Maven 项目实例 SpringbootExample04;
        (2) Spring Boot Web 配置;
        (3) 导入 Thymeleaf 依赖包;
        (4) 配置静态资源(jQuery、Bootstrap、Images);
        
    具体操作请参考 “” 里的项目实例 SpringbootExample02。

    SpringbootExample04 和 SpringbootExample02 相比,SpringbootExample04 不包含 Thymeleaf 模版文件(templates/*.htm l)和国际化。


3. 配置 Spring security

    1)  修改 pom.xml,导入 Spring security 依赖包

 1         
 2             ...
 3             
 4                 ...
 5 
 6                 
 7                 
 8                     org.springframework.boot
 9                     spring-boot-starter-security
10                 
11 
12                 ...
13             
14 
15             ...
16         


        在IDE中项目列表 -> SpringbootExample04 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 修改 src/main/resources/application.properties 文件,添加如下配置

        # security
        spring.security.user.name=admin
        spring.security.user.password=123456
        spring.security.user.roles=admin

        运行并访问 http://localhost:9090/test,自动跳转到 http://localhost:9090/login (Spring security 的默认页面),输入上面的用户名和密码登录,登录后跳转到 http://localhost:9090/test。

  注:如果不在 application.properties 里配置 admin, 默认用户名是 user,密码在项目启动时输出在控制台,格式如下:

    Using generated security password: 61c29bfd-0f2b-4a35-a27c-c569f9b6f02d

    This generated password is for development use only. Your security configuration must be updated before running your application in production.

  配置 admin 后,默认用户 user 将失效。


    3) 创建 src/main/resources/templates/common.html 文件

 1         
"header(var)"> 2 "UTF-8"> 3 "</span><span style="color: rgba(128, 0, 0, 1)">${var}</span><span style="color: rgba(128, 0, 0, 1)">"</span>>Title 4 "stylesheet" th:href="@{/lib/bootstrap-4.2.1-dist/css/bootstrap.min.css}" href="/lib/bootstrap-4.2.1-dist/css/bootstrap.min.css"> 5 <script language="javascript" th:src="@{/lib/jquery/jquery-3.6.0.min.js}" src="/lib/jquery/jquery-3.6.0.min.js"></script> 6 <script language="javascript" th:src="@{/lib/bootstrap-4.2.1-dist/js/bootstrap.min.js}" src="/lib/bootstrap-4.2.1-dist/js/bootstrap.min.js"></script> 7
8 9 19 20
"content-footer(var)" class="container" id="content-footer-id"> 21

"${var}">Content Footer

22


    4) 创建 src/main/resources/templates/login.html 文件

 1         
 2         "en" xmlns:th="http://www.thymeleaf.org">
 3         "common::header(var='Login')">
 4         
 5         
 6         
"common::content-header">
7 8
class="container" id="content" th:style="'min-height: 480px;'"> 9

Login

10 11

 

12
class="alert alert-info" role="alert" th:text="${message}" th:if ="${message} != null">
13 14
"POST" action="/login/post"> 15
class="form-group"> 16 17 "text" class="form-control" id="username" name="username" value="admin" /> 18
19
class="form-group"> 20 21 "password" class="form-control" id="password" name="password" value="123456" /> 22
23 24
25 26
27 28
"common::content-footer(var='Copyright © 2020')">
29 30 <script type="text/javascript"> 31 $(document).ready(function(){ 32 console.log("jQuery is running"); 33 }); 34 </script> 35 36


    5) 创建 src/main/resources/templates/home.html 文件

 1         
 2         "en" xmlns:th="http://www.thymeleaf.org">
 3         "common::header(var='Home')">
 4         
 5         
 6             
"common::content-header">
7 8
class="container" id="content" th:style="'min-height: 480px;'"> 9

Home Page

10 11

 

12
class="alert alert-info" role="alert" th:text="${message}" th:if ="${message} != null">
13
14 15
"common::content-footer(var='Copyright © 2020')">
16 17 <script type="text/javascript"> 18 $(document).ready(function(){ 19 console.log("jQuery is running"); 20 }); 21 </script> 22 23


    6) 修改 src/main/java/com/example/controller/IndexController.java 文件

 1         package com.example.controller;
 2 
 3         import org.springframework.ui.Model;
 4         import org.springframework.stereotype.Controller;
 5         import org.springframework.web.bind.annotation.RequestMapping;
 6         import org.springframework.web.bind.annotation.ResponseBody;
 7 
 8         @Controller
 9         public class IndexController {
10             @ResponseBody
11             @RequestMapping("/test")
12             public String test() {
13                 return "Test Page";
14             }
15 
16             @RequestMapping("/home")
17             public String home(Model model) {
18                 model.addAttribute("message", "Spring Boot Login Demo");
19                 return "home";
20             }
21 
22             @RequestMapping("/login")
23             public String login() {
24                 return "login";
25             }
26         }


    7) 创建 src/main/java/com/example/config/WebSecurityConfig.java 文件

 1         package com.example.config;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;
 4         import org.springframework.context.annotation.Configuration;
 5         import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 6         import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 7         import org.springframework.security.core.userdetails.UserDetailsService;
 8         import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 9 
10         @Configuration
11         public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
12             @Autowired
13             private UserDetailsService userDetailsService;
14 
15             @Override
16             protected void configure(AuthenticationManagerBuilder auth) throws Exception {
17                 auth.userDetailsService(userDetailsService);
18             }
19 
20             @Override
21             protected void configure(HttpSecurity http) throws Exception {
22                 // 配置认证
23                 http.authorizeRequests()
24                     .antMatchers("/lib/**").permitAll()
25                     .antMatchers("/images/**").permitAll()
26                     .antMatchers("/error").permitAll()
27                     .antMatchers("/**/*.html").permitAll()
28                     .anyRequest().authenticated()
29 
30                     .and()
31                     .formLogin()
32                     .loginPage("/login") // 自定义登录页面
33                     .loginProcessingUrl("/login/post") // 登录访问路径
34                     .defaultSuccessUrl("/home").permitAll()    // 登陆成功之后跳转地址
35 
36                     .and()
37                     .csrf().disable(); // 关闭 csrf 保护功能,默认是开启的
38 
39             }
40 
41         }


        运行并访问 http://localhost:9090/home,自动跳转到 http://localhost:9090/login (Spring security 的默认页面),输入上面的用户名和密码,登录后跳转到 http://localhost:9090/home。


4. 配置 Druid

    1) 修改 pom.xml,导入 MariaDB、JDBC、Druid 依赖包

 1         
 2             ...
 3             
 4                 ...
 5 
 6                 
 7                 
 8                     org.mariadb.jdbc
 9                     mariadb-java-client
10                 
11                 
12                 
13                     org.springframework.boot
14                     spring-boot-starter-data-jdbc
15                 
16                 
17                 
18                     com.alibaba
19                     druid
20                     1.2.8
21                 
22 
23                 ...
24             
25 
26             ...
27         


        在IDE中项目列表 -> SpringbootExample04 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 创建 src/main/java/com/example/config/DruidDataSourceConfig.java 文件

 1         package com.example.config;
 2 
 3         import javax.sql.DataSource;
 4         import java.sql.SQLException;
 5 
 6         import com.alibaba.druid.pool.DruidDataSource;
 7         import org.springframework.boot.context.properties.ConfigurationProperties;
 8         import org.springframework.context.annotation.Bean;
 9         import org.springframework.context.annotation.Configuration;
10         import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
11 
12         @Configuration
13         public class DruidDataSourceConfig implements WebMvcConfigurer {
14 
15             @ConfigurationProperties("spring.datasource")
16             @Bean
17             public DataSource dataSource() throws SQLException {
18                 DruidDataSource druidDataSource = new DruidDataSource();
19                 return druidDataSource;
20             }
21         }


    3) 修改 src/main/resources/application.properties 文件,添加如下代码:

1         # 数据源连接信息
2         spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
3         spring.datasource.url=jdbc:mysql://localhost:3306/springboot_example
4         spring.datasource.username=root
5         spring.datasource.password=123456


    4) 修改 src/main/java/com/example/controller/IndexController.java 文件

 1         package com.example.controller;
 2 
 3         import java.sql.SQLException;
 4         import javax.sql.DataSource;
 5         import org.springframework.beans.factory.annotation.Autowired;
 6 
 7         import org.springframework.ui.Model;
 8         import org.springframework.stereotype.Controller;
 9         import org.springframework.web.bind.annotation.RequestMapping;
10         import org.springframework.web.bind.annotation.ResponseBody;
11 
12         @Controller
13         public class IndexController {
14             @Autowired
15             DataSource dataSource;
16 
17             ...
18  
19             @ResponseBody
20             @RequestMapping("/druid")
21             public String druid() throws SQLException {
22                 String str = "Druid Page

"; 23 str += "DataSource:" + dataSource.getClass() + "
"; 24 str += "DataSource Connection:" + dataSource.getConnection() + "
"; 25 return str; 26 } 27 }


        运行并访问 http://localhost:9090/druid,登录后,页面显示类似如下内容就表示 Druid 设置成功。

            Druid Page

            DataSource:class com.alibaba.druid.pool.DruidDataSource
            DataSource Connection:org.mariadb.jdbc.MariaDbConnection@3fc45e93

        注:如何开启 Druid 内置监控页面,请参考 “” 的整合 Druid 数据源 -> https://www.cnblogs.com/tkuang/p/16180160.html 。


5. 配置数据库(MariaDB)

    1) XAMPP for Windows

        https://www.apachefriends.org/download.html
        
        本文安装版本 7.4.25,用到 XAMPP 的如下功能:

            + Apache 2.4.51
            + MariaDB 10.4.21 (MySQL的分支)
            + PHP 7.4.25 (VC15 X86 64bit thread safe) + PEAR
            + phpMyAdmin 5.1.1

    2) 创建数据库 springboot_example 和 user 表,SQL 脚本如下

 1         CREATE TABLE `user` (
 2             `id` int(11) NOT NULL,
 3             `username` varchar(50) NOT NULL,
 4             `password` varchar(255) DEFAULT NULL,
 5             `age` int(11) DEFAULT NULL,
 6             `createtime` timestamp NULL DEFAULT NULL
 7         ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 8 
 9         INSERT INTO `user` (`id`, `username`, `password`, `age`, `createtime`) VALUES
10         (1, 'admin', 'admin', 21, '2020-01-01 01:01:01'),(2, 'test', 'test', 28, '2020-02-02 02:02:02');


    3)创建 src/main/java/com/example/entity/User.java 文件

 1         package com.example.entity;
 2 
 3         import java.util.Date;
 4 
 5         public class User {
 6             private Integer id;
 7             private String username;
 8             private String password;
 9             private Integer age;
10             private Date createtime;
11 
12             public User() {
13 
14             }
15 
16             public Integer getId() {
17                 return id;
18             }
19             public void setId(Integer id) {
20                 this.id = id;
21             }
22 
23             public String getUsername() {
24                 return username;
25             }
26             public void setName(String username) {
27                 this.username = username;
28             }
29 
30             public String getPassword() {
31                 return password;
32             }
33             public void setPassword(String password) {
34                 this.password = password;
35             }
36 
37             public Integer getAge() {
38                 return age;
39             }
40             public void setAge(Integer age) {
41                 this.age = age;
42             }
43 
44             public Date getCreatetime() {
45                 return createtime;
46             }
47             public void setCreatetime(Date createtime) {
48                 this.createtime = createtime;
49             }
50 
51             @Override
52             public String toString() {
53                 return "User {" +
54                         "username = " + username +
55                         ", password = " + password +
56                         ", age = " + age +
57                         ", createtime = " + createtime +
58                         '}';
59             }
60         }

6. 配置 MyBatis

    1)修改 pom.xml,导入 MyBatis 依赖包

 1         
 2             ...
 3             
 4                 ...
 5 
 6                 
 7                 
 8                     org.mybatis.spring.boot
 9                     mybatis-spring-boot-starter
10                     2.2.0
11                 
12 
13                 ...
14             
15 
16             ...
17         


        在IDE中项目列表 -> SpringbootExample04 -> 点击鼠标右键 -> Maven -> Reload Project

    2) 修改 src/main/resources/application.properties 文件,添加如下代码:

        # mybatis
        mybatis.type-aliases-package=com.example
        mybatis.configuration.map-underscore-to-camel-case=true

    3)创建 src/main/java/com/example/mapper/UserMapper.java 文件

 1         package com.example.mapper;
 2 
 3         import org.apache.ibatis.annotations.Mapper;
 4         import org.apache.ibatis.annotations.Select;
 5         import org.apache.ibatis.annotations.Param;
 6 
 7         import com.example.entity.User;
 8 
 9         @Mapper
10         public interface UserMapper {
11             
12             @Select("SELECT * FROM user WHERE username = #{username}")
13             User getUserByName(@Param("username") String username);
14 
15         }


    4) 修改 src/main/java/com/example/controller/IndexController.java 文件

 1         package com.example.controller;
 2 
 3         import java.sql.SQLException;
 4         import javax.sql.DataSource;
 5         import org.springframework.beans.factory.annotation.Autowired;
 6 
 7         import org.springframework.ui.Model;
 8         import org.springframework.stereotype.Controller;
 9         import org.springframework.web.bind.annotation.RequestMapping;
10         import org.springframework.web.bind.annotation.ResponseBody;
11 
12         import com.example.entity.User;
13         import com.example.mapper.UserMapper;
14 
15         @Controller
16         public class IndexController {
17             @Autowired
18             DataSource dataSource;
19             @Autowired
20             private UserMapper userMapper;
21 
22             ...
23  
24             @ResponseBody
25             @RequestMapping("/mybatis")
26             public String mybatis() throws SQLException {
27                 User user = userMapper.getUserByName("test");
28                 String str = "MyBatis Page

"; 29 str += user + "
"; 30 return str; 31 } 32 }


        运行并访问 http://localhost:9090/mybatis,登录后,页面显示类似如下内容就表示 MyBatis 设置成功。

            MyBatis Page

            User {username = test, password = test, age = 21, createtime = Wed Jan 01 01:01:01 CST 2020}


7.  把 MyBatis 用户数据整合到 Security 的用户验证

    1) 创建 src/main/java/com/example/service/MyUserDetailsService.java 文件

 1         package com.example.service;
 2 
 3         import java.util.List;
 4         import java.util.ArrayList;
 5 
 6         import org.springframework.beans.factory.annotation.Autowired;
 7         import org.springframework.security.core.userdetails.User;
 8         import org.springframework.security.core.userdetails.UserDetails;
 9         import org.springframework.security.core.userdetails.UserDetailsService;
10         import org.springframework.security.core.userdetails.UsernameNotFoundException;
11         import org.springframework.security.core.GrantedAuthority;
12 
13         import com.example.mapper.UserMapper;
14 
15         public class MyUserDetailsService implements UserDetailsService {
16             @Autowired
17             private UserMapper userMapper;
18 
19             @Override
20             public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
21 
22                 com.example.entity.User user = userMapper.getUserByName(username);
23 
24                 List authorities = new ArrayList<>();
25 
26                 User userDetails= new User(user.getUsername(), user.getPassword(), authorities);
27                 return userDetails;
28             }
29         }


    2) 修改 src/main/java/com/example/config/WebSecurityConfig.java 文件

 1         package com.example.config;
 2 
 3         import org.springframework.beans.factory.annotation.Autowired;
 4         import org.springframework.context.annotation.Configuration;
 5         import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 6         import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 7         //import org.springframework.security.core.userdetails.UserDetailsService;
 8         import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 9 
10         import org.springframework.context.annotation.Bean;
11         import org.springframework.security.crypto.password.NoOpPasswordEncoder;
12         import org.springframework.security.crypto.password.PasswordEncoder;
13         import com.example.service.MyUserDetailsService;
14 
15         @Configuration
16         public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
17             @Autowired
18             //private UserDetailsService userDetailsService;
19             private MyUserDetailsService userDetailsService;
20 
21             // 数据库用明文密码,所以需要 NoOpPasswordEncoder 编码器
22             @Bean
23             public PasswordEncoder passwordEncoder(){
24                 return NoOpPasswordEncoder.getInstance();
25             }
26 
27             @Override
28             protected void configure(AuthenticationManagerBuilder auth) throws Exception {
29                 //auth.userDetailsService(userDetailsService);
30                 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
31             }
32 
33             ...
34 
35         }


        运行并访问 http://localhost:9090/test,自动跳转到 http://localhost:9090/login。注意:这里要输入数据库里的用户名和密码(admin/admin 或 test/test),登录成功后显示 http://localhost:9090/test 页面。