【springboot】學習4:整合JDBC、整合druid、整合mybatis、整合 SpringSecurity

nickkkkkkkkk發表於2020-09-24

1、SpringBoot整合JDBC

1.1、SpringData簡介

對於資料訪問層,無論是 SQL(關係型資料庫) 還是 NOSQL(非關係型資料庫),Spring Boot 底層都是採用 Spring Data 的方式進行統一處理。

Spring Boot 底層都是採用 Spring Data 的方式進行統一處理各種資料庫,Spring Data 也是 Spring 中與 Spring Boot、Spring Cloud 等齊名的知名專案。

Sping Data 官網:https://spring.io/projects/spring-data

資料庫相關的啟動器 :可以參考官方文件:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter

1.2、建立測試專案測試資料來源

1、我去新建一個專案測試:springboot-data-jdbc ; 引入相應的模組!基礎模組
在這裡插入圖片描述
2、專案建好之後,發現自動幫我們匯入瞭如下的啟動器

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

3、編寫yaml配置檔案連線資料庫;

spring:
  datasource:
    username: root
    password: root
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver

4、配置完這一些東西后,我們就可以直接去使用了,因為SpringBoot已經預設幫我們進行了自動配置;去測試類測試一下

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入資料來源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下預設資料來源
        System.out.println(dataSource.getClass());
        //獲得連線
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);
        //關閉連線
        connection.close();
    }
}

結果:我們可以看到他預設給我們配置的資料來源為 : class com.zaxxer.hikari.HikariDataSource , 我們並沒有手動配置

我們來全域性搜尋一下,找到資料來源的所有自動配置都在 :DataSourceAutoConfiguration檔案:

@Import(
    {Hikari.class, Tomcat.class, Dbcp2.class, Generic.class, DataSourceJmxConfiguration.class}
)
protected static class PooledDataSourceConfiguration {
    protected PooledDataSourceConfiguration() {
    }
}

這裡匯入的類都在 DataSourceConfiguration 配置類下,可以看出 Spring Boot 2.2.5 預設使用HikariDataSource 資料來源,而以前版本,如 Spring Boot 1.5 預設使用 org.apache.tomcat.jdbc.pool.DataSource 作為資料來源;

HikariDataSource 號稱 Java WEB 當前速度最快的資料來源,相比於傳統的 C3P0 、DBCP、Tomcat jdbc 等連線池更加優秀;
可以使用 spring.datasource.type 指定自定義的資料來源型別,值為 要使用的連線池實現的完全限定名。

關於資料來源我們並不做介紹,有了資料庫連線,顯然就可以 CRUD 運算元據庫了。但是我們需要先了解一個物件 JdbcTemplate

1.3、JDBCTemplate

1、有了資料來源(com.zaxxer.hikari.HikariDataSource),然後可以拿到資料庫連線(java.sql.Connection),有了連線,就可以使用原生的 JDBC 語句來運算元據庫;

2、即使不使用第三方第資料庫操作框架,如 MyBatis等,Spring 本身也對原生的JDBC 做了輕量級的封裝,即JdbcTemplate。

3、資料庫操作的所有 CRUD 方法都在 JdbcTemplate 中。

4、Spring Boot 不僅提供了預設的資料來源,同時預設已經配置好了 JdbcTemplate 放在了容器中,程式設計師只需自己注入即可使用

5、JdbcTemplate 的自動配置是依賴 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 類

JdbcTemplate主要提供以下幾類方法

  • execute方法:可以用於執行任何SQL語句,一般用於執行DDL語句;
  • update方法及batchUpdate方法:update方法用於執行新增、修改、刪除等語句;
  • batchUpdate方法用於執行批處理相關語句;
  • query方法及queryForXXX方法:用於執行查詢相關語句;
  • call方法:用於執行儲存過程、函式相關語句。

1.4、測試

編寫一個Controller,注入 jdbcTemplate,編寫測試方法進行訪問測試;

@RestController
@RequestMapping("/jdbc")
public class JdbcController {

    /**
     * Spring Boot 預設提供了資料來源,預設提供了 org.springframework.jdbc.core.JdbcTemplate
     * JdbcTemplate 中會自己注入資料來源,用於簡化 JDBC操作
     * 還能避免一些常見的錯誤,使用起來也不用再自己來關閉資料庫連線
     */
    @Autowired
    JdbcTemplate jdbcTemplate;

    //查詢employee表中所有資料
    //List 中的1個 Map 對應資料庫的 1行資料
    //Map 中的 key 對應資料庫的欄位名,value 對應資料庫的欄位值
    @GetMapping("/list")
    public List<Map<String, Object>> userList(){
        String sql = "select * from employee";
        List<Map<String, Object>> maps = jdbcTemplate.queryForList(sql);
        return maps;
    }
    
    //新增一個使用者
    @GetMapping("/add")
    public String addUser(){
        //插入語句,注意時間問題
        String sql = "insert into employee(last_name, email,gender,department,birth)" +
                " values ('狂神說','24736743@qq.com',1,101,'"+ new Date().toLocaleString() +"')";
        jdbcTemplate.update(sql);
        //查詢
        return "addOk";
    }

    //修改使用者資訊
    @GetMapping("/update/{id}")
    public String updateUser(@PathVariable("id") int id){
        //插入語句
        String sql = "update employee set last_name=?,email=? where id="+id;
        //資料
        Object[] objects = new Object[2];
        objects[0] = "test";
        objects[1] = "123456@qq.com";
        jdbcTemplate.update(sql,objects);
        //查詢
        return "updateOk";
    }

    //刪除使用者
    @GetMapping("/delete/{id}")
    public String delUser(@PathVariable("id") int id){
        //插入語句
        String sql = "delete from employee where id=?";
        jdbcTemplate.update(sql,id);
        //查詢
        return "deleteOk";
    }
    
}

測試請求,結果正常;

到此,CURD的基本操作,使用 JDBC 就搞定了。
https://docs.spring.io/spring-boot/docs/2.0.4.RELEASE/reference/htmlsingle/#using-boot-starter

1.5、原理探究 :

org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration 資料來源配置類作用 :根據邏輯判斷之後,新增資料來源;

SpringBoot預設支援以下資料來源:
com.zaxxer.hikari.HikariDataSource (Spring Boot 2.0 以上,預設使用此資料來源)

org.apache.tomcat.jdbc.pool.DataSource

org.apache.commons.dbcp2.BasicDataSource

可以使用 spring.datasource.type 指定自定義的資料來源型別,值為 要使用的連線池實現的完全限定名。預設情況下,它是從類路徑自動檢測的。

    @Configuration
    @ConditionalOnMissingBean({DataSource.class})
    @ConditionalOnProperty(
        name = {"spring.datasource.type"}
    )
    static class Generic {
        Generic() {
        }

        @Bean
        public DataSource dataSource(DataSourceProperties properties) {
            return properties.initializeDataSourceBuilder().build();
        }
    }

2、SpringBoot整合Druid

2.1、Druid簡介

Java程式很大一部分要運算元據庫,為了提高效能運算元據庫的時候,又不得不使用資料庫連線池。

Druid 是阿里巴巴開源平臺上一個資料庫連線池實現,結合了 C3P0、DBCP 等 DB 池的優點,同時加入了日誌監控。

Druid 可以很好的監控 DB 池連線和 SQL 的執行情況,天生就是針對監控而生的 DB 連線池。

Druid已經在阿里巴巴部署了超過600個應用,經過一年多生產環境大規模部署的嚴苛考驗。

Spring Boot 2.0 以上預設使用 Hikari 資料來源,可以說 Hikari 與 Driud 都是當前 Java Web 上最優秀的資料來源,我們來重點介紹 Spring Boot 如何整合 Druid 資料來源,如何實現資料庫監控。

Github地址:https://github.com/alibaba/druid/

com.alibaba.druid.pool.DruidDataSource 基本配置引數如下:
https://www.cnblogs.com/wuyun-blog/p/5679073.html

2.2、配置資料來源

1、新增上 Druid 資料來源依賴。

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>

2、切換資料來源;之前已經說過 Spring Boot 2.0 以上預設使用 com.zaxxer.hikari.HikariDataSource 資料來源,但可以 通過 spring.datasource.type 指定資料來源。

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource # 自定義資料來源

3、資料來源切換之後,在測試類中注入 DataSource,然後獲取到它,輸出一看便知是否成功切換;

在這裡插入圖片描述

4、切換成功!既然切換成功,就可以設定資料來源連線初始化大小、最大連線數、等待時間、最小連線數 等設定項;可以檢視原始碼

spring:
  datasource:
    username: root
    password: root
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 預設是不注入這些屬性值的,需要自己繫結
    #druid 資料來源專有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置監控統計攔截的filters,stat:監控統計、log4j:日誌記錄、wall:防禦sql注入
    #如果允許時報錯  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #則匯入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

5、匯入Log4j 的依賴
如果不匯入就會報錯!!!因為上面的配置檔案配置過濾器使用到了log4j

        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

6、現在需要程式設計師自己為 DruidDataSource 繫結全域性配置檔案中的引數,再新增到容器中,而不再使用 Spring Boot 的自動生成了;我們需要 自己新增 DruidDataSource 元件到容器中,並繫結屬性;

自定義資料來源。可以 return new MyDruidDataSource();

@Configuration
public class DruidConfig {

    /*
       將自定義的 Druid資料來源新增到容器中,不再讓 Spring Boot 自動建立
       繫結全域性配置檔案中的 druid 資料來源屬性到 com.alibaba.druid.pool.DruidDataSource從而讓它們生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是將 全域性配置檔案中
       字首為 spring.datasource的屬性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名引數中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

}

7、去測試類中測試一下;看是否成功!

@SpringBootTest
class SpringbootDataJdbcApplicationTests {

    //DI注入資料來源
    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {
        //看一下預設資料來源
        System.out.println(dataSource.getClass());
        //獲得連線
        Connection connection =   dataSource.getConnection();
        System.out.println(connection);

        DruidDataSource druidDataSource = (DruidDataSource) dataSource;
        System.out.println("druidDataSource 資料來源最大連線數:" + druidDataSource.getMaxActive());
        System.out.println("druidDataSource 資料來源初始化連線數:" + druidDataSource.getInitialSize());

        //關閉連線
        connection.close();
    }
}

輸出結果 :可見配置引數已經生效!


2.3、配置Druid資料來源監控

Druid 資料來源具有監控的功能,並提供了一個 web 介面方便使用者檢視,類似安裝 路由器 時,人家也提供了一個預設的 web 頁面。

所以第一步需要設定 Druid 的後臺管理頁面,比如 登入賬號、密碼 等;配置後臺管理;

在DruidConfig中新增.

    //後臺監控:web.xml  ServletRegistrationBean
    //因為springboot 內建了servlet容器,所以沒有web.xml,替代方法:ServletRegistrationBean
    //想要用什麼,就new 什麼 然後配置進去,再放進容器.
    @Bean
    public ServletRegistrationBean  statViewServlet(){
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        //後臺需要有人登陸,賬號密碼配置
        HashMap<String, String> initParameters = new HashMap<>();
        //增加配置,登陸的key是固定的
        initParameters.put("loginUsername", "root");
        initParameters.put("loginPassword", "123456");

        //允許誰可以訪問,為空,表示所有人.
        initParameters.put("allow" , "");

        //禁止誰能訪問
//        initParameters.put("deny","192.168.10.20");

        //設定初始化引數
        bean.setInitParameters(initParameters);
        return bean;
    }

配置完畢後,我們可以選擇訪問 :http://localhost:8080/druid/login.html

在這裡插入圖片描述
進入之後

在這裡插入圖片描述


配置 Druid web 監控 filter 過濾器

//配置 Druid 監控 之  web 監控的 filter
//WebStatFilter:用於配置Web和Druid資料來源之間的管理關聯監控統計
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:設定哪些請求進行過濾排除掉,從而不進行統計
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示過濾所有請求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

平時在工作中,按需求進行配置即可,主要用作監控

3、SpringBoot 整合mybatis

3.1、匯入mybatis所需要的依賴

        <!-- 引入 myBatis,這是 MyBatis官方提供的適配 Spring Boot 的,而不是Spring Boot自己的-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

3.2、配置資料庫連線資訊

spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 預設是不注入這些屬性值的,需要自己繫結
    #druid 資料來源專有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置監控統計攔截的filters,stat:監控統計、log4j:日誌記錄、wall:防禦sql注入
    #如果允許時報錯  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #則匯入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

3.3、我們這裡就是用預設的資料來源了;先去測試一下連線是否成功!

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootDemoMybatisApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    public void contextLoads() throws SQLException {

        System.out.println("資料來源>>>>>>" + dataSource.getClass());
        Connection connection = dataSource.getConnection();
        System.out.println("連線>>>>>>>>>" + connection);
        System.out.println("連線地址>>>>>" + connection.getMetaData().getURL());
        connection.close();

    }

}

檢視輸出結果,資料庫配置OK!


3.4、建立實體類

省略…

3.5、配置Mapper介面類

package com.wei.mapper;
import com.wei.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;

//這個註解表示,這是一個mybatis的mapper介面.
@Mapper
@Repository
public interface UserMapper {
    List<User> findAll();
    User findUserById(Integer id );
    int addUser(User user);
    int updateUser(User user);
    int deleteUser(Integer id );
}

3.6、對應Mapper對映檔案

<?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.kuang.mybatis.pojo.mapper.UserMapper">

    <select id="selectUser" resultType="User">
    select * from user
    </select>

    <select id="selectUserById" resultType="User">
    select * from user where id = #{id}
	</select>

    <insert id="addUser" parameterType="User">
    insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
	</insert>

    <update id="updateUser" parameterType="User">
    update user set name=#{name},pwd=#{pwd} where id = #{id}
	</update>

    <delete id="deleteUser" parameterType="int">
    delete from user where id = #{id}
	</delete>
</mapper>

3.7、maven配置資源過濾問題

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

3.8、SpringBoot 整合!

以前 MyBatis 未與 spring 整合時,配置資料來源、事務、連線資料庫的賬號、密碼等都是在 myBatis 核心配置檔案中進行的myBatis 與 spring 整合後,配置資料來源、事務、連線資料庫的賬號、密碼等就交由 spring 管理。因此,在這裡我們即使不使用mybatis配置檔案也完全ok!

既然已經提供了 myBatis 的對映配置檔案,自然要告訴 spring boot 這些檔案的位置

yaml 檔案再新增一些配置. 指定實體類所在包和mapper檔案的位置

mybatis:
  type-aliases-package: com.wei.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

已經說過 spring boot 官方並沒有提供 myBaits 的啟動器,是 myBatis 官方提供的開發包來適配的 spring boot,從 pom.xml 檔案中的依賴包名也能看出來,並非是以 spring-boot 開頭的;

同理上面全域性配置檔案中的這兩行配置也是以 mybatis 開頭 而非 spring 開頭也充分說明這些都是 myBatis 官方提供的

可以從 org.mybatis.spring.boot.autoconfigure.MybatisProperties 中檢視所有配置項

@ConfigurationProperties(
    prefix = "mybatis"
)
public class MybatisProperties {
    public static final String MYBATIS_PREFIX = "mybatis";
    private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private Class<?> typeAliasesSuperType;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Class<? extends LanguageDriver> defaultScriptingLanguageDriver;
    private Properties configurationProperties;
    @NestedConfigurationProperty
    private Configuration configuration;

也可以直接去檢視 官方文件

3.9、編寫controller

package com.wei.controller;

import com.wei.mapper.UserMapper;
import com.wei.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Date;
import java.util.List;

@Controller
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/findAll")
    @ResponseBody
    public List<User> findAll(){
        List<User> all = userMapper.findAll();
        return all;
    }


    @RequestMapping("/findUserById/{id}")
    @ResponseBody
    public User findUserById(@PathVariable("id") Integer id ){
        User user = userMapper.findUserById(id);
        return user;
    }
    @RequestMapping("/addUser")
    public String addUser(){
        User user = new User();
        user.setUsername("test");user.setSex("1");
        user.setBirthday(new Date());user.setAddress("changle");
        userMapper.addUser(user);
        return "redirect:/findAll";
    }
    @RequestMapping("/updateUser")
    public String updateUser(){
        User user = new User();
        user.setId(52);
        user.setUsername("test2");user.setSex("2");
        user.setBirthday(new Date());user.setAddress("changle2222");
        userMapper.updateUser(user);
        return "redirect:/findAll";
    }

    @RequestMapping("/deleteUser/{id}")
    public String deleteUser(@PathVariable("id") Integer id){
        userMapper.deleteUser(id);
        return "redirect:/findAll";
    }



}

3.10、啟動專案訪問進行測試!

步驟:

Mybatis整合包

mybatis-spring-boot-starter

1.匯入包 (pow.xml匯入座標)

2.配置檔案 (新增 XXXmapper.xml 對映檔案)

3.mybatis配置(application.yaml 中配置mybatis,1.實體類/2.mapper檔案)

4.編寫sql ( 在mapper 中編寫,或者使用註解)
5.service層呼叫dao層

6.controller呼叫service層

注:配置資料庫連線資訊(不變)

spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 預設是不注入這些屬性值的,需要自己繫結
    #druid 資料來源專有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置監控統計攔截的filters,stat:監控統計、log4j:日誌記錄、wall:防禦sql注入
    #如果允許時報錯  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #則匯入 log4j 依賴即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

4、SpringBoot:整合SpringSecurity

4.1、安全簡介

在 Web 開發中,安全一直是非常重要的一個方面。安全雖然屬於應用的非功能性需求,但是應該在應用開發的初期就考慮進來。如果在應用開發的後期才考慮安全的問題,就可能陷入一個兩難的境地:一方面,應用存在嚴重的安全漏洞,無法滿足使用者的要求,並可能造成使用者的隱私資料被攻擊者竊取;另一方面,應用的基本架構已經確定,要修復安全漏洞,可能需要對系統的架構做出比較重大的調整,因而需要更多的開發時間,影響應用的釋出程式。因此,從應用開發的第一天就應該把安全相關的因素考慮進來,並在整個應用的開發過程中。

市面上存在比較有名的:Shiro,Spring Security !

這裡需要闡述一下的是,每一個框架的出現都是為了解決某一問題而產生了,那麼Spring Security框架的出現是為了解決什麼問題呢?

首先我們看下它的官網介紹:Spring Security官網地址

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements

Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架。它實際上是保護基於spring的應用程式的標準。

Spring Security是一個框架,側重於為Java應用程式提供身份驗證和授權。與所有Spring專案一樣,Spring安全性的真正強大之處在於它可以輕鬆地擴充套件以滿足定製需求

從官網的介紹中可以知道這是一個許可權框架。想我們之前做專案是沒有使用框架是怎麼控制許可權的?對於許可權 一般會細分為功能許可權,訪問許可權,和選單許可權。程式碼會寫的非常的繁瑣,冗餘。

怎麼解決之前寫許可權程式碼繁瑣,冗餘的問題,一些主流框架就應運而生而Spring Scecurity就是其中的一種。

Spring 是一個非常流行和成功的 Java 應用開發框架。Spring Security 基於 Spring 框架,提供了一套 Web 應用安全性的完整解決方案。一般來說,Web 應用的安全性包括使用者認證(Authentication)和使用者授權(Authorization)兩個部分。使用者認證指的是驗證某個使用者是否為系統中的合法主體,也就是說使用者能否訪問該系統。使用者認證一般要求使用者提供使用者名稱和密碼。系統通過校驗使用者名稱和密碼來完成認證過程。使用者授權指的是驗證某個使用者是否有許可權執行某個操作。在一個系統中,不同使用者所具有的許可權是不同的。比如對一個檔案來說,有的使用者只能進行讀取,而有的使用者可以進行修改。一般來說,系統會為不同的使用者分配不同的角色,而每個角色則對應一系列的許可權。

對於上面提到的兩種應用情景,Spring Security 框架都有很好的支援。在使用者認證方面,Spring Security 框架支援主流的認證方式,包括 HTTP 基本認證、HTTP 表單驗證、HTTP 摘要認證、OpenID 和 LDAP 等。在使用者授權方面,Spring Security 提供了基於角色的訪問控制和訪問控制列表(Access Control List,ACL),可以對應用中的領域物件進行細粒度的控制。

4.2、實戰測試

4.2.1、實驗環境搭建

1、新建一個初始的springboot專案web模組,thymeleaf模組

2、匯入靜態資源

welcome.html
|views
|level1
1.html
2.html
3.html
|level2
1.html
2.html
3.html
|level3
1.html
2.html
3.html
Login.html

3、controller跳轉!

package com.wei.springboot06security.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouterController {

    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }
    @RequestMapping("/Login")
    public String toLogin(){
        return "views/login";
    }
    @RequestMapping("/level{a}/{b}")
    public String level(@PathVariable("a") Integer a ,@PathVariable("b") Integer b ){
        return "views/level"+a+"/"+b;
    }

}

4、測試實驗環境是否OK!


4.2.2、認識SpringSecurity

Spring Security 是針對Spring專案的安全框架,也是Spring Boot底層安全模組預設的技術選型,他可以實現強大的Web安全控制,對於安全控制,我們僅需要引入spring-boot-starter-security 模組,進行少量的配置,即可實現強大的安全管理!

記住幾個類:

  • WebSecurityConfigurerAdapter:自定義Security策略 (繼承WebSecurityConfigurerAdapter)
  • AuthenticationManagerBuilder:自定義認證策略 (configure 的引數,可以覆寫子類的configure 方法)
  • @EnableWebSecurity:開啟WebSecurity模式 ( 在繼承類上新增該註解)

Spring Security的兩個主要目標是 “認證” 和 “授權”(訪問控制)。

“認證”(Authentication)

身份驗證是關於驗證您的憑據,如使用者名稱/使用者ID和密碼,以驗證您的身份。

身份驗證通常通過使用者名稱和密碼完成,有時與身份驗證因素結合使用。

“授權” (Authorization)

授權發生在系統成功驗證您的身份後,最終會授予您訪問資源(如資訊,檔案,資料庫,資金,位置,幾乎任何內容)的完全許可權。

這個概念是通用的,而不是隻在Spring Security 中存在。

4.2.3、認證和授權

目前,我們的測試環境,是誰都可以訪問的,我們使用 Spring Security 增加上認證和授權的功能

1、引入 Spring Security 模組

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

2、編寫 Spring Security 配置類

參考官網:https://spring.io/projects/spring-security

檢視我們自己專案中的版本,找到對應的幫助文件:

https://docs.spring.io/spring-security/site/docs/5.3.0.RELEASE/reference/html5/#servlet-applications%208.16.4

在這裡插入圖片描述

3、編寫基礎配置類

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity // 開啟WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       
  }
}

4、定製請求的授權規則

@Override
protected void configure(HttpSecurity http) throws Exception {
   // 定製請求的授權規則
   // 首頁所有人可以訪問
   http.authorizeRequests().antMatchers("/").permitAll()
  .antMatchers("/level1/**").hasRole("vip1")
  .antMatchers("/level2/**").hasRole("vip2")
  .antMatchers("/level3/**").hasRole("vip3");
}

5、測試一下:發現除了首頁都進不去了!因為我們目前沒有登入的角色,因為請求需要登入的角色擁有對應的許可權才可以!

6、在configure()方法中加入以下配置,開啟自動配置的登入功能!

// 開啟自動配置的登入功能
// /login 請求來到登入頁
// /login?error 重定向到這裡表示登入失敗
http.formLogin();

7、測試一下:發現,沒有許可權的時候,會跳轉到登入的頁面!

在這裡插入圖片描述

8、檢視剛才登入頁的註釋資訊;

我們可以定義認證規則,重寫configure(AuthenticationManagerBuilder auth)方法

package com.wei.springboot06security.config;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {



//    授權
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首頁所有人都可以訪問,但是功能頁只有對應有許可權的人才能訪問
        http.authorizeRequests().antMatchers("/").permitAll()
        .antMatchers("/level1/**").hasRole("vip1")
        .antMatchers("/level2/**").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3");


        //沒有許可權預設跳到登入頁面
        http.formLogin();

    }

    //認證    springboot 2.1.x可以直接使用
    //密碼編碼  java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    //在spring security 5.0+ 新增了很多的加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
                .withUser("user").password(passwordEncoder.encode("123456")).roles("vip1","vip2")
                .and().withUser("user2").password(passwordEncoder.encode("123456")).roles("vip1")
                .and().withUser("root").password(passwordEncoder.encode("123456")).roles("vip3","vip2","vip1");
        //user 可以訪問vip1.vip2許可權的頁面,root可以訪問vip1-3 所有的頁面
        //訪問到無許可權就會報錯403 . 可以設定跳轉到error/403.html  ,
	}
}





  • 新增了passwordEncoder(new BCryptPasswordEncoder()) 就不會報錯:There is no PasswordEncoder mapped for the id “null”
  • 原因,我們要將前端傳過來的密碼進行某種方式加密,否則就無法登入,修改程式碼

9、測試,發現,登入成功,並且每個角色只能訪問自己認證下的規則!搞定

4.2.4、許可權控制和登出

1、開啟自動配置的登出的功能

//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
   //....
   //開啟自動配置的登出的功能
      // /logout 登出請求
   http.logout();
}

2、我們在前端,增加一個登出的按鈕,index.html 導航欄中

<a class="item" th:href="@{/logout}">
   <i class="address card icon"></i> 登出
</a>

3、我們可以去測試一下,登入成功後點選登出,發現登出完畢會跳轉到登入頁面!

4、但是,我們想讓他登出成功後,依舊可以跳轉到首頁,該怎麼處理呢?

// .logoutSuccessUrl("/"); 登出成功來到首頁
http.logout().logoutSuccessUrl("/");

5、測試,登出完畢後,發現跳轉到首頁OK

6、我們現在又來一個需求:使用者沒有登入的時候,導航欄上只顯示登入按鈕,使用者登入之後,導航欄可以顯示登入的使用者資訊及登出按鈕!還有就是,比如user這個使用者,它只有 vip1,vip2功能,那麼登入則只顯示這兩個功能,而vip3的功能選單不顯示!這個就是真實的網站情況了!該如何做呢?

我們需要結合thymeleaf中的一些功能

sec:authorize=“isAuthenticated()”:是否認證登入!來顯示不同的頁面

Maven依賴:

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>

7、修改我們的 前端頁面
1. 匯入名稱空間
2. xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
3. 修改導航欄,增加認證判斷
4.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>首頁</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/css/style.css}" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
            <a class="item"  th:href="@{/index}">首頁</a>

            <!--登入登出-->
            <div class="right menu">
                <!--如果未登陸 顯示登陸-->
                <div sec:authorize="!isAuthenticated()" >
                    <a class="item" th:href="@{/toLogin}">
                        <i class="address card icon"></i> 登入
                    </a>

                </div>
                <!--已登入 顯示使用者名稱和登出按鈕-->
                <div sec:authorize="isAuthenticated()" >

                    <a class="item">
                        使用者名稱: <span sec:authentication="name"></span>
                        角色: <span sec:authentication="principal.authorities"></span>
                    </a>
                    <a class="item" th:href="@{/logout}">
                        <i class="sign-out icon"></i> 登出
                    </a>
                </div>

            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security test</h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">
            <div class="column" sec:authorize="hasRole('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip2')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>
    
</div>


<script th:src="@{/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/js/semantic.min.js}"></script>

</body>
</html>

8、重啟測試,我們可以登入試試看,登入成功後確實,顯示了我們想要的頁面;

9、如果登出404了,就是因為它預設防止csrf跨站請求偽造,因為會產生安全問題,我們可以將請求改為post表單提交,或者在spring security中關閉csrf功能;我們試試:
在 配置中增加 http.csrf().disable();關閉csrf功能 ,關閉之後,登出無需再次確認!

        //開啟 登出功能
//        http.csrf().disable(); 關閉csrf功能 ,關閉之後,登出無需再次確認!
        http.csrf().disable();
        http.logout().logoutSuccessUrl("/");

10、我們繼續將下面的角色功能塊認證完成!
關鍵點:sec:authorize=“hasRole(‘vip1’)”

        <div class="ui three column stackable grid">
            <div class="column" sec:authorize="hasRole('vip1')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 1</h5>
                            <hr>
                            <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                            <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                            <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip2')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>

11、測試一下!

12、許可權控制和登出搞定!

4.2.5、記住我

現在的情況,我們只要登入之後,關閉瀏覽器,再登入,就會讓我們重新登入,但是很多網站的情況,就是有一個記住密碼的功能,這個該如何實現呢?

1、開啟記住我功能

//定製請求的授權規則
@Override
protected void configure(HttpSecurity http) throws Exception {
//。。。。。。。。。。。
   //記住我
   http.rememberMe();
}

2、我們再次啟動專案測試一下,發現登入頁多了一個記住我功能,我們登入之後關閉 瀏覽器,然後重新開啟瀏覽器訪問,發現使用者依舊存在!

思考:如何實現的呢?其實非常簡單

我們可以檢視瀏覽器的cookie

-在這裡插入圖片描述

3、我們點選登出的時候,可以發現,spring security 幫我們自動刪除了這個 cookie

在這裡插入圖片描述
4、結論:登入成功後,將cookie傳送給瀏覽器儲存,以後登入帶上這個cookie,只要通過檢查就可以免登入了。如果點選登出,則會刪除這個cookie.

4.2.6、定製登入頁

現在這個登入頁面都是spring security 預設的,怎麼樣可以使用我們自己寫的Login介面呢?

1、在剛才的登入頁配置後面指定 loginpage

        //沒有許可權預設跳到登入頁面, 引數對應表單的name屬性
        http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password");

2、然後前端也需要指向我們自己定義的 login請求

<a class="item" th:href="@{/toLogin}">
   <i class="address card icon"></i> 登入
</a>

3、我們登入,需要將這些資訊傳送到哪裡,我們也需要配置,login.html 配置提交請求及方式,方式必須為post:

在 loginPage()原始碼中的註釋上有寫明:
在這裡插入圖片描述

login.html+

<form th:action="@{/toLogin}" method="post" >
                            <div class="field">
                                <label>Username</label>
                                <div class="ui left icon input">
                                    <input type="text" placeholder="Username" name="username">
                                    <i class="user icon"></i>
                                </div>
                            </div>

                            <div class="field">
                                <label>Password</label>
                                <div class="ui left icon input">
                                    <input type="password" placeholder="Password" name="password">
                                    <i class="lock icon"></i>
                                </div>
                            </div>

                            <div class="field">
                                <div class="ui left icon input">
                                    <input type="checkbox" name="remember">在這臺電腦上記住我
                                </div>
                            </div>
                            <input type="submit" class="ui blue submit button"/>
                        </form>

4、這個請求提交上來,我們還需要驗證處理,怎麼做呢?我們可以檢視formLogin()方法的原始碼!我們配置接收登入的使用者名稱和密碼的引數!

//沒有許可權預設跳到登入頁面
http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password");

5、在登入頁增加記住我的多選框

<input type="checkbox" name="remember"> 記住我

6、後端驗證處理!

//開啟記住我功能
http.rememberMe().rememberMeParameter("remember");

7、測試,OK

4.3、完整配置程式碼

package com.wei.springboot06security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.sql.DataSource;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//    授權
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //首頁所有人都可以訪問,但是功能頁只有對應有許可權的人才能訪問
        http.authorizeRequests().antMatchers("/").permitAll()
        .antMatchers("/level1/**").hasRole("vip1")
        .antMatchers("/level2/**").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3");


        //沒有許可權預設跳到登入頁面
        http.formLogin().loginPage("/toLogin").usernameParameter("username").passwordParameter("password");

        //開啟 登出功能
//        http.csrf().disable(); 關閉csrf功能 ,關閉之後,登出無需再次確認!
        http.csrf().disable();
        http.logout().logoutSuccessUrl("/");

        //開啟記住我功能
        http.rememberMe().rememberMeParameter("remember");

    }

    //認證    springboot 2.1.x可以直接使用
    //密碼編碼  java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    //在spring security 5.0+ 新增了很多的加密方法
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        auth.inMemoryAuthentication().passwordEncoder(passwordEncoder)
                .withUser("user").password(passwordEncoder.encode("123456")).roles("vip1","vip2")
                .and().withUser("user2").password(passwordEncoder.encode("123456")).roles("vip1")
                .and().withUser("root").password(passwordEncoder.encode("123456")).roles("vip3","vip2","vip1");
        //訪問到無許可權就會報錯403 . 可以設定跳轉到error/403.html  ,
	}

}


相關文章