89.【SpringBoot-02】

news2025/1/16 21:49:38

SpringBoot

  • 聊一聊如何构建一个网站
  • (十四)、.SpringBoot整合数据库操作
    • 1.整合JDBC
        • (1).SpringData简介
        • (2).整合JDBC
        • (3).JdbcTemplate ⭐
    • 2. 整合Druid数据源 (德鲁伊)
        • (1).Druid简介
        • (2).配置数据源
        • (3).配置Druid数据源监控
        • (4).配置Druid数据源过滤器
        • (5).注解的详解⭐
    • 3.整合Mybatis
        • (1).整合测试
  • (十五)、SpringSecurity (安全)
    • 1.认识SpringSecurity
    • 2.实战使用
        • (1).搭建环境
        • (2).用户认证和授权
        • (3).权限控制和注销
        • (4).记住我和首页定制
        • (5).定制登入页
  • (十六)、Shiro
    • 1.概述
        • (1).简介
        • (2).有哪些功能?
        • (3)Shiro架构 (外部)
        • (4).外部架构
        • (5).认证流程
    • 2.快速入门
        • (1).ini插件安装和配置
        • (2).分析案例
    • 3.Springboot集成Shiro
        • (1).创建环境
        • (2).实现登入拦截
        • (3).实现用户认证
        • (4).Shiro整合Mybatis
        • (5).授权功能的实现
        • (6).Shiro和thymeleaf整合

聊一聊如何构建一个网站

https://www.bjchuhai.com/ 76Z7D
格式化代码块: Ctrl+Alt+L
全局搜索: Shift+Shift
局部搜索:Ctrl+F

  • 模板: (前端的模板)

http://www.cssmoban.com/ 模板之家

  • 框架: (组件: 我们自己进行组合合并)
    Bootstrap
    semantic-ui
    https://1.semantic-ui.com/

1.前端搞定:页面长什么样子:数据

2.设计数据库

3.前端让他能够自动运行,独立化工程

4.数据接口如何对接,json,对象all in one

5.前后端联调测试
后台框架:xadmin http://x.xuebingsi.com/

前端界面:至少自己能够通过前端框架,组合出来一个网站页面

(十四)、.SpringBoot整合数据库操作

1.整合JDBC

(1).SpringData简介

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理

Spring Boot 底层都是采用 Spring Data 的方式进行统一处理各种数据库,Spring Data 也是 Spring 中与 Spring BootSpring Cloud 等齐名的知名项目。

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

(2).整合JDBC

数据库相关的启动器 :可以参考官方文档:

https://docs.spring.io/spring-boot/docs/2.2.5.RELEASE/reference/htmlsingle/#using-boot-starter
1.我们创建一个SQL工程项目
在这里插入图片描述
2.创建完模板之后 我们在pom.xml中会有以下的依赖

  • 我们要记得更改对应的mysql驱动版本依赖
<dependency>    
    <groupId>org.springframework.boot</groupId>    
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

3.编写yaml配置文件 连接数据库---application.yaml

  • 全场不需要使用 双引号进行引注
spring:
  datasource:
    # 我们这里用户名和密码都不需要双引号 ,
      username: root
      password: 121788
      url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=utf-8
      driver-class-name: com.mysql.jdbc.Driver

4.我们配置完以上基本配置之后,我们进行测试数据

  • 我们要对DataSource进行控制权的反转。 也就是自动装配
  • 调用DataSource的一个getConnection()链接的一个方法 不要在方法里面写形参了
  • 然后我们对数据进行遍历
package com.jsxs;

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

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@SpringBootTest
class SpringBoot03SqlApplicationTests {
    //  如果我们这里添加 @AutoWried 出现dataSource爆红,那么我们就进行改成Resource
    @Resource
//    这个数据源的主要目的就是为了连接数据库,里面内敛一个getConnection()这个方法。
    private DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
//        这里查看一下默认的数据源
        System.out.println(dataSource.getClass());
//        链接数据库的操作,切记我们这里不能写形参了。
        Connection connection = dataSource.getConnection();
//        输入sql语句
        String sql="select *from user";
//        我们进行预编译语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
//        这里我们调用查询的方法
        ResultSet resultSet = preparedStatement.executeQuery();
//        我们开始对数据进行遍历
        while (resultSet.next()){
            System.out.println(resultSet.getInt(1)+resultSet.getString(2));
        }
        connection.close();
    }
}

在这里插入图片描述
在这里插入图片描述
5.源码分析:
我们全局搜索 DataSourceProperties.class,我们点开看源码。
利用yaml对DataSource进行赋值的操作。
在这里插入图片描述
结果:我们可以看到他默认给我们配置的数据源为 : 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

(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.查询数据库的全部信息

我们这里要使用List<>集合内嵌Map集合,因为Map集合的形参必须是两个。所以我们通过内嵌Map集合我们就可以实现对数据库的信息(包括字段名和信息)

我们如果想使用ResultFul风格, (1).需要在请求的路径上加 {xxx} 。(2).需要在形参上添加注解@PathVariable(“xxx”)。

JdbcTempladet类的update()方法形参有很多种, (sql,objects).我们可以使用这样的方法对数据进行动态的处理

package com.jsxs.controller;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

@RestController
public class JDBCTemplate {
    @Resource
    private JdbcTemplate jdbcTemplate;

    //  查询
    @RequestMapping("/userList")
    public List<Map<String, Object>> Jdbc() {
//        查询数据库的所有信息
//        没有实体类,数据库中的属性怎么获取?    Map<属性名,值>,    List<Object>只能有一个属性形参
//        为什么要这样做? 因为我们传输的是JSON值,要求我们这样做。
        List<Map<String, Object>> list_maps = jdbcTemplate.queryForList("select *from user");
        return list_maps;
    }

    //  增加
    @RequestMapping("/addUser")
    public String addUser() {
        String sql = "insert into user values(8,'嘿嘿','123444')";
        int update = jdbcTemplate.update(sql);
        if (update > 0) {
            return "update success";
        } else {
            return "Update on!";
        }
    }

    //    删除
    @RequestMapping("/deleteUser")
    public String deleteUser() {
        String sql = "delete from user where id=8";
        int update = jdbcTemplate.update(sql);
        if (update > 0) {
            return "delete success!";
        } else {
            return "delete no!";
        }
    }

    //    更改
//    利用ResultFul风格的条件
//     (1). 我们在路径添加的时候,我们需要花括号 {}
//       (2).  我们需要注解  @PathVariable("id")
//          (3). update(): 后面有很多形参,我们可以添加数组,实现 ? 来实现对数据的更改。
    @RequestMapping("/updateUser/{id}")
    public String updateUser(@PathVariable("id") int id) {
        String sql = "update user set name=?,pwd=? where id=" + id;
//        我们这里对Object设置成数组、
        Object[] objects = new Object[2];
        objects[0] = "1111111";
        objects[1] = "1111111";
//       我们这里多添加一个参数,第一个是sql语句。第二个是objects数组;
        int update = jdbcTemplate.update(sql, objects);
        if (update > 0) {
            return "update success!";
        } else {
            return "update no!";
        }
    }
}

2. 整合Druid数据源 (德鲁伊)

因为SpringBoot 内置了Servlet容器,所以没有web.xml, 替代方法: ServletRegistrationBean

数据库
数据库是一个容器,包含了很多数据,当然这些数据可能存在不同的小容器()里面。
若用水来形容数据,数据库就是水库。

数据源
数据源是连接到数据库的一类路径,它包含了访问数据库的信息(地址、用户名、密码)。
数据源就像是排水管道。

数据库连接
数据库连接是根据数据源产生的实际连接上数据库的路径。
数据库连接就像是管道里面的水管,这些水管都按照管道(数据源)的配置访问数据库。当打开了数据连接的时候,就像是打开了水管一样。

数据库连接池
每个数据源可能会配置数据库连接池,就像是排水管道的自动化系统。
数据库连接池的作用就是维护数据库连接,减少创建和删除数据库连接的操作,来达到减少数据访问时耗的目的。

(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 基本配置参数如下:
在这里插入图片描述
在这里插入图片描述

(2).配置数据源

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: 121788
      url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=utf-8
      driver-class-name: com.mysql.jdbc.Driver
    # 我们这里指定的是阿里巴巴的 德鲁伊 的数据源
      type: com.alibaba.druid.pool.DruidDataSource

3、数据源切换之后,在测试类中注入 DataSource,然后获取到它,输出一看便知是否成功切换;
在这里插入图片描述

#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.看到需要用到log4j,所以需要在pom中导入log4j的依赖

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

5、现在需要程序员自己为 DruidDataSource 绑定全局配置文件中的参数,再添加到容器中,而不再使用 Spring Boot 的自动生成了;我们需要 自己添加 DruidDataSource 组件到容器中,并绑定属性;

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

}

(6).去测试类进行测试是否连接 德鲁伊数据源
在这里插入图片描述

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

(3).配置Druid数据源监控

DynamicRegistrationBean 源码
(1).这里的路径是 druid/* 不是 druid/** (2).用户名和密码的Key值都是固定的 不能随意更改

//    后台监控
    @Bean
    public ServletRegistrationBean statViewServlet(){
        ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");

// 1.       后台需要有人登入,账号密码配置
        Map<String, String> initParameters=new HashMap<>();
//        增加配置
        initParameters.put("loginUsername","admin");  //  这个Key值是固定的,不能随意更改
        initParameters.put("loginPassword","121788"); //  这个Key值是固定的,不能随意更改

//        允许谁可以访问?
    //initParams.put("allow", "localhost"):表示只有本机可以访问
    //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
        initParameters.put("allow","");   //如果不写value,就是所有人都可以访问
//        不允许谁访问?
        initParameters.put("jsxs","792.168.11.123");   // 禁止某人访问
// 2.       设置初始化参数
        statViewServletServletRegistrationBean.setInitParameters(initParameters);
        return statViewServletServletRegistrationBean;
    }

2.配置过滤器和监控支持

    #配置监控统计拦截的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.配置完毕后,我们可以选择访问 :http://localhost:8080/druid
在这里插入图片描述

在这里插入图片描述
访问http://localhost:8080/userList
在这里插入图片描述
观察到德鲁伊的后台,会检测到SQL语句的变化
在这里插入图片描述

(4).配置Druid数据源过滤器

//    配置过滤器
    @Bean
    public FilterRegistrationBean Filter(){
        FilterRegistrationBean filterFilterRegistrationBean = new FilterRegistrationBean();

        filterFilterRegistrationBean.setFilter(new WebStatFilter());

//        可以过滤那些请求?
        Map<String, String> initParameters=new HashMap<>();

//        排除谁被过滤?
        initParameters.put("exclusions","*.js,*.css,*.html,*/druid/*");


//        设置初始化参数
        filterFilterRegistrationBean.setInitParameters(initParameters);
        return filterFilterRegistrationBean;
    }

注意点:DruidConfig文件一定要添加配置注解,在里面配置的一些servlet和filter都要添加@Bean注解

(5).注解的详解⭐

@SpringBootApplication
这个注解是SpringBoot最核心的注解,用在SpringBoot的主类上,标识这是一个SpringBoot应用,用来开启SpringBoot的各项能力。实际上这个注解是@Configuration,@EnableAutoConfiguration,@ComponentScan三个注解的组合。由于这些注解一般都是一起使用,所以SpringBoot提供了一个统一的注解@SpringBootApplication。
@EnableAutoConfiguration
允许SpringBoot自动配置注解,开启这个注解之后,SpringBoot就能根据当前类路径下的包或者类来配置SpringBean。
如:当前路径下有Mybatis这个JAR包,MybatisAutoConfiguration注解就能根据相关参数来配置Mybatis的各个SpringBean,
@Configuration
用于定义配置类,指出该类是Bean配置的信息源,相当于传统的xml配置文件,一般加在主类上。如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类。
@ComponentScan
组件扫描。让SpringBoot扫描到Configuration类并把它加入到程序上下文。@ComponentScan注解默认就会装配标识了@Controller,@Service,@Repository,@Component注解的类到Spring容器中。
@RestController
用于标注控制层组件(如struts中的action),表示这是个控制器bean,并且是将函数的返回值直接填入HTTP响应体中,是REST风格的控制器;它是@Controller和@ResponseBody的合集。
@Bean
相当于XML中的< bean> < /bean>,放在方法的上面,而不是类,意思是产生一个bean,并交给Spring管理。
@PathVariable
路径变量,参数与大括号里的名字一样要相同。
@ConfigurationProperties
将配置文件中的Person属性赋的值映射到该组件中

3.整合Mybatis

(1).整合测试

1.导入Mybatis的依赖

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

2.我们在编写配置的时候,driver-class-name 驱动一定要放在最后面.否则会报错
反列:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: 121788
      url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useonicode=true&characterEncoding=utf-8

在这里插入图片描述
正列:

spring:
  datasource:
  
      username: root
      password: 121788
      url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useonicode=true&characterEncoding=utf-8
      driver-class-name: com.mysql.jdbc.Driver

在这里插入图片描述
3.我们的pom.xml文件中,需要引入mybatis的驱动。并且JDBC的驱动需要指定版本否则报错

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jsxs</groupId>
    <artifactId>SpringBoot-04-Mybatis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBoot-04-Mybatis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
<!--        我们导入Mybatis的启动器-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<!--  如果不对数据库驱动添加版本的话,就默认是最高的-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4、测试数据库是否连接成功!
测试数据:

package com.jsxs;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

@SpringBootTest
class SpringBoot04MybatisApplicationTests {

    @Resource
    private DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        Connection connection = dataSource.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement("select *from user");
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()){
            System.out.println(resultSet.getString(1)+" "+resultSet.getString(2));
        }
        connection.close();
    }
}

在这里插入图片描述
5.设置实体类pojo

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

6.创建Dao层接口 mapper文件夹下面设置接口UserMapper.java

  • @Mapper 这个注解表示了这是一个 Mybatis 的 mapper 类相当于Dao层的接口。 类: Dao
  • @Repository Dao层接口的注解
package com.jsxs.mapper;

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

import java.util.List;

//这个注解表示了这是一个 Mybatis 的 mapper 类相当于Dao层的接口。 类: Dao
@Mapper
//  注解类 Dao
@Repository
public interface UserMapper {

//    查询全部的用户
    public List<User> queryList();
//    通过id进行查询用户
    public User UserById(int id);
//    进行增加用户信息
    public int addUser(User user);
//    进行更新用户
    public int updateUser(User user);
//    通过id进行数据的查询
    public int deleteUserById(int id);

}

7.在resources目录下设置mybatis/UserMapper.xml

  1. 绑定mapper/User.Mapper.java工作空间
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 1. 我们首先要绑定工作空间-->
<mapper namespace="com.jsxs.mapper.UserMapper">
<!-- 2.  我们编写SQL语句   -->
<!--   id是方法名, 返回类型是实体类 -->
    <select id="queryList" resultType="com.jsxs.pojo.User">
        select *from user;
    </select>
<!-- 3.通过id查找   #{xxx} 占位符 -->
    <select id="UserById" parameterType="int" resultType="com.jsxs.pojo.User">
        select *from user where id=#{id}
    </select>
<!--    3.增加用户信息-->
    <insert id="addUser" parameterType="com.jsxs.pojo.User">
        insert into user values(#{id},#{name},#{pwd})
    </insert>
<!--    4.进行更新用户信息-->
    <update id="updateUser" parameterType="com.jsxs.pojo.User">
        update user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>
<!--    进行删除数据-->
    <delete id="deleteUserById" parameterType="int">
        delete from user where id=#{id}
    </delete>
</mapper>

8.在Resource资源目录下配置UserMapper.xml的实现。application.yaml

  • resource目录下切换文档需要是 ‘/’ 不是 ‘.’
  • classpath: 指的是我们设置的UserMapper.xml
spring:
  datasource:

      username: root
      password: 121788
      url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useonicode=true&characterEncoding=utf-8
      driver-class-name: com.mysql.jdbc.Driver

# 整合Mybatis 就相当于Mybatis的核心包一样
mybatis:
      type-aliases-package: com.jsxs.pojo.*
      mapper-locations: classpath:mybatis/mapper/*.xml

设置Controller层,不设置业务层了。我们只进行测试数据

package com.jsxs.controller;

import com.jsxs.mapper.UserMapper;
import com.jsxs.pojo.User;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
@RequestMapping("/jsxs")
public class UserController {

    @Resource
    private UserMapper userMapper;

//    查询全部的信息
    @RequestMapping("/all")
    public List<User> queryList(){
        List<User> users = userMapper.queryList();
        return users;
    }
//   通过id进行查询数据
    @RequestMapping("/id/{id}")
    public User queryById(@PathVariable("id") int id){
        User user = userMapper.UserById(id);
        return user;
    }
//  http://localhost:8080/jsxs/add?id=8&name=sdsds&pwd=2711111
    @RequestMapping("/add")
    public String addUser(User user){
        int i = userMapper.addUser(user);
        if (i>0){
            return "恭喜您,数据添加成功";
        }else {
            return "很抱歉,数据添加失败";
        }
    }
// 进行数据的修改
    @RequestMapping("/update")
    public String updateUser(User user){
        int i = userMapper.updateUser(user);
        if (i>0){
            return "恭喜您,数据更新成功";
        }else {
            return "很抱歉,数据更新失败";
        }
    }
//

    @RequestMapping("/delete/{id}")
    public String deleteUser(@PathVariable("id") int id){
        int i = userMapper.deleteUserById(id);
        if (i>0){
            return "恭喜您,数据删除成功";
        }else {
            return "很抱歉,数据删除失败";
        }
    }
}

在这里插入图片描述

(十五)、SpringSecurity (安全)

一个安全的框架,其实通过过滤器和拦截器也可以实现

首先我们看下它的官网介绍: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 Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

1.认识SpringSecurity

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
    Spring Security的两个主要目标是 “认证” 和 “授权”(访问控制)。
    认证:就是给某个人赋予什么样的角色(名片)
    授权: 就是某个人(角色/名片)能行驶什么样的权力

“认证”(Authentication)

身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。

身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

“授权” (Authorization)

授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

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

2.实战使用

(1).搭建环境

1.我们首先导入静态资源
在这里插入图片描述
2.引入thymeleaf依赖需求

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

3.设置路由转向Controller

  • 这里我们学习到一个新技术,就是我们可以使用ReusltFul风格对其页面数据进行跳转
    @RequestMapping("/level1/{id}")
    public String toOne(@PathVariable("id") int id) {

        return "views/level1/"+id;
    }
package com.jsxs.controller;

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

@Controller
@RequestMapping({"/jsxs","/"})
public class RouterController {

    @RequestMapping({"/", "/index"})
    public String index() {
        return "index";
    }

    @RequestMapping("/toLogin")
    public String toLogin() {

        return "views/login";
    }

    //    vip1

//    这里我们使用几千前端传参实现对页面跳转的控制
    @RequestMapping("/level1/{id}")
    public String toOne(@PathVariable("id") int id) {

        return "views/level1/"+id;
    }

    //    这里我们使用几千前端传参实现对页面跳转的控制
    @RequestMapping("/level2/{id}")
    public String toTwo(@PathVariable("id") int id) {

        return "views/level2/"+id;
    }

    //    这里我们使用几千前端传参实现对页面跳转的控制
    @RequestMapping("/level3/{id}")
    public String toThree(@PathVariable("id") int id) {

        return "views/level3/"+id;
    }
}

在这里插入图片描述

(2).用户认证和授权

1.引入 SpringSecurity 依赖

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

2.新建一个配置文件,并且继承 WebSecurityConfigurerAdapter,并且要添加@EnableWebSecurity注解 (授权)
授权

package com.jsxs.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
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//    链式编程
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        首页所有人可以访问,但是功能页只有对应有权限的人才能访问
        http.authorizeRequests()
                .antMatchers("/").permitAll()
//                这里意思就是说: 在level1下的所有页面只有vip1才能进行访问
                .antMatchers("/level1/**").hasRole("vip1")
//                这里意思就是说: 在level2下的所有页面只有vip2才能进行访问
                .antMatchers("/level2/**").hasRole("vip2")
//                这里意思就是说: 在level3下的所有页面只有vip3才能进行访问
                .antMatchers("/level3/**").hasRole("vip3");
        super.configure(http);
    }
}

3.我们进行测试登入 如果我们没有权限就进入了登入页面。(系统自带非自写)
账户是: user 密码: 会显示在控制台

//        没有权限默认跳到登入页面
             http.formLogin();

在这里插入图片描述
4.输入账户密码正确无误后 会进入我们所需的页面
在这里插入图片描述
在这里插入图片描述

6.我们进行用户认证 在springBoot 2.xx之后出现了很多的加密方式我们为了反编译所以我们要对密码进行加密的方式进行处理
认证

package com.jsxs.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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     *
     * @param auth 认证
     * @throws Exception
     * 
     * There is no PasswordEncoder mapped for the id "null"  如果我们直接写就会发生密码编码错误,这里是因为springBoot 版本的原因
     * 在Spring Security 5.0+ 新增了很多的加密方法
     * 
     */
//    缓存 和 数据库 都可以尽心访问
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        这些数据正常应该从数据库中读
        //
        auth.inMemoryAuthentication()
//                创建用户名和密码和角色 v1 v2
                .withUser("jsxs").password("123456").roles("vip2","vip3")
                .and() // 我们可以通过 .and() 进行创建多个用户
                .withUser("root").password("123456").roles("vip1","vip2","vip3")
                .and()
                .withUser("guset").password("123456").roles("vip1");
    }
}

呈现500错误
在这里插入图片描述
如何解决500错误呢? 进行密码加密处理
有很多种加密的方式 : 在这里我们使用new BCryptPasswordEncoder() 加密

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        这些数据正常应该从数据库中读
        //
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//                创建用户名和密码和角色 v1 v2
                .withUser("jsxs").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and() // 我们可以通过 .and() 进行创建多个用户
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guset").password(new BCryptPasswordEncoder().encode("121788")).roles("vip1");
    }

完整效果的展示:
1.假如说我们登入的角色是vip1和vip2那么就只能访问vip1和vip2的页面
认证:就是给某个人赋予什么样的角色(名片)
授权: 就是某个人(角色/名片)能行驶什么样的权力

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

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     *
     * @param http  授权
     * @throws Exception
     */

//    链式编程
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        首页所有人可以访问,但是功能页只有对应有权限的人才能访问
        http.authorizeRequests()
                .antMatchers("/").permitAll()
//                这里意思就是说: 在level1下的所有页面只有vip1才能进行访问
                .antMatchers("/level1/**").hasRole("vip1")
//                这里意思就是说: 在level2下的所有页面只有vip2才能进行访问
                .antMatchers("/level2/**").hasRole("vip2")
//                这里意思就是说: 在level3下的所有页面只有vip3才能进行访问
                .antMatchers("/level3/**").hasRole("vip3");


//        没有权限默认跳到登入页面
             http.formLogin();
    }


    /**
     *
     * @param auth 认证
     * @throws Exception
     *
     * There is no PasswordEncoder mapped for the id "null"  如果我们直接写就会发生密码编码错误,这里是因为springBoot 版本的原因
     * 在Spring Security 5.0+ 新增了很多的加密方法
     *
     */
//    缓存 和 数据库 都可以尽心访问
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        这些数据正常应该从数据库中读
        //
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//                创建用户名和密码和角色 v1 v2
                .withUser("jsxs").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                .and() // 我们可以通过 .and() 进行创建多个用户
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                .and()
                .withUser("guset").password(new BCryptPasswordEncoder().encode("121788")).roles("vip1");
    }
}

我们登入的用户名是: jsxs ,密码是 123456.该角色的等级是vip2 vip3.所以我们访问不到.在这里因为我们已经登入过一次账户了,所以不会继续跳转到登入页面
在这里插入图片描述

在这里插入图片描述

(3).权限控制和注销

1、开启自动配置的注销的功能

//             注销
        http.logout();

2、我们在前端,增加一个注销的按钮,index.html 导航栏中

                <a class="item" th:href="@{/logout}">
                    <i class="sign-out icon"></i> 注销
                </a>

在这里插入图片描述
3、我们可以去测试一下,登录成功后点击注销,发现注销完毕会跳转到登录页面!
4、但是,我们想让他注销成功后,依旧可以跳转到首页,该怎么处理呢?

//             注销并且回退到指定路径 ---》我指向的是首页
        http.logout().logoutSuccessUrl("/");

5、测试,注销完毕后,发现跳转到首页OK
6、我们现在又来一个需求:用户没有登录的时候,导航栏上只显示登录按钮,用户登录之后,导航栏可以显示登录的用户信息及注销按钮!还有就是,比如kuangshen这个用户,它只有 vip2,vip3功能,那么登录则只显示这两个功能,而vip1的功能菜单不显示!这个就是真实的网站情况了!该如何做呢?

我们需要结合thymeleaf中的一些功能

sec:authorize=“isAuthenticated()”:是否认证登录!来显示不同的页面

导入thymeleaf-security结合的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、修改我们的 前端页面

导入命名空间
spring5 的命名空间这样写

xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

修改导航栏,增加认证判断

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<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.bootcdn.net/ajax/libs/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/qinjiang/css/qinstyle.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" th:href="@{/logout}">
                        <i class="sign-out icon"></i> 注销
                    </a>
                </div>
                <div sec:authorize="isAuthenticated()">
                    <!--如果已登入显示名字和角色-->
                    <a class="item">
                        <!--显示名字-->
                        用户名: <span sec:authentication="principal.username"></span>
                        <!--显示角色-->
                        角色: <span sec:authentication="principal.authorities"></span>
                    </a>
                </div>
            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security Study by 秦疆</h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">
<!--            假如说有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>
            <!--            假如说有vip2的权限-->
            <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>
            <!--            假如说有vip3的权限-->
            <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="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>

</body>
</html>

8、重启测试,我们可以登录试试看,登录成功后确实,显示了我们想要的页面;
9、如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题,我们可以将请求改为post表单提交,或者在spring security中关闭csrf功能;我们试试:在 配置中增加 http.csrf().disable();
SecurityConfig.java

        http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求

到目前为止已近实现了权限的访问资源不同,接下来还想实现对不同用户的页面显示内容不同:

关键代码:sec:authorize=“hasRole()” //判断当前的权限是否有这个权限直接加在想要设置权限的标签内即可

(4).记住我和首页定制

现在的情况,我们只要登录之后,关闭浏览器,再登录,就会让我们重新登录,但是很多网站的情况,就是有一个记住密码的功能,这个该如何实现呢?很简单

1、开启记住我功能

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
   //记住我
   http.rememberMe();
}

2、我们再次启动项目测试一下,发现登录页多了一个记住我功能,我们登录之后关闭 浏览器,然后重新打开浏览器访问,发现用户依旧存在!
在这里插入图片描述

思考:如何实现的呢?其实非常简单

我们可以查看浏览器的cookie
在这里插入图片描述
并且可以知道这个cookie的保存日期默认为14天

3、我们点击注销的时候,可以发现,spring security 帮我们自动删除了这个 cookie

4、结论:登录成功后,将cookie发送给浏览器保存,以后登录带上这个cookie,只要通过检查就可以免登录了。如果点击注销,则会删除这个cookie,具体的原理我们在JavaWeb阶段都讲过了,这里就不在多说了!

(5).定制登入页

现在这个登录页面都是spring security 默认的,怎么样可以使用我们自己写的Login界面呢?

1、在刚才的登录页配置后面指定 loginpage

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

2、然后前端也需要指向我们自己定义的 login登入成功请求的路径
login.html

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

在这里插入图片描述
图示解析:
在这里插入图片描述
4、这个请求提交上来,我们还需要验证处理,怎么做呢?我们可以查看formLogin()方法的源码!我们配置接收登录的用户名和密码的参数!

例子:比如下列就会出现报错:

在这里插入图片描述
在这里插入图片描述
这时就需要去config中设置一下,将对应的字段进行连接

 http.formLogin().usernameParameter("name").passwordParameter("pwd").loginPage("/toLogin");

在这里插入图片描述
5、在登录页增加记住我的选择框
添加前端页面显示

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

进行配置数据

http.rememberMe().rememberMeParameter("remember");

在这里插入图片描述

(十六)、Shiro

1.概述

(1).简介

Apache Shiro是一个强大且易用的Java安全框架

可以完成身份验证、授权、密码和会话管理

Shiro 不仅可以用在 JavaSE 环境中,也可以用在 JavaEE 环境中

官网: http://shiro.apache.org/
下载地址: https://github.com/apache/shiro/tree/1.4.x

(2).有哪些功能?

在这里插入图片描述

  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

(3)Shiro架构 (外部)

在这里插入图片描述

应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;其每个API的含义:

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面SecurityManager才是实际的执行者

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器

​ Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源

也就是说对于我们而言,最简单的一个Shiro应用:

应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入

(4).外部架构

在这里插入图片描述
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;

SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能

Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;

SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);

SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的

(5).认证流程

在这里插入图片描述
用户 提交 身份信息、凭证信息 封装成 令牌 交由 安全管理器 认证

2.快速入门

(1).ini插件安装和配置

将案例拷贝:

按照官网提示找到 快速入门案例
Github地址: https://github.com/apache/shiro/tree/main/samples/quickstart
从GitHub 的文件中可以看出这个快速入门案例是一个 Maven 项目

1.新建一个 Maven 工程,删除其 src 目录,将其作为父工程
2.在父工程中新建一个 Maven 模块

<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
        <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>
        </dependency>
    </dependencies>

复制快速入门案例 POM.xml 文件中的依赖 (版本号自选)

把快速入门案例中的 resource 下的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 文件

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
# =============================================================================
# Quickstart INI Realm configuration
#
# For those that might not understand the references in this file, the
# definitions are all based on the classic Mel Brooks' film "Spaceballs". ;)
# =============================================================================

# -----------------------------------------------------------------------------
# Users and their assigned roles
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
# -----------------------------------------------------------------------------
[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

ok需要下载ini插件,如果在setting中无法下载,就去官网下载对应版本的然后导入
在这里插入图片描述
导入quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName Quickstart
 * @Description TODO
 * @Author GuoSheng
 * @Date 2021/4/20  17:28
 * @Version 1.0
 **/
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):
//        已经过期----》 我们配置了最新的如下
        DefaultSecurityManager defaultSecurityManager=new DefaultSecurityManager();
        IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
        defaultSecurityManager.setRealm(iniRealm);


        // 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(defaultSecurityManager);

        // 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);
    }
}

因为原来的版本已经过期,我们配置最新的如下
在这里插入图片描述
执行一下main方法:

2023-02-01 15:59:55,744 INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 
2023-02-01 15:59:55,959 INFO [Quickstart] - Retrieved the correct value! [aValue] 
2023-02-01 15:59:55,960 INFO [Quickstart] - User [lonestarr] logged in successfully. 
2023-02-01 15:59:55,960 INFO [Quickstart] - May the Schwartz be with you! 
2023-02-01 15:59:55,960 INFO [Quickstart] - You may use a lightsaber ring.  Use it wisely. 
2023-02-01 15:59:55,960 INFO [Quickstart] - You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  Here are the keys - have fun! 

Process finished with exit code 0

(2).分析案例

1.通过 SecurityUtils 获取当前执行的用户 Subject

Subject currentUser = SecurityUtils.getSubject();

2.通过 当前用户拿到 Session,shiro的session

Session session = currentUser.getSession();

3.用 Session 存值取值

session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");

4.判断用户是否被认证

currentUser.isAuthenticated()

5.执行登录操作

currentUser.login(token);

6.打印其标识主体

currentUser.getPrincipal()

7.注销

currentUser.logout();

全部代码:

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName Quickstart
 * @Description TODO
 * @Author GuoSheng
 * @Date 2021/4/20  17:28
 * @Version 1.0
 **/
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:
        /**
         * 使用配置的领域、用户、角色和权限创建 Shiro SecurityManager 的最简单方法是使用简单的 INI 配置。
         * 我们将通过使用可以摄取.ini文件并返回 SecurityManager 实例的工厂来实现这一点:
         *
         */

        // Use the shiro.ini file at the root of the classpath
        // (file: and url: prefixes load from files and urls respectively):

//        已经过期----》 我们配置了最新的如下
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
        defaultSecurityManager.setRealm(iniRealm);


        // 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.
        /**
         * 对于此简单示例快速入门,请将 SecurityManager 设置为可作为 JVM 单一实例访问。大多数应用程序不会这样做,而
         * 是依赖于其容器配置或 Web 应用的 Web.xml。这超出了本简单快速入门的范围,
         * 因此我们只做最低限度的操作,以便你可以继续了解情况。
         */
        SecurityUtils.setSecurityManager(defaultSecurityManager);

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

        // get the currently executing user:
        /**
         *  Subject⭐ 对象输出:  获取当前的用户对象
         */
        Subject currentUser = SecurityUtils.getSubject();

        // Do some stuff with a Session (no need for a web or EJB container!!!)

        /**
         * Session : 通过当前用户拿到Session
         */
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Subject=>session [" + value + "]");
        }

        // let's login the current user so we can check against roles and permissions:

//        测试当前的用户是否被认证~
        if (!currentUser.isAuthenticated()) {
//            Token:  令牌,没有获取,随机
            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);
    }
}

3.Springboot集成Shiro

(1).创建环境

1.在刚刚的父项目中新建一个 springboot 模块

2.导入Thymeleaf驱动器依赖

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

3.在Template的包中创建首页并且利用Thymeleaf进行取值的操作

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
</body>
</html>

4.创建Controller测试是否成功

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {
    @RequestMapping({"/","/index.html","index"})
    public String index(Model model){
        model.addAttribute("msg","Hello,Shiro");
        System.out.println("第一步----》跳转index页面成功!!!");
        return "index";
    }
}

在这里插入图片描述
5.导入 SpringBoot 和 Shiro 整合包的依赖

<!--SpringBoot 和 Shiro 整合包-->
		<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring-boot-web-starter</artifactId>
			<version>1.6.0</version>
		</dependency

下面是编写配置文件
Shiro 三大要素

subject -> ShiroFilterFactoryBean ----当前用户
securityManager -> DefaultWebSecurityManager ----管理所有用户
Realm
实际操作中对象创建的顺序 : realm -> securityManager -> subject ----连接数据

6.创建config文件,编写UserRealm.java继承AuthorizingRealm并实现方法

package com.jsxs.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {
    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=》授权的方法");
        return null;
    }
    //  认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行了=》认证的方法");
        return null;
    }
}

可以两个方法的功能主要实现了授权和认证,我们添加两个输出一会看看效果

7.在Config文件夹下编写ShiroConfig.java文件,将三大Bean注入到spring中
①根据前面提供的顺序,先编写realm类:

    //创建 Realm 对象 ----》 Realm  需要自定义----> 01
    @Bean(name = "userRealm")
    public UserRealm  userRealm(){
        return new UserRealm();
    }

②创建用户管理,这里需要用到前面创建的realm,因此在realm之后进行编写

    //DefaultWebSecurityManger  ----》 SecurityManger ---->02
    @Bean(name = "getDefaultWebSecurityManager")  // 设置这个Bean的name 默认的是---》方法名
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm Realm){  // 通过注解指定是 public UserRealm  userRealm() 这个Bean
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        defaultWebSecurityManager.setRealm(Realm);  //进行关联 Realm

        return defaultWebSecurityManager;
    }

③创建subject,需要传入一个securityManager,因此最后进行编写(是不是很像套娃)

    // ShiroFilterFactoryBean   ----》 Subject
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ // 通过注解指定是 public DefaultWebSecurityManager getDefaultWebSecurityManager 这个Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);  //设置安全管理器

        return shiroFilterFactoryBean;
    }
package com.jsxs.config;

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   ----》 Subject
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ // 通过注解指定是 public DefaultWebSecurityManager getDefaultWebSecurityManager 这个Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);  //设置安全管理器

        /**
        *   anno :无需认证就可以访问
         *   authc : 必须认证了才能访问
         *   user :必须拥有 记住我 功能才有用
         *   perms : 拥有对某个资源的权限才能访问
         *   role : 拥有某个角色权限次啊能使用
         */
        Map<String, String> filterMap = new LinkedHashMap<>();//
        filterMap.put("/add","authc");
        filterMap.put("/add.html","authc");
        filterMap.put("/update","authc");
        filterMap.put("/update.html","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);  //这个一定要添加

        //设置登入的请求
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        return shiroFilterFactoryBean;

    }

    //DefaultWebSecurityManger  ----》 SecurityManger ---->02
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){  // 通过注解指定是 public UserRealm  userRealm() 这个Bean
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        defaultWebSecurityManager.setRealm(userRealm);  //进行关联 Realm

        return defaultWebSecurityManager;
    }
    //创建 Realm 对象 ----》 Realm  需要自定义----> 01
    @Bean(name = "userRealm")
    public UserRealm  userRealm(){
        return new UserRealm();
    }
}

8.设置路由映射的信息

package com.jsxs.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping({"/","jsxs"})
public class MyController {
    //  首页
    @RequestMapping({"/","/index.html","index"})
    public String index(Model model){
        model.addAttribute("msg","Hello,Shiro");
        System.out.println("第一步----》跳转index页面成功!!!");
        return "index";
    }
    //  添加
    @RequestMapping({"/add.html","/add"})
    public String add(){
        return "user/add";
    }
    // 修改
    @RequestMapping({"/update.html","/update"})
    public String update(){
        return "user/update";
    }
}

9.设置跳转路径

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<hr>
<a th:href="@{/add}">add</a> | <a th:href="@{/update}">update</a>
</body>
</html>

在这里插入图片描述
以上的链接无需认证就可以直接访问了

(2).实现登入拦截

        /**
        *   anno :无需认证就可以访问
         *   authc : 必须认证了才能访问
         *   user :必须拥有 记住我 功能才有用
         *   perms : 拥有对某个资源的权限才能访问
         *   role : 拥有某个角色权限次啊能使用
         */
    // ShiroFilterFactoryBean   ----》 Subject
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ // 通过注解指定是 public DefaultWebSecurityManager getDefaultWebSecurityManager 这个Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);  //设置安全管理器

        /**
        *   anno :无需认证就可以访问
         *   authc : 必须认证了才能访问
         *   user :必须拥有 记住我 功能才有用
         *   perms : 拥有对某个资源的权限才能访问
         *   role : 拥有某个角色权限次啊能使用
         */
        Map<String, String> filterMap = new LinkedHashMap<>();//
        filterMap.put("/add","authc");
        filterMap.put("/add.html","authc");
        filterMap.put("/update","authc");
        filterMap.put("/update.html","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);  //这个一定要添加

        //设置登入的请求
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        return shiroFilterFactoryBean;

图示解析:
在这里插入图片描述
当然在拦截请求中也支持通配符:如拦截指定/user下的所有请求:/user/*

(3).实现用户认证

实现用户认证需要去realm类的认证方法中去配置

这里我们先把用户名和密码写死,实际中是要去数据库中去取的

去到UserRealm.java中

package com.jsxs.config;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

public class UserRealm extends AuthorizingRealm {
    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=》授权的方法");
        return null;
    }

    //  认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=》认证的方法");
        String name = "admin";
        String password = "123456";
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        if (!userToken.getUsername().equals(name)) {
            return null;  // 抛出异常 Unknow
        }
        //  密码认证 ----》Shiro帮我们做
        return new SimpleAuthenticationInfo("",password,"");
    }
}

编写Controller中的登录方法:

    @RequestMapping("/login1")
    public String Login(String username,String password,Model model){
        // 获取当前的用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登入数据 ----> 封装成为令牌进行加密
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {  // 如果执行成功
            subject.login(token);   //执行登入的方法,如果没有异常就进行登入
            return "index";
        } catch (UnknownAccountException e) {   //用户名不存在
            model.addAttribute("msg","用户名错误");
            System.out.println("用户名错误");
            return "user/Login";
        }catch (IncorrectCredentialsException e) {   //密码不存在
            System.out.println("密码错误");
            model.addAttribute("msg","密码错误");
            return "user/Login";
        }
    }

在这里插入图片描述
在这里插入图片描述

(4).Shiro整合Mybatis

1.首先导入需要的所有的mavem依赖
①mysql-connect ②druid ③log4j ④mybtis-spring-boot-starter

        <!--     JDBC驱动器   -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!--数据库驱动-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--德鲁伊-->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!-- log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--        Mybatis 驱动包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>

2.编写配置文件application.properties或者application.yaml
application.Properties

# properties 这个文件负责对数据起别名和指向mapper.xml
mybatis.type-aliases-package=com.jsxs.pojo
mybatis.mapper-locations=classpath:mapper/*.xml

application.yaml

# yaml 负责编写数据源
spring:
  datasource:
    # 我们这里用户名和密码都不需要双引号 ,
    username: root
    password: 121788
    url: jdbc:mysql://localhost:3306/mybatis?userUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.jdbc.Driver
    # 我们这里指定的是阿里巴巴的 德鲁伊 的数据源
    type: com.alibaba.druid.pool.DruidDataSource

3.编写jsxs文件下的pojo,controller,service,mapper文件夹参考前面的springboot整合mybatis代码哦!
在这里插入图片描述
整合完毕的文件目录:
在这里插入图片描述
在mapper中添加了一个queryUserByName的方法:

package com.jsxs.mapper;

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

@Repository
@Mapper
public interface UserMapper {
    // 通过用户名查找用户
    public User queryByName(String name);

}

4.编写根目录下mapper/mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 1. 我们首先要绑定工作空间-->
<mapper namespace="com.jsxs.mapper.UserMapper">
    <!-- 2.  我们编写SQL语句   -->
    <!--   id是方法名, 返回类型是实体类 -->
   <select id="queryByName" parameterType="String" resultType="com.jsxs.pojo.User">
       select *from user where name=#{name}
   </select>
</mapper>

5.创建service层 并且编写UserService.java接口以及接口的实现类 UserServiceImpl.java

package com.jsxs.service;

import com.jsxs.pojo.User;

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

package com.jsxs.service;

import com.jsxs.mapper.UserMapper;
import com.jsxs.pojo.User;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService{

    @Resource
    private UserMapper userMapper;

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

6.进行测试数据
测试成功
7.修改成数据库信息
UserRealm.java 修改

package com.jsxs.config;

import com.jsxs.pojo.User;
import com.jsxs.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

import javax.annotation.Resource;

public class UserRealm extends AuthorizingRealm {
    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=》授权的方法");
        return null;
    }
    //我们需要添加业务层的支持
    @Resource
    UserService userService;
    //  认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=》认证的方法");
        // 链接虚拟的数据库
 //       String name = "admin";
  //      String password = "123456";
        //  链接真实的数据库
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        User user = userService.queryByName(userToken.getUsername());   //获取前端的名字,通过前端的截取的用户名进行查询
        if (user==null) {   // 假如说查询的user为空,那么就返回null
            return null;  // 抛出异常 UnknownAccount
        }
        //  密码认证 ----》Shiro帮我们做
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

ShiroConfig 无修改

package com.jsxs.config;

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   ----》 Subject
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ // 通过注解指定是 public DefaultWebSecurityManager getDefaultWebSecurityManager 这个Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);  //设置安全管理器

        /**
        *   anno :无需认证就可以访问
         *   authc : 必须认证了才能访问
         *   user :必须拥有 记住我 功能才有用
         *   perms : 拥有对某个资源的权限才能访问
         *   role : 拥有某个角色权限次啊能使用
         */
        Map<String, String> filterMap = new LinkedHashMap<>();//
        filterMap.put("/add","authc");
        filterMap.put("/add.html","authc");
        filterMap.put("/update","authc");
        filterMap.put("/update.html","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);  //这个一定要添加

        //设置登入的请求
        shiroFilterFactoryBean.setLoginUrl("/toLogin");
        return shiroFilterFactoryBean;

    }

    //DefaultWebSecurityManger  ----》 SecurityManger ---->02
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){  // 通过注解指定是 public UserRealm  userRealm() 这个Bean
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        defaultWebSecurityManager.setRealm(userRealm);  //进行关联 Realm

        return defaultWebSecurityManager;
    }
    //创建 Realm 对象 ----》 Realm  需要自定义----> 01
    @Bean(name = "userRealm")
    public UserRealm  userRealm(){
        return new UserRealm();
    }
}

测试一下bingo搞定
在这里插入图片描述

(5).授权功能的实现

去到ShiroConfig.java中,添加代码,注意要添加在(顺序)

        filterMap.put("/add","perms[user:add]");
        filterMap.put("/add.html","perms[user:add]");

之后我们执行I一下发现登陆后add标签也无权访问
显示401的意思就是未授权
在这里插入图片描述
当然正常的是要访问无权限页面的(授权要写在认证的前面)

package com.jsxs.config;

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   ----》 Subject
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){ // 通过注解指定是 public DefaultWebSecurityManager getDefaultWebSecurityManager 这个Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);  //设置安全管理器

        /**
        *   anno :无需认证就可以访问
         *   authc : 必须认证了才能访问
         *   user :必须拥有 记住我 功能才有用
         *   perms : 拥有对某个资源的权限才能访问
         *   role : 拥有某个角色权限次啊能使用
         */
        //  拦截
        //  认证才能访问
        Map<String, String> filterMap = new LinkedHashMap<>();//
        filterMap.put("/add","perms[user:add]");
        filterMap.put("/add.html","perms[user:add]");
        filterMap.put("/update","authc");
        filterMap.put("/update.html","authc");
        // 授权才能访问

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);  //这个一定要添加

        //假如说没有认证/授权我们就会走这个路径
        shiroFilterFactoryBean.setLoginUrl("/toLogin");  //假如说没有权限/认证我们就会走这个路径
        
        //假如说此用户没有授权我们就会走如下的页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        return shiroFilterFactoryBean;

    }

    //DefaultWebSecurityManger  ----》 SecurityManger ---->02
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){  // 通过注解指定是 public UserRealm  userRealm() 这个Bean
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        defaultWebSecurityManager.setRealm(userRealm);  //进行关联 Realm

        return defaultWebSecurityManager;
    }
    //创建 Realm 对象 ----》 Realm  需要自定义----> 01
    @Bean(name = "userRealm")
    public UserRealm  userRealm(){
        return new UserRealm();
    }
}

在这里插入图片描述
接下来我们去UserRealm中给添加权限,注意不要导错类SimpleAuthorizationInfo

    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=》授权的方法");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");   //给用户添加这个权限
        return info;
    }
package com.jsxs.config;

import com.jsxs.pojo.User;
import com.jsxs.service.UserService;
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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

import javax.annotation.Resource;

public class UserRealm extends AuthorizingRealm {
    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=》授权的方法");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermission("user:add");   //给用户添加这个权限
        return info;
    }
    //我们需要添加业务层的支持
    @Resource
    UserService userService;
    //  认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=》认证的方法");
        // 链接虚拟的数据库
 //       String name = "admin";
  //      String password = "123456";
        //  链接真实的数据库
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        User user = userService.queryByName(userToken.getUsername());   //获取前端的名字,通过前端的截取的用户名进行查询
        if (user==null) {   // 假如说查询的user为空,那么就返回null
            return null;  // 抛出异常 UnknownAccount
        }
        //  密码认证 ----》Shiro帮我们做
        return new SimpleAuthenticationInfo("",user.getPwd(),"");
    }
}

在这里插入图片描述
诶,接下来所有用户就都有这个权限

但是不想让所有用户都有某个权限要怎么实现呢?

首先先去数据表中添加一个权限字段
在这里插入图片描述
并且要记得修改pojo实体类

package com.jsxs.pojo;

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

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

ok数据层完毕,接下来要思考,我们想要获取到用户的权限,但是获取用户是在刚刚认证中通过userservice获取的,
在认证里面查到的东西我们怎么放到授权中去呢?
想到了两个:①session ②用户当前对象的属性来取get方法或(点属性)
我们在认证中,最后new了一个SimpleAuthenticationInfo的当前认证对象,之前里面要传递三个参数分别为:
(资源,密码,realmName)

我们把获取到的user传入到资源中去。

return new SimpleAuthenticationInfo(user,user.getPwd(),"");

这样一来,user这个资源就传递到了当前用户subject整体资源中,通过当前subject获取资源来获取到这个user

所以在授权的功能中要先获取subject当前用户

Subject subject = SecurityUtils.getSubject();

然后通过subject获取到资源

User currentUser = (User) subject.getPrincipal();

再给当前用户添加user中对应的权限名,注意方法名是addStringPermission权限

 info.addStringPermission(currentUser.getPerms());

最后返回当前权限info

package com.jsxs.config;

import com.jsxs.pojo.User;
import com.jsxs.service.UserService;
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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

import javax.annotation.Resource;

public class UserRealm extends AuthorizingRealm {
    //  授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行了=》授权的方法");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //info.addStringPermission("user:add");   //给所有用户添加这个权限

        //拿到当前登入的这个对象
        Subject subject = SecurityUtils.getSubject();
        // 获取共享的数据--->拿到User对象
        User currentUser = (User) subject.getPrincipal();
        // 通过查询当前的数据库信息,设定当前用户的权限
        info.addStringPermission(currentUser.getPerms());
        return info;
    }
    //我们需要添加业务层的支持
    @Resource
    UserService userService;
    //  认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了=》认证的方法");
        // 链接虚拟的数据库
 //       String name = "admin";
  //      String password = "123456";
        //  链接真实的数据库
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        User user = userService.queryByName(userToken.getUsername());   //获取前端的名字,通过前端的截取的用户名进行查询
        if (user==null) {   // 假如说查询的user为空,那么就返回null
            return null;  // 抛出异常 UnknownAccount
        }
        //  密码认证 ----》Shiro帮我们做
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");  //------->这里我们为第一个参数赋值,目的就是我们可以共享这个资源
    }
}

退出功能就和之前security的logout一样,设置一下退出的路径就可

好现在基本需求都完了,但是想和之前security一样实现没有权限的就不要显示,就需要和thymeleaf结合

(6).Shiro和thymeleaf整合

首先要导入shrio和thymeleaf结合的依赖

<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

ok接下来就要去前端页面编写标签了,之前在security中用到的是sec,这里用的shiro,当然也要导入命名空间

下面是spring-security的命名空间

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

shiro的:

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

或则
推荐

xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

前端代码:
假如说存在权限add我们就显示,不存在的话就不显示

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"
>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<p>
    <a th:href="@{/toLogin}">登入</a>
</p>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
    <a th:href="@{/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/update}">update</a>
</div>
</body>
</html>

在这里插入图片描述

登录按钮无权限时显示:

<div shiro:notAuthenticated>
    <a th:href="@{/toLogin}">登录</a>
</div>

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/198033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Echarts的Y轴添加定值横线的示例

第010个点击查看专栏目录Echarts折线图的y轴要画一条横线&#xff0c;主要是在series中设置markLine的图表标线参数&#xff0c;具体的参考源代码。文章目录示例效果示例源代码&#xff08;共142行&#xff09;相关资料参考专栏介绍示例效果 示例源代码&#xff08;共142行&…

怎么在Windows电脑更新 DirectX ?

玩游戏的人应该都对DirectX不陌生&#xff0c;它可以提高游戏或多媒体程序的运行效率&#xff0c;增强3d图形和声音效果。但很多人都不知道DirectX该如何更新&#xff0c;这篇文章将以Win10为例&#xff0c;教大家怎么在电脑上更新DirectX。 一、检查当前DirectX版本 如果你不…

简单聊一聊组件封装

封装一个思维导图组件 最近封装了一个简单的思维导图组件&#xff0c;在此简单记录一下心里历程 组件样式 组件结构设计 节点之间的线分成三部分&#xff0c;分别是竖线左边的横线A、竖线B、竖线右边的横线C&#xff0c;所以一个节点可以包含以下几个元素&#xff1a; 横线…

VBA提高篇_18 VBA代码录制优化Select(tion)及表格合并Merge(cells()/Rows()/Columns()

文章目录1. Cells(1,1)2. Rows(Str)和Columns(Str)3. VBA合并单元格3.1 Range.MergeCells属性:3.2 Range.Merge/UnMerage属性:3.3 Range.Merge(参数True/False)3.4 操作合并/取消合并单元格的两种方法4. Select / Selection 和 录制宏的代码优化4.1 Select / Selection4.2 录制…

anconda的pip下载包出现的问题

问题一: 在anconda里面如何创建新的python环境(也就是更换新的python版本) 1.先打开anconda软件,创建需要的环境 2. 环境创建好之后,去pycharm里面进行配置解释器 3. 这样就可以用了 问题二:pip的安装软件时出现包找不到的问题? 注意:因为我们刚刚创建了一个python环境,等…

Python基于已知的分幅条带号筛选出对应遥感影像文件的方法

本文介绍基于Python语言&#xff0c;结合已知研究区域中所覆盖的全部遥感影像的分幅条带号&#xff0c;从大量的遥感影像文件中筛选落在这一研究区域中的遥感影像文件的方法。 首先&#xff0c;先来明确一下本文所需实现的需求。现已知一个研究区域&#xff08;四川省&#xff…

【C++】C++入门 函数重载

前言 自然语言中&#xff0c;一个词可以有多重含义&#xff0c;人们可以通过上下文来判断该词真实的含义&#xff0c;即该词被重载了。 函数重载一、函数重载定义二、函数重载的条件1. 参数类型不同2. 参数个数不同3. 参数类型顺序不同三、函数重载的原理--名字修饰(name Mangl…

怎么把网页变成灰色?怎么让头像或某一部分不变灰色?filter/backdrop-filter/mix-blend-mode/svg/grayscale(1)

在国家公祭日&#xff0c;我们哀悼沉思&#xff0c;势要勿忘国耻振兴中华&#xff1b;在国家重要人物逝世后&#xff0c;举国哀悼&#xff1b;这些时段很多网站都会积极呼应&#xff0c;给与自己的网页设置成灰色&#xff1b;那给网页设置成灰色是经过怎样的设置来实现的呢&…

利用pandas_udf加速机器学习任务

note pandas udf和python udf区别&#xff1a;前者向量化是在不同partition上处理pandas_udf使用panda API来处理分布式数据集&#xff0c;而toPandas()将分布式数据集转换为本地数据&#xff0c;然后使用pandas进行处理&#xff0c;如果Pyspark的dataframe非常大&#xff0c;…

快速排序和归并排序哪个快?

两个排序的基本思想都是分治&#xff08;分而治之&#xff09;,实现一般都使用递归实现。1.快速排序双边指针&#xff08;交换法&#xff09;&#xff1a;记录分界值 &#xff0c;创建左右指针&#xff08;记录下标&#xff09;。以第一个元素为分界值&#xff0c;先从右向左找…

C语言指针变量作为函数参数

在C语言中&#xff0c;函数的参数不仅可以是整数、小数、字符等具体的数据&#xff0c;还可以是指向它们的指针。用指针变量作函数参数可以将函数外部的地址传递到函数内部&#xff0c;使得在函数内部可以操作函数外部的数据&#xff0c;并且这些数据不会随着函数的结束而被销毁…

ocelot的单节点解决方案

ocelot的问题前面我们解决了consul的单节点的集群扩建。这里讨论如果在多客户端访问时&#xff0c;单网关也会有瓶颈。如果单台挂掉&#xff0c;那么也会麻烦&#xff0c;所以根据项目需要解决问题。ocelot多节点部署最简单的粗暴解决&#xff0c;多部署几台网关。但是我们需要…

mac环境和windows环境下GeoServer如何安装部署

geoserver是从事GIS行业都应当了解的一个gis服务器。 所以说学会geoserver是一个非常必要的事情。那么这篇文章呢我就带着大家来一起学习如何在Mac机器上和windows机器上安装并部署Geoserver。 首先不管是哪个环境我们都需要去官网上先下载安装包。 第一步我们要去geoserver的…

【计算机组成原理】y = a * b + c 的执行具体流程

文章目录1.2.2 认识各个硬件部件1. 主存储器的基本组成2. 运算器的基本组成3. 控制器的基本组成4. 计算机的工作流程1.2.2 认识各个硬件部件 1. 主存储器的基本组成 存储体&#xff1a;存放数据和指令地址寄存器&#xff1a;用来存放读取存储体数据时存放的具体位置数据寄存器…

12、字符(串)输入、输出

目录 一、字符数据输入&#xff0f;输出 1. 字符数据输入 2. 字符数据输出 二、字符串输入&#xff0f;输出 1. 字符串输入函数 2. 字符串输出函数 一、字符数据输入&#xff0f;输出 1. 字符数据输入 字符数据输入使用的是getchar函数&#xff0c;其作用是从终端&…

前端遇到的问题

inputs-outputs https://angular.cn/guide/inputs-outputs 用于父组件与子组件间的值传递 在项目中引入核心组件 父组件在其html界面绑定属性 在子组件里通过Input传递值 具体里: 使用默认规则数组绑定固定值(比如id)的错误 这里两个ts文件之间传值,采用了一个get函数,…

微信小程序 view组件的基本使用

1.view基本理论 能看图就尽量减少文字提示&#xff0c;从图书可以看出ABC是纵向排列的。 为什么会纵向排列而不是横向排列&#xff0c;那是因为view是块元素&#xff0c;能占满整一行。 怎么让view块元素横向并排呢&#xff1f; 向上图一样横向排列&#xff0c;接下来教学从0…

Kerberos协议与认证数据包分析

Kerberos协议 Kerberos是一种在开放的非安全网络中认证并识别用户身份信息的方法, 它旨使用密钥加密技术为客户端/服务端应用程序提供强身份认证。 目前主流的Kerberos版本是2005年的RFC4120标准的Kerberos v5, Windows、Linux和MacOs均支持Kerberos协议。Kerberos基础 Kerbe…

Quartz入门看这一篇文章就够了

第一章 Quartz简介 第一节 Quartz是什么? 1Quartz [kwɔːts]是一个完全由Java编写的开源的作业调度框架第二节 Quartz可以用来做什么? 比如说买火车票下单之30分钟之后,查看是否付款付款完成之后,在乘车日期的时候是否乘车或者每个月1号扣房贷每个月20号自动还信用卡想定时…

为什么要使用采购管理软件 采购管理软件推荐

在企业发展道路上&#xff0c;采购部门是一个非常重要的组织&#xff0c;它会直接影响到企业的经济效益。企业在采购活动中&#xff0c;如果还是采用传统线下的采购方式&#xff0c;那么很容易导致采购信息不集中&#xff0c;效率低&#xff0c;出错率高&#xff0c;最终损害企…