狂神說spring-boot系列四_框架整合與安全認證


整合JDBC

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

整合JDBC

 

創建測試項目測試數據源

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: 123456
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/springboot?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

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方法:用於執行存儲過程、函數相關語句。

測試

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

package com.kuang.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@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] = "秦疆";
        objects[1] = "24736743@sina.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 就搞定了。

 集成Druid

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 基本配置參數如下:

 

 

 

 

 

 

配置數據源

1、添加上 Druid 數據源依賴。

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

2、切換數據源;之前已經說過 Spring Boot 2.0 以上默認使用 com.zaxxer.hikari.HikariDataSource 數據源,但可以 通過 spring.datasource.type 指定數據源。

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/springboot?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: 123456
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/springboot?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 的依賴

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

6、現在需要程序員自己為 DruidDataSource 綁定全局配置文件中的參數,再添加到容器中,而不再使用 Spring Boot 的自動生成了;我們需要 自己添加 DruidDataSource 組件到容器中,並綁定屬性;

package com.kuang.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@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();
    }
}

 

 

配置Druid數據源監控

Druid 數據源具有監控的功能,並提供了一個 web 界面方便用戶查看,類似安裝 路由器 時,人家也提供了一個默認的 web 頁面。

所以第一步需要設置 Druid 的后台管理頁面,比如 登錄賬號、密碼 等;配置后台管理;

//配置 Druid 監控管理后台的Servlet;
//內置 Servlet 容器時沒有web.xml文件,所以使用 Spring Boot 的注冊 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

    // 這些參數可以在 com.alibaba.druid.support.http.StatViewServlet 
    // 的父類 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    initParams.put("loginUsername", "admin"); //后台管理界面的登錄賬號
    initParams.put("loginPassword", "123456"); //后台管理界面的登錄密碼

    //后台允許誰可以訪問
    //initParams.put("allow", "localhost"):表示只有本機可以訪問
    //initParams.put("allow", ""):為空或者為null時,表示允許所有訪問
    initParams.put("allow", "");
    //deny:Druid 后台拒絕誰訪問
    //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip訪問

    //設置初始化參數
    bean.setInitParameters(initParams);
    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;
}

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

所以druid的配置為

package com.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource(){
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean statViewServlet() {//配置Druid數據源監控
        ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

        // 這些參數可以在 com.alibaba.druid.support.http.StatViewServlet
        // 的父類 com.alibaba.druid.support.http.ResourceServlet 中找到
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin"); //后台管理界面的登錄賬號
        initParams.put("loginPassword", "123456"); //后台管理界面的登錄密碼

        //后台允許誰可以訪問
        //initParams.put("allow", "localhost"):表示只有本機可以訪問
        //initParams.put("allow", ""):為空或者為null時,表示允許所有訪問
        initParams.put("allow", "");
        //deny:Druid 后台拒絕誰訪問
        //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip訪問

        //設置初始化參數
        bean.setInitParameters(initParams);
        return bean;
    }

    //配置 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;
    }
}

 

 

整合MyBatis

 

官方文檔:http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

Maven倉庫地址:https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter/2.1.1

 

整合測試

1、導入 MyBatis 所需要的依賴

 

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

2、配置數據庫連接信息(不變)

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解決時區的報錯
    url: jdbc:mysql://localhost:3306/springboot?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

3、測試數據庫是否連接成功!

4、創建實體類,導入 Lombok!

Department.java

package com.kuang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {

    private Integer id;
    private String departmentName;

}

5、創建mapper目錄以及對應的 Mapper 接口

DepartmentMapper.java

//@Mapper : 表示本類是一個 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {

    // 獲取所有部門信息
    List<Department> getDepartments();

    // 通過id獲得部門
    Department getDepartment(Integer id);

}

6、對應的Mapper映射文件

DepartmentMapper.xml

<?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.mapper.DepartmentMapper">

    <select id="getDepartments" resultType="Department">
       select * from department;
    </select>

    <select id="getDepartment" resultType="Department" parameterType="int">
       select * from department where id = #{id};
    </select>

</mapper>

7、maven配置資源過濾問題

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yaml</include>
<include>**/*.yml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yaml</include>
<include>**/*.yml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>

 

8、編寫部門的 DepartmentController 進行測試!

@RestController
public class DepartmentController {
    
    @Autowired
    DepartmentMapper departmentMapper;
    
    // 查詢全部部門
    @GetMapping("/getDepartments")
    public List<Department> getDepartments(){
        return departmentMapper.getDepartments();
    }

    // 查詢全部部門
    @GetMapping("/getDepartment/{id}")
    public Department getDepartment(@PathVariable("id") Integer id){
        return departmentMapper.getDepartment(id);
    }
    
}

啟動項目訪問進行測試!

我們增加一個員工類再測試下,為之后做准備

1、新建一個pojo類 Employee ;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {

    private Integer id;
    private String lastName;
    private String email;
    //1 male, 0 female
    private Integer gender;
    private Integer department;
    private Date birth;

    private Department eDepartment; // 冗余設計

}

2、新建一個 EmployeeMapper 接口

//@Mapper : 表示本類是一個 MyBatis 的 Mapper
@Mapper
@Repository
public interface EmployeeMapper {

    // 獲取所有員工信息
    List<Employee> getEmployees();

    // 新增一個員工
    int save(Employee employee);

    // 通過id獲得員工信息
    Employee get(Integer id);

    // 通過id刪除員工
    int delete(Integer id);

}

3、編寫 EmployeeMapper.xml 配置文件

<?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.mapper.EmployeeMapper">

    <resultMap id="EmployeeMap" type="Employee">
        <id property="id" column="eid"/>
        <result property="lastName" column="last_name"/>
        <result property="email" column="email"/>
        <result property="gender" column="gender"/>
        <result property="birth" column="birth"/>
        <association property="eDepartment"  javaType="Department">
            <id property="id" column="did"/>
            <result property="departmentName" column="dname"/>
        </association>
    </resultMap>

    <select id="getEmployees" resultMap="EmployeeMap">
        select e.id as eid,last_name,email,gender,birth,d.id as did,d.department_name as dname
        from department d,employee e
        where d.id = e.department
    </select>

    <insert id="save" parameterType="Employee">
        insert into employee (last_name,email,gender,department,birth)
        values (#{lastName},#{email},#{gender},#{department},#{birth});
    </insert>

    <select id="get" resultType="Employee">
        select * from employee where id = #{id}
    </select>

    <delete id="delete" parameterType="int">
        delete from employee where id = #{id}
    </delete>

</mapper>

4、編寫EmployeeController類進行測試

@RestController
public class EmployeeController {

    @Autowired
    EmployeeMapper employeeMapper;

    // 獲取所有員工信息
    @GetMapping("/getEmployees")
    public List<Employee> getEmployees(){
        return employeeMapper.getEmployees();
    }

    @GetMapping("/save")
    public int save(){
        Employee employee = new Employee();
        employee.setLastName("kuangshen");
        employee.setEmail("qinjiang@qq.com");
        employee.setGender(1);
        employee.setDepartment(101);
        employee.setBirth(new Date());
        return employeeMapper.save(employee);
    }

    // 通過id獲得員工信息
    @GetMapping("/get/{id}")
    public Employee get(@PathVariable("id") Integer id){
        return employeeMapper.get(id);
    }

    // 通過id刪除員工
    @GetMapping("/delete/{id}")
    public int delete(@PathVariable("id") Integer id){
        return employeeMapper.delete(id);
    }

}

集成SpringSecurity

安全簡介

在 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),可以對應用中的領域對象進行細粒度的控制。

實戰測試

實驗環境搭建

1、新建一個初始的springboot項目web模塊,thymeleaf模塊

2、導入靜態資源

 https://files.cnblogs.com/files/henuliulei/SpringSecurity%E7%B4%A0%E6%9D%90_ioqdonxc.rar

3、controller跳轉!

package com.kuang.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("/toLogin")
   public String toLogin(){
       return "views/login";
  }

   @RequestMapping("/level1/{id}")
   public String level1(@PathVariable("id") int id){
       return "views/level1/"+id;
  }

   @RequestMapping("/level2/{id}")
   public String level2(@PathVariable("id") int id){
       return "views/level2/"+id;
  }

   @RequestMapping("/level3/{id}")
   public String level3(@PathVariable("id") int id){
       return "views/level3/"+id;
  }

}

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

 

認識SpringSecurity

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

記住幾個類:

  • WebSecurityConfigurerAdapter:自定義Security策略

  • AuthenticationManagerBuilder:自定義認證策略

  • @EnableWebSecurity:開啟WebSecurity模式

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

“認證”(Authentication)

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

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

“授權” (Authorization)

授權發生在系統成功驗證您的身份后,最終會授予您訪問資源(如信息,文件,數據庫,資金,位置,幾乎任何內容)的完全權限。

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

認證和授權

目前,我們的測試環境,是誰都可以訪問的,我們使用 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 8.16.4

 

 

 3、編寫基礎配置類

package com.kuang.config;

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)方法

//定義認證規則
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   
   //在內存中定義,也可以在jdbc中去拿....
   auth.inMemoryAuthentication()
          .withUser("kuangshen").password("123456").roles("vip2","vip3")
          .and()
          .withUser("root").password("123456").roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password("123456").roles("vip1","vip2");
}

9、測試,我們可以使用這些賬號登錄進行測試!發現會報錯!

There is no PasswordEncoder mapped for the id “null”

 

 

 

 

//定義認證規則
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
   //在內存中定義,也可以在jdbc中去拿....
   //Spring security 5.0中新增了多種加密方式,也改變了密碼的格式。
   //要想我們的項目還能夠正常登陸,需要修改一下configure中的代碼。我們要將前端傳過來的密碼進行某種方式加密
   //spring security 官方推薦的是使用bcrypt加密方式。
   
   auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
          .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
          .and()
          .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
          .and()
          .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}

 

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

權限控制和注銷

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、我們現在又來一個需求:用戶沒有登錄的時候,導航欄上只顯示登錄按鈕,用戶登錄之后,導航欄可以顯示登錄的用戶信息及注銷按鈕!還有就是,比如kuangshen這個用戶,它只有 vip2,vip3功能,那么登錄則只顯示這兩個功能,而vip1的功能菜單不顯示!這個就是真實的網站情況了!該如何做呢?

我們需要結合thymeleaf中的一些功能
sec:authorize="isAuthenticated()":是否認證登錄!來顯示不同的頁面

Maven依賴:

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
   <groupId>org.thymeleaf.extras</groupId>
   <artifactId>thymeleaf-extras-springsecurity5</artifactId>
   <version>3.0.4.RELEASE</version>
</dependency>

7、修改我們的 前端頁面

導入命名空間

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

修改導航欄,增加認證判斷

<!--登錄注銷-->
<div class="right menu">

   <!--如果未登錄-->
   <div sec:authorize="!isAuthenticated()">
       <a class="item" th:href="@{/login}">
           <i class="address card icon"></i> 登錄
       </a>
   </div>

   <!--如果已登錄-->
   <div sec:authorize="isAuthenticated()">
       <a class="item">
           <i class="address card icon"></i>
          用戶名:<span sec:authentication="principal.username"></span>
          角色:<span sec:authentication="principal.authorities"></span>
       </a>
   </div>

   <div sec:authorize="isAuthenticated()">
       <a class="item" th:href="@{/logout}">
           <i class="address card icon"></i> 注銷
       </a>
   </div>
</div>

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

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

http.csrf().disable();//關閉csrf功能:跨站請求偽造,默認只能通過post方式提交logout請求
http.logout().logoutSuccessUrl("/");

10、我們繼續將下面的角色功能塊認證完成!

<!-- sec:authorize="hasRole('vip1')" -->
<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>

11、測試一下!

12、權限控制和注銷搞定!

記住我

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

1、開啟記住我功能

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

2、我們再次啟動項目測試一下,發現登錄頁多了一個記住我功能,我們登錄之后關閉 瀏覽器,然后重新打開瀏覽器訪問,發現用戶依舊存在!

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

我們可以查看瀏覽器的cookie

 

 

 

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

 

 

 

4、結論:登錄成功后,將cookie發送給瀏覽器保存,以后登錄帶上這個cookie,只要通過檢查就可以免登錄了。如果點擊注銷,則會刪除這個cookie,具體的原理我們在JavaWeb階段都講過了,這里就不在多說了!

定制登錄頁

現在這個登錄頁面都是spring security 默認的,怎么樣可以使用我們自己寫的Login界面呢?

1、在剛才的登錄頁配置后面指定 loginpage

  http.formLogin().loginPage("/toLogin");

然后前端也需要指向我們自己定義的 login請求
<a class="item" th:href="@{/toLogin}">
   <i class="address card icon"></i> 登錄
</a>

3、我們登錄,需要將這些信息發送到哪里,我們也需要配置,login.html 配置提交請求及方式,方式必須為post:

在 loginPage()源碼中的注釋上有寫明:

 

 

 

<form th:action="@{/login}" 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" name="password">
           <i class="lock icon"></i>
       </div>
   </div>
   <input type="submit" class="ui blue submit button"/>
</form>

4、這個請求提交上來,我們還需要驗證處理,怎么做呢?我們可以查看formLogin()方法的源碼!我們配置接收登錄的用戶名和密碼的參數!

http.formLogin()
  .usernameParameter("username")
  .passwordParameter("password")
  .loginPage("/toLogin")
  .loginProcessingUrl("/login"); // 登陸表單提交請求

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

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

 

6、后端驗證處理!

//定制記住我的參數!
http.rememberMe().rememberMeParameter("remember");

7、測試,OK

完整配置代碼

package com.kuang.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;

@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");


       //開啟自動配置的登錄功能:如果沒有權限,就會跳轉到登錄頁面!
           // /login 請求來到登錄頁
           // /login?error 重定向到這里表示登錄失敗
       http.formLogin()
          .usernameParameter("username")
          .passwordParameter("password")
          .loginPage("/toLogin")
          .loginProcessingUrl("/login"); // 登陸表單提交請求

       //開啟自動配置的注銷的功能
           // /logout 注銷請求
           // .logoutSuccessUrl("/"); 注銷成功來到首頁

       http.csrf().disable();//關閉csrf功能:跨站請求偽造,默認只能通過post方式提交logout請求
       http.logout().logoutSuccessUrl("/");

       //記住我
       http.rememberMe().rememberMeParameter("remember");
  }

   //定義認證規則
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //在內存中定義,也可以在jdbc中去拿....
       //Spring security 5.0中新增了多種加密方式,也改變了密碼的格式。
       //要想我們的項目還能夠正常登陸,需要修改一下configure中的代碼。我們要將前端傳過來的密碼進行某種方式加密
       //spring security 官方推薦的是使用bcrypt加密方式。

       auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
              .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
              .and()
              .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
              .and()
              .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
  }
}

跟着狂神學Shiro

Shiro

什么是Shiro?

Apache Shiro是一個Java 的安全(權限)框架。
Shiro可以非常容易的開發出足夠好的應用,其不僅可以用在JavaSE環境,也可以用在JavaEE環境。
Shiro可以完成,認證,授權,加密,會話管理,Web集成,緩存等.

●下載地址: http://shiro.apache.org/

在這里插入圖片描述

有哪些功能

 

 

●Authentication: 身份認證、登錄,驗證用戶是不是擁有相應的身份;
●Authorization:授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限,即判斷用戶能否進行什么操作,如:驗證某個用戶是否擁有某個角色,或者細粒度的驗證某個用戶對某個資源是否具有某個權限!
●Session Manager: 會話管理,即用戶登錄后就是第-次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通的JavaSE環境,也可以是Web環境;
●Cryptography: 加密,保護數據的安全性,如密碼加密存儲到數據庫中,而不是明文存儲;
●Web Support: Web支持,可以非常容易的集成到Web環境;
●Caching: 緩存,比如用戶登錄后,其用戶信息,擁有的角色、權限不必每次去查,這樣可以提高效率
●Concurrency: Shiro支持多線程應用的並發驗證,即,如在-個線程中開啟另-一個線程,能把權限自動的傳
播過去
●Testing:提供測試支持;
●RunAs:允許一個用戶假裝為另-一個用戶(如果他們允許)的身份進行訪問;
●Remember Me:記住我,這個是非常常見的功能,即一-次登錄后, 下次再來的話不用登錄了

Shiro架構(外部)

從外部來看Shiro,即從應用程序角度來觀察如何使用shiro完成工作:
在這里插入圖片描述

 

●subject: 應用代碼直接交互的對象是Subject, 也就是說Shiro的對外API核心就是Subject, Subject代表了當前的用戶,這個用戶不-定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等,與Subject的所有交互都會委托給SecurityManager; Subject其實是一一個門面, SecurityManageer 才是
實際的執行者
●SecurityManager: 安全管理器,即所有與安全有關的操作都會與SercurityManager交互, 並且它管理着所有的Subject,可以看出它是Shiro的核心,它負責與Shiro的其他組件進行交互,它相當於SpringMVC的DispatcherServlet的角色
●Realm: Shiro從Realm獲取安全數據 (如用戶,角色,權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較,來確定用戶的身份是否合法;也需要從Realm得到用戶相應的角色、權限,進行驗證用戶的操作是否能夠進行,可以把Realm看DataSource;

●subject: 應用代碼直接交互的對象是Subject, 也就是說Shiro的對外API核心就是Subject, Subject代表了當前的用戶,這個用戶不-定是一個具體的人,與當前應用交互的任何東西都是Subject,如網絡爬蟲,機器人等,與Subject的所有交互都會委托給SecurityManager; Subject其實是一一個門面, SecurityManageer 才是
實際的執行者
●SecurityManager: 安全管理器,即所有與安全有關的操作都會與SercurityManager交互, 並且它管理着所有的Subject,可以看出它是Shiro的核心,它負責與Shiro的其他組件進行交互,它相當於SpringMVC的DispatcherServlet的角色
●Realm: Shiro從Realm獲取安全數據 (如用戶,角色,權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較,來確定用戶的身份是否合法;也需要從Realm得到用戶相應的角色、權限,進行驗證用戶的操作是否能夠進行,可以把Realm看DataSource;

 

 

 

●Subject: 任何可以與應用交互的用戶;
●Security Manager:相當於SpringMVC中的DispatcherSerlet; 是Shiro的心臟, 所有具體的交互都通過Security Manager進行控制,它管理者所有的Subject, 且負責進行認證,授權,會話,及緩存的管理。
●Authenticator:負責Subject認證, 是-一個擴展點,可以自定義實現;可以使用認證策略(Authentication Strategy),即什么情況下算用戶認證通過了;
●Authorizer:授權器,即訪問控制器,用來決定主體是否有權限進行相應的操作;即控制着用戶能訪問應用中
的那些功能;
●Realm: 可以有-一個或者多個的realm, 可以認為是安全實體數據源,即用於獲取安全實體的,可以用JDBC實現,也可以是內存實現等等,由用戶提供;所以- -般在應用中都需要實現自己的realm
●SessionManager:管理Session生 命周期的組件,而Shiro並不僅僅可以用在Web環境,也可以用在普通的JavaSE環境中
●CacheManager: 緩存控制器,來管理如用戶,角色,權限等緩存的;因為這些數據基本上很少改變,放到緩存中后可以提高訪問的性能;
●Cryptography:密碼模塊,Shiro 提高了一些常見的加密組件用於密碼加密, 解密等

Shiro快速開始
准備工作

GitHub資源

創建一個普通maven項目springboot-08-shiro,然后刪除src目錄,這樣的話就可以在這個項目里新建很多model.

在springboot-08-shiro里新建model hello_shiro

找到文件

 

 

 在pom.xml中復制

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

更改細節

 

    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.21</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

復制

 

 

 log4j.properties

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini
[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# 
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -----------------------------------------------------------------------------
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5

然后

 

 

Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.lang.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);


    public static void main(String[] args) {

        // The easiest way to create a Shiro SecurityManager with configured
        // realms, users, roles and permissions is to use the simple INI config.
        // We'll do that by using a factory that can ingest a .ini file and
        // return a SecurityManager instance:

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        // for this simple example quickstart, make the SecurityManager
        // accessible as a JVM singleton.  Most applications wouldn't do this
        // and instead rely on their container configuration or web.xml for
        // webapps.  That is outside the scope of this simple quickstart, so
        // we'll just do the bare minimum so you can continue to get a feel
        // for things.
        SecurityUtils.setSecurityManager(securityManager);

        // Now that a simple Shiro environment is set up, let's see what you can do:

        // get the currently executing user:
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                currentUser.login(token);
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //test a role:
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //test a typed permission (not instance-level)
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring.  Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        //a (very powerful) Instance Level permission:
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        //all done - log out!
        currentUser.logout();

        System.exit(0);
    }
}

啟動

2020-11-04 09:13:59,275 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2020-11-04 09:14:00,629 INFO [Quickstart] - Retrieved the correct value! [aValue] 
2020-11-04 09:14:00,630 INFO [Quickstart] - User [lonestarr] logged in successfully. 
2020-11-04 09:14:00,631 INFO [Quickstart] - May the Schwartz be with you! 
2020-11-04 09:14:00,631 INFO [Quickstart] - You may use a lightsaber ring.  Use it wisely. 
2020-11-04 09:14:00,632 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun! 

 

 

SpringBoot整合Shiro環境搭建

    • 新建SpringBoot項目,勾選webthymeleaf

    • 保持項目清潔,刪除
      在這里插入圖片描述

    • templates下新建index.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="https://www.thymeleaf.org"
      xmlns:shiro="https://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h1>首頁</h1>

<p th:text="${msg}"></p>

</body>
</html>

 

  • controller包下新建MyController
package com.huang.controller;

import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpSession;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","hello,shiro");
        return "index";
    }
}
  • 測試正常

在這里插入圖片描述

讓我們繼續

Subject用戶
SecurityManager管理所有用戶
Realm連接數據

GitHub資源

在這里插入圖片描述
pom.xml復制

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
        </dependency>

發現並不好使更換為

        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>

編寫Realm

 

 編寫ShiroConfig類

 

再增加兩個頁面

 

 Controller加入

 

 首頁添加鏈接

 

 設置過濾器過濾沒有權限的訪問

 

 添加一個登入頁

 

 

 

 如何沒有權限讓他跳轉到登入頁

 

 

接下來進行認證,首先登入頁添加action

 

 添加登入認證

 

此時token是全局的,在登入時Realm里面認證方法也會執行 ,我們可以在認證里面做判斷

 

 

shiro整合mybatis

導入jar包

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

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

編寫application.yml

spring:
  datasource:
    username: root
    password: jia5211314
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
    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
mybatis:
  type-aliases-package: com.huang.pojo
  mapper-locations: classpath:mybatis/mapper/*.xml

編寫實體類

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

User

package com.huang.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;


}

mapper包編寫UserMapper

package com.huang.mapper;

import com.huang.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper//這個注解表示了這是一個mybatis的mapper類
@Repository
public interface UserMapper {

    List<User> queryUserList();

    User queryUserById(int id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}

resource包下新建mybatis包下健mapper

UserMapper.xml

<?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.huang.mapper.UserMapper">

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

    <select id="queryUserByName" resultType="User" parameterType="String">
        select * from user where name=#{name}
    </select>

</mapper>

service

UserService接口

package com.huang.service;

import com.huang.pojo.User;

public interface UserService {
    User queryUserByName(String name);
}

UserServiceImpl

package com.huang.service;

import com.huang.mapper.UserMapper;
import com.huang.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUserByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

ShiroSpringbootApplicationTests中進行測試

package com.huang;

import com.huang.service.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ShiroSpringbootApplicationTests {
    @Autowired
    UserServiceImpl userService;

    @Test
    void contextLoads() {
        System.out.println(userService.queryUserByName("張三"));
    }

}
  • 測試成功

現在我們可以從數據庫中取得數據,那么之前認證時偽造的數據可以變成從數據庫中獲取。此時我們使用的數據加密方式是簡單加密,可以換成顏值加密,md5加密等

Shiro請求授權實現

現在希望擁有指定權限的人才可以訪問指定頁面,此時要進行授權

 

 

 

 

 

 同時,希望未授權跳轉到未授權頁面,因此先創建一個未授權頁面(或者直接在本頁面顯示)

 

 

 那怎樣給用戶添加授權呢

 

 

 實際上用戶的權限都是從數據庫中獲取的,但是user對象我們可以從認證里面獲取,進而獲取用戶的權限

\

 

 

package com.bupt.config;

import com.bupt.service.UserService;
import com.bupt.service.UserServiceImpl;
import org.apache.catalina.security.SecurityUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

public class UserRealm extends AuthorizingRealm {


    @Autowired
    UserService userService;

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("執行授權");
        SimpleAuthorizationInfo Info = new SimpleAuthorizationInfo();
        Info.addStringPermission("user:add");

        return Info;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("執行認證");

        //此處的賬號信息可以使用Mapper從數據庫獲取
        String name = "root";
        String password = "123";

        UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
        if (!userToken.getUsername().equals(name)) {
            return null;
        }

        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        session.setAttribute("loginUser", userToken);

        return new SimpleAuthenticationInfo("", password, "");
    }


}

 

 ShiroConfig

package com.bupt.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    //ShiroFilterFactoryBean,實現過濾

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        Map<String, String> filterMap = new LinkedHashMap<>();
//        filterMap.put("/user/*","authc");
        filterMap.put("/user/add", "perms[user:add]");
        filterMap.put("/user/update", "perms[user:update]");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        return shiroFilterFactoryBean;
    }


    //DefaultWebSecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(userRealm);
        return defaultWebSecurityManager;
    }

    //創建 realm 對象
    @Bean
    public UserRealm userRealm() {
        return new UserRealm();
    }

    //整合ShiroDialect:用來整合Shiro thymeleaf
    @Bean
    public ShiroDialect getShiroDialect() {
        return new ShiroDialect();
    }
}

 

 

Shiro整合Thymeleaf

導入jar

 

 




 

整合兩者需要注冊shiroDialect

 

 

前端進行判斷

 

 

我們希望登入之后,登入的連接就不存在了,可以通過session進行判斷

 

 

 

 

代碼整理

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM