Registro diario: SpringBoot integra SpringSecurity (separación delantera y trasera) + JWT + Redis

SpringBoot integra SpringSecurity

Esta publicación de blog no explica el principio, pero proporciona directamente el código para realizar la separación de la integración de SpringBoot de SpringSecurity + JWT + Redis antes y después.

1. Construya el proyecto

1. Construya el proyecto springboot

Si no sabe cómo crear un proyecto Springboot, puede consultar la publicación del blog que escribí a continuación. Si puede crear un proyecto Springboot, simplemente omita este paso.

Sección 1: Creación de proyectos idea padre-hijo
Sección 2: integración de Springboot Mybatis (introducción)
Sección 3: integración de Springboot Mybatis (@Select of mapper)
Sección 4: integración de Springboot Mybatis (controlador + servicio + mapeador) Proceso completo
Sección 5: Springboot integra Mybatis (transacción declarativa @Transactional)

2. Importar dependencias

dependencias centrales

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

<!--redis 依赖-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!--引入jwt-->
<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>3.11.0</version>
</dependency>

<!-- fastjson -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.83</version>
</dependency>

otras dependencias

<dependencies>

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

        <!--单元测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <!--简化get set等-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.yaml/snakeyaml -->
        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.25</version>
        </dependency>

        <!-- mybatis 支持 SprigBoot -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <!-- mysql 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

        <!--阿里巴巴数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.20</version>
        </dependency>

        <!--MyBatis Plus 的依赖包 ,比如可以直接使用他封装好的sql语句,selectOne等-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.2</version>
        </dependency>

        <!--常用工具类 比如数字处理NumberUtils、字符串处理类StringUtils、日期类DateUtils-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>

    </dependencies>

3. Configurar yml

No existe ninguna configuración por seguridad, principalmente para configurar la base de datos y redis. No hay tutoriales de instalación para mysql y redis, simplemente instálelo usted mismo, es muy simple.

server:
  port: 8081

spring:
  profiles:
    active: dev
  datasource:
    url: jdbc:mysql://localhost:3306/study-demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.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

  # redis配置
  redis:
    host: localhost
    port: 6379
    #我没有给redis设置账号和密码
    #username: guest
    #password: guest


# 日志输出配置
logging:
  level:
    root: info


#jwt 自定义配置
Jwt:
  #jwt签名私钥
  secretKey: sdsdsd23232323

4. Inicie el proyecto

Ver el resultado Usando la contraseña de seguridad generada: xxxx significa que Springboot ha integrado SpringSecurity con éxito
inserte la descripción de la imagen aquí
Servicio de acceso http://127.0.0.1:8081/ Esta es mi propia IP y puerto. Verás el salto a la página que viene con SpringSecurity.

  1. Usuario de cuenta predeterminado
  2. La contraseña es una cadena de MD5 487eb5c9-0d7e-4a06-b9d5-1c0235b4ca2a generada por la consola.

inserte la descripción de la imagen aquí

2. Cree una tabla de roles y permisos de usuario relevantes.

Por conveniencia, solo construí la tabla de usuarios, la tabla de permisos y la tabla de asociación de permisos de usuarios.

1. Cree el juego de caracteres y la clasificación de la base de datos.

inserte la descripción de la imagen aquí

2. Estructura de la tabla de usuarios

CREATE TABLE `sys_user` (
  `user_id` int(8) NOT NULL AUTO_INCREMENT,
  `account` varchar(32) DEFAULT NULL COMMENT '账号',
  `user_name` varchar(32) DEFAULT NULL COMMENT '用户名',
  `password` varchar(64) DEFAULT NULL COMMENT '用户密码',
  `last_login_time` datetime DEFAULT NULL COMMENT '上一次登录时间',
  `enabled` tinyint(1) DEFAULT '1' COMMENT '账号是否可用。默认为1(可用)',
  `account_not_expired` tinyint(1) DEFAULT '1' COMMENT '是否过期。默认为1(没有过期)',
  `account_not_locked` tinyint(1) DEFAULT '1' COMMENT '账号是否锁定。默认为1(没有锁定)',
  `credentials_not_expired` tinyint(1) DEFAULT NULL COMMENT '证书(密码)是否过期。默认为1(没有过期)',
  `create_time` datetime DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户表';

3. Estructura de la tabla de permisos

CREATE TABLE `sys_permission` (
  `permission_id` int(8) NOT NULL,
  `permission_code` varchar(32) DEFAULT NULL,
  `permission_name` varchar(32) DEFAULT NULL,
  `url` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`permission_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='权限表';

4. Estructura de la tabla de asociación de permisos y usuarios

CREATE TABLE `sys_user_permission_relation` (
  `user_permission_relation_id` int(8) NOT NULL,
  `user_id` int(8) DEFAULT NULL,
  `permission_id` int(8) DEFAULT NULL,
  PRIMARY KEY (`user_permission_relation_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户权限关联表';

5. Insertar datos de prueba

INSERT INTO `sys_user` VALUES (1, '888', '小张', '$2a$10$2mO7/KcswzO3SQU7TX3fiOfkypjdOn3tLBezV/tf2IJXdQu1BpxK2', '2023-08-16 09:45:53', 1, 1, 1, 1, '2023-08-09 17:49:20', '2023-08-09 17:49:22');

INSERT INTO `sys_permission` VALUES (1, 'sys:queryUser', '查询用户', '/getUser');

INSERT INTO `sys_user_permission_relation` VALUES (1, 1, 1);

3. Crear clases de entidad y Mapper

1、Usuario del sistema

Clase de entidad de usuario


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

@Data
@Accessors(chain = true)//链式; 存取器。通过该注解可以控制getter和setter方法的形式。
@TableName("sys_user")
public class SysUser implements Serializable {
    
    

    private static final long serialVersionUID = 915478504870211231L;

    @TableId(value = "user_id", type = IdType.ID_WORKER)
    private Integer userId;

    //账号
    private String account;

    //用户名
    private String userName;

    //用户密码
    private String password;

    //上一次登录时间
    private Date lastLoginTime;

    //账号是否可用。默认为1(可用)
    private Boolean enabled;

    //是否过期。默认为1(没有过期)
    private Boolean accountNotExpired;

    //账号是否锁定。默认为1(没有锁定)
    private Boolean accountNotLocked;

    //证书(密码)是否过期。默认为1(没有过期)
    private Boolean credentialsNotExpired;

    //创建时间
    private Date createTime;

    //修改时间
    private Date updateTime;

}

mapeador de usuario


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.study.test.web.entity.SysUser;//这是你自己实体类放的路径,记得修改下


public interface SysUserMapper extends BaseMapper<SysUser> {
    
    


}

2、Permiso del sistema

clase de entidad de permiso

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@Accessors(chain = true)//链式; 存取器。通过该注解可以控制getter和setter方法的形式。
@TableName("sys_permission")
public class SysPermission implements Serializable {
    
    

    @TableId(value = "permission_id", type = IdType.ID_WORKER)
    private Integer permissionId;

    private String permissionCode;

    private String permissionName;

    private String url;
}

mapeador de permisos


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.study.test.web.entity.SysPermission;//这是你自己实体类放的路径,记得修改下
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface SysPermissionMapper extends BaseMapper<SysPermission> {
    
    


    /**
     * 通过用户id查询用户的权限数据
     * @param userId
     * @return
     */
    @Select({
    
    "<script>"+
            " SELECT p.* FROM"+
            " sys_user u"+
            " LEFT JOIN sys_user_permission_relation r ON u.user_id = r.user_id"+
            " LEFT JOIN sys_permission p on r.permission_id = p.permission_id"+
            " WHERE u.user_id = #{userId}"+
            "</script>"

    })
    List<SysPermission> selectPermissionList(@Param("userId") Integer userId);
}

3、Relación de permisos de usuario del sistema

Clase de entidad asociada a usuarios y permisos

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)//链式; 存取器。通过该注解可以控制getter和setter方法的形式。
@TableName("sys_user_permission_relation")
public class SysUserPermissionRelation  {
    
    

    @TableId(value = "user_permission_relation_id", type = IdType.ID_WORKER)
    private Integer userPermissionRelationId;

    private Integer userId;

    private Integer permissionId;
}

Mapeador de asociaciones de usuarios y permisos

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.study.test.web.entity.SysUserPermissionRelation;//这是你自己实体类放的路径,记得修改下

public interface SysUserPermissionRelationMapper extends BaseMapper<SysUserPermissionRelation> {
    
    

}

4. Configurar @MapperScan

No olvide configurar @MapperScan; de lo contrario, no se podrá encontrar el asignador y StudyApplication es el nombre de mi clase de inicio.


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.study.test.web.mapper")//这要修改为你自己mapper放的路径
public class StudyApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(StudyApplication.class, args);
    }

}

Una vez completadas estas tareas, recuerde reiniciar el proyecto para ver si tiene éxito. Si no tiene éxito, debe depurarlo usted mismo.

Cuatro, configuración de Redis

Redis aquí es principalmente para realizar la integración de JWT con Spring Security y realizar el inicio de sesión simbólico de redis.

1、RedisConfiguración

package com.study.test.common.config;//这是我存放redisconfig的路径

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

//RedisConfig 配置类
@Configuration
public class RedisConfig {
    
    


    //解决redis可视化乱码问题,方便调试查找问题
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
    
    
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        redisTemplate.setKeySerializer(stringRedisSerializer); // key的序列化类型

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }


}

2. RedisUtil

herramienta redis para resolver el problema de la duplicación de código usando redis

package com.study.test.common.utils;//这是我存放redis工具的路径

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
@Order(-1)
public final class RedisUtil {
    
    

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return 0
     */

    public boolean expire(String key, long time) {
    
    
        try {
    
    
            if (time > 0) {
    
    
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
    
    
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
    
    
        try {
    
    
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
    
    

        if (key != null && key.length > 0) {
    
    
            if (key.length == 1) {
    
    
                redisTemplate.delete(key[0]);
            } else {
    
    
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
    
    
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
    
    
        try {
    
    
            if (time > 0) {
    
    
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
    
    
                set(key, value);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 递增
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
    
    

        if (delta < 0) {
    
    
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }


    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
    
    

        if (delta < 0) {
    
    
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */

    public Object hget(String key, String item) {
    
    
        return redisTemplate.opsForHash().get(key, item);
    }


    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */

    public Map<Object, Object> hmget(String key) {
    
    
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */

    public boolean hmset(String key, Map<String, Object> map) {
    
    
        try {
    
    
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
    
    
        try {
    
    
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
    
    
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * 0
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
    
    
        try {
    
    
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
    
    
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
    
    
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
    
    
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
    
    
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
    
    
        return redisTemplate.opsForHash().increment(key, item, -by);
    }

    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
    
    

        try {
    
    
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
    
    

        try {
    
    
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
    
    
        try {
    
    
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
    
    
        try {
    
    
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================list=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
    
    

        try {
    
    
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return 0
     */
    public long lGetListSize(String key) {
    
    
        try {
    
    
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头, 第二个元素,依次类推;index<0时,-,表尾,-倒数第二个元素,依次类推
     * @return 0
     */
    public Object lGetIndex(String key, long index) {
    
    
        try {
    
    
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return 0
     */
    public boolean lSet(String key, List<Object> value, long time) {
    
    
        try {
    
    
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return 0
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
    
    
        try {
    
    
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
    
    
        try {
    
    
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            return 0;
        }
    }
}

Después de agregar la configuración de Redis, recuerde reiniciar el proyecto para ver si hay algún problema.

5. Unificar el formato de devolución global y manejar las excepciones del sistema.

1. Unificar el formato de devolución global

Código API

package com.study.test.common.api;//这是我存放ApiCode 的目录


public enum ApiCode {
    
    

    SUCCESS(200, "成功"),

    SYSTEM_ERROR(500, "操作失败"),

    NOT_FOUND(404,"未找到该资源");

    private final int code;
    private final String msg;

    ApiCode(final int code, final String msg) {
    
    
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
    
    
        return code;
    }

    public String getMsg() {
    
    
        return msg;
    }

}

ApiResultado

package com.study.test.common.api;//这是我存放ApiResult的路径


import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
public class ApiResult<T> implements Serializable {
    
    

    private static final long serialVersionUID = 1L;

    //状态码
    private int code;

    //返回数据
    private T data;

    //结果信息
    private String message;

    //时间字符串
    private String time;

    private ApiResult(){
    
    

    }

    //定义成功的构造器
    private ApiResult(T data){
    
    
        this.code = ApiCode.SUCCESS.getCode();
        this.message = ApiCode.SUCCESS.getMsg();
        this.data = data;
        this.time = LocalDateTime.now().toString();

    }

    private ApiResult(ApiCode apiCode){
    
    
        this.code = apiCode.getCode();
        this.message = apiCode.getMsg();
        this.time = LocalDateTime.now().toString();
    }

    private ApiResult(int code,String msg){
    
    
        this.code = code;
        this.message = msg;
        this.time = LocalDateTime.now().toString();
    }

    private ApiResult(ApiCode apiCode,T data){
    
    
        this.code = apiCode.getCode();
        this.message = apiCode.getMsg();
        this.data = data;
        this.time = LocalDateTime.now().toString();
    }

    /**
     * 成功的时候调用
     * @param data
     * @return
     * @param <T>
     */
    public static <T> ApiResult<T> success(T data){
    
    
        return new ApiResult(data);
    }

    /**
     * 根据状态返回结果
     * @param apiCode
     * @return
     * @param <T>
     */
    public static <T> ApiResult<T> build(ApiCode apiCode){
    
    
        return new ApiResult(apiCode);
    }

    /**
     * 根据code和msg返回结果
     * @param code
     * @param msg
     * @return
     * @param <T>
     */
    public static <T> ApiResult<T> build(int code,String msg){
    
    
        return new ApiResult(code,msg);
    }

    /**
     * 根据状态和数据返回结果
     * @param apiCode
     * @param data
     * @return
     * @param <T>
     */
    public static <T> ApiResult<T> build(ApiCode apiCode,T data){
    
    
        return new ApiResult(apiCode,data);
    }


    /**
     * 返回异常结果
     * @param code
     * @param msg
     * @return
     * @param <T>
     */
    public static <T> ApiResult<T> error(int code,String msg){
    
    
        return new ApiResult(code,msg);
    }
}



2. Manejo de excepciones del sistema global

1. Clase de excepción personalizada

package com.study.test.common.exception;//这是我异常类存放的目录


import com.study.test.common.api.ApiCode;

import lombok.Data;


/**
 * 自定义异常类
 */
@Data
public class BusinessException extends RuntimeException{
    
    

    private int code;

    private String msg;


    public BusinessException(ApiCode apiCode) {
    
    
        super(apiCode.getMsg());
        this.code = apiCode.getCode();
        this.msg = apiCode.getMsg();
    }


}



2. Capture excepciones globales

package com.study.test.common.exception;//这是我存放全局异常的目录

import com.study.test.common.api.ApiCode;
import com.study.test.common.api.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    

    //自定义异常
    @ExceptionHandler(BusinessException.class)
    public ApiResult systemExceptionHandler(BusinessException e) {
    
    
        log.error("BusinessException全局异常:{}",e);
        return ApiResult.error(e.getCode(), e.getMsg());
    }

    //系统异常
    @ExceptionHandler(Exception.class)
    public ApiResult exceptionHandler(Exception e) {
    
    
        log.error("Exception全局异常:{}",e);
        return ApiResult.error(ApiCode.SYSTEM_ERROR.getCode(), e.getMessage());
    }


}



Recuerda reiniciar para ver si hay algún problema.

6. Herramientas

1. Clase de herramienta JwtUtils

package com.study.test.common.utils;//这是我存放jwt工具类的目录

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;


@Component
public class JwtUtils {
    
    
    private static String secretKey;

    private static Integer amount = 1800;//jwt的过期周期/秒 默认30分钟

    @Value("${Jwt.secretKey}")
    public void secretKey(String secretKey) {
    
    
        JwtUtils.secretKey =  secretKey;
    }


    /**
     * 创建token
     * @param payloadMap 存储的内容,自定义,一般是用户id
     * @return
     */
    public static String generateToken(Map<String, String> payloadMap) {
    
    

        HashMap headers = new HashMap();

        JWTCreator.Builder builder = JWT.create();

        //定义jwt过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, amount);


        //payload
        payloadMap.forEach((k, v) ->{
    
    
            builder.withClaim(k, v);
        });


        // 生成token
        String token = builder.withHeader(headers)//header
                //.withClaim("second",amount)//jwt的过期周期/秒,可以用于jwt快过期的时候自动刷新
                .withExpiresAt(instance.getTime())//指定令牌的过期时间
                .sign(Algorithm.HMAC256(secretKey));//签名


        return token;
    }


    /**
     * 校验token是否合法
     * @param token
     * @return
     */
    public static DecodedJWT verifyToken(String token) {
    
    

        /*
        如果有任何验证异常,此处都会抛出异常
        SignatureVerificationException 签名不一致异常
        TokenExpiredException 令牌过期异常
        AlgorithmMismatchException 算法不匹配异常
        InvalidClaimException 失效的payload异常
        */
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);



        return decodedJWT;
    }

    /**
     * 获取token信息
     * @param token
     * @return
     */
    public static DecodedJWT getTokenInfo(String token) {
    
    
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
        return decodedJWT;
    }

    /**
     * 获取token信息方法
     */
    /*public static Map<String, Claim> getTokenInfo(String token) {

        return JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token).getClaims();
    }*/
}


2. Clase de herramienta WebUtils

package com.study.test.common.utils;//这是我存放web工具类的目录

import javax.servlet.http.HttpServletResponse;

public class WebUtils {
    
    
    public static String rednerString(HttpServletResponse response, String content) {
    
    
        try{
    
    
            response.setStatus(200);
            response.setContentType("application/json;charset=utf-8");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().print(content);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return null;
    }
}

7. Configuración entre dominios

package com.study.test.common.config;//这是我存放跨域的目录


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

//跨越请求配置类
@Configuration
public class CorsConfig {
    
    

    private CorsConfiguration buildConfig() {
    
    
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        //  你需要跨域的地址  注意这里的 127.0.0.1 != localhost
        // * 表示对所有的地址都可以访问
        corsConfiguration.addAllowedOrigin("*");  // 1
        //  跨域的请求头
        corsConfiguration.addAllowedHeader("*"); // 2
        //  跨域的请求方法
        corsConfiguration.addAllowedMethod("*"); // 3
        //加上了这一句,大致意思是可以携带 cookie
        //最终的结果是可以 在跨域请求的时候获取同一个 session
        corsConfiguration.setAllowCredentials(true);
        return corsConfiguration;
    }
    @Bean
    public CorsFilter corsFilter() {
    
    

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //配置 可以访问的地址
        source.registerCorsConfiguration("/**", buildConfig());
        return new CorsFilter(source);
    }

}

8. Una vez finalizados estos, se completa el trabajo preparatorio del anteproyecto.

Sólo consulte
inserte la descripción de la imagen aquí

9. Integre la seguridad de Spring

1. Clase de herramienta de excepción de autenticación de Spring Security

AuthExceptionUtil

package com.study.test.security.utils;//我存放AuthExceptionUtil的目录


import com.study.test.common.api.ApiResult;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.AuthorizationServiceException;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.csrf.CsrfException;

//认证异常工具类
public class AuthExceptionUtil {
    
    

    public static ApiResult getErrMsgByExceptionType(AuthenticationException e) {
    
    

        if (e instanceof LockedException) {
    
    

            return ApiResult.error(1100,"账户被锁定,请联系管理员!");

        } else if (e instanceof CredentialsExpiredException) {
    
    
            return ApiResult.error(1105,"用户名或者密码输入错误!");

        }else if (e instanceof InsufficientAuthenticationException) {
    
    
            return ApiResult.error(403,"请登录!");

        } else if (e instanceof AccountExpiredException) {
    
    
            return ApiResult.error(1101, "账户过期,请联系管理员!");

        } else if (e instanceof DisabledException) {
    
    
            return ApiResult.error(1102, ("账户被禁用,请联系管理员!"));

        } else if (e instanceof BadCredentialsException) {
    
    
            return ApiResult.error(1105, "用户名或者密码输入错误!");

        }else if (e instanceof AuthenticationServiceException) {
    
    

            return ApiResult.error(1106, "认证失败,请重试!");
        }

        return ApiResult.error(1200, e.getMessage());
    }

    public static ApiResult getErrMsgByExceptionType(AccessDeniedException e) {
    
    

        if (e instanceof CsrfException) {
    
    

            return ApiResult.error(-1001, "非法访问跨域请求异常!");
        } else if (e instanceof CsrfException) {
    
    

            return ApiResult.error(-1002,"非法访问跨域请求异常!");
        } else if (e instanceof AuthorizationServiceException) {
    
    

            return ApiResult.error(1101, "认证服务异常请重试!");
        }else if (e instanceof AccessDeniedException) {
    
    

            return ApiResult.error(4003, "权限不足不允许访问!");
        }

        return ApiResult.error(1200, e.getMessage());
    }

}


Clase de manejo de excepciones de falla de autorización personalizada

package com.study.test.security.service;//我存放的目录

import com.study.test.common.utils.WebUtils;
import com.study.test.security.utils.AuthExceptionUtil;

import com.alibaba.fastjson.JSON;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//自定义授权失败异常处理类
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    
    
    @Override
    public void handle(HttpServletRequest httpServletRequest,
                       HttpServletResponse httpServletResponse,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
    
    

        System.out.println("AccessDeniedHandler:暂无权限");
        WebUtils.rednerString(httpServletResponse, JSON.toJSONString(AuthExceptionUtil.getErrMsgByExceptionType(accessDeniedException)));

    }
}

Clase de manejo de excepciones de falla de autenticación personalizada

package com.study.test.security.service;//我存放的目录

import com.study.test.common.utils.WebUtils;
import com.study.test.security.utils.AuthExceptionUtil;

import com.alibaba.fastjson.JSON;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//自定义认证失败异常处理类
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    
    
    @Override
    public void commence(HttpServletRequest httpServletRequest,
                         HttpServletResponse httpServletResponse,
                         AuthenticationException authenticationException) throws IOException, ServletException {
    
    
        System.out.println("AuthenticationEntryPoint:用户未登录");
        WebUtils.rednerString(httpServletResponse, JSON.toJSONString(AuthExceptionUtil.getErrMsgByExceptionType(authenticationException)));
    }
}

Reinicie para ver si hay algún problema, se procesa la excepción personalizada de seguridad, el siguiente paso es cómo implementar el inicio de sesión personalizado

2. Autenticación personalizada frontal y posterior separada

  1. Interfaz de autenticación: su clase de implementación representa al usuario que actualmente accede al sistema y encapsula información relacionada con el usuario.
  2. Interfaz AuthenticationManager: define el método de autenticación.
  3. Interfaz UserDetailsService: la interfaz principal para cargar datos específicos del usuario. Define un método para consultar información del usuario según el nombre de usuario.
  4. Interfaz UserDetails: proporciona información básica del usuario. La información del usuario obtenida y procesada a través de UserDetailsService de acuerdo con el nombre de usuario debe encapsularse en un objeto UserDetails y devolverse. Luego, encapsule esta información en el objeto de autenticación.

UserDetails (información del usuario)

Personalizamos la información de un usuario para implementar seguridad UserDetails

package com.gzgs.security.web.security.entity;//我存放的路径

import com.alibaba.fastjson.annotation.JSONField;
import com.gzgs.security.web.entity.SysUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;


@Data
@NoArgsConstructor
@AllArgsConstructor
public class LogUser implements UserDetails {
    
    

    //用户信息
    private SysUser user;

    //用户权限
    private List<String> permissions;

    //存储SpringSecurity所需要的权限信息的集合
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    public LogUser(SysUser user,List<String> permissions){
    
    

        this.user = user;
        this.permissions = permissions;

    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
    
        // 将权限信息封装成 SimpleGrantedAuthority
        if (authorities != null) {
    
    
            return authorities;
        }
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
        authorities = this.permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());

        return authorities;

    }

    @Override
    public String getPassword() {
    
    
        return user.getPassword();
    }

    @Override
    public String getUsername() {
    
    
        return user.getUserName();
    }

    @Override
    public boolean isAccountNonExpired() {
    
    
        return user.getAccountNotExpired();
    }

    @Override
    public boolean isAccountNonLocked() {
    
    
        return user.getAccountNotLocked();
    }

    @Override
    public boolean isCredentialsNonExpired() {
    
    
        return user.getCredentialsNotExpired();
    }

    @Override
    public boolean isEnabled() {
    
    
        return user.getEnabled();
    }
}

UserDetailsService (anular el método loadUserByUsername)

Escriba su propia lógica de inicio de sesión en el método loadUserByUsername, que implica verificar la base de datos

package com.study.test.security.service;//这是我的目录

import com.study.test.security.entity.LogUser;
import com.study.test.web.entity.SysPermission;
import com.study.test.web.entity.SysUser;
import com.study.test.web.mapper.SysPermissionMapper;
import com.study.test.web.mapper.SysUserMapper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    


    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    @Autowired
    private SysUserMapper userMapper;



    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    

        //需要构造出 org.springframework.security.core.userdetails.User 对象并返回


        System.out.println("用户名:"+username);
        if (username == null || "".equals(username)) {
    
    
            throw new RuntimeException("用户不能为空");
        }

        //根据用户名查询用户
        SysUser user = userMapper.selectOne(new QueryWrapper<SysUser>().eq("account", username));
        if (user == null) {
    
    
            throw new RuntimeException("用户不存在");
        }



        List<String> permissionsList = new ArrayList<>();

        if (user != null) {
    
    
            //获取该用户所拥有的权限
            List<SysPermission> sysPermissions = sysPermissionMapper.selectPermissionList(user.getUserId());

            // 声明用户授权
            sysPermissions.forEach(sysPermission -> {
    
    
                permissionsList.add(sysPermission.getPermissionCode());

            });
        }

        //返回用户信息
        return new LogUser(user,permissionsList);

    }


    //这是加密的算法,把加密后的密码update你用户表的数据库用户的密码上
    public static void main(String[] args) {
    
    
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode("123456");
        System.out.println(encode);
    }


}


JwtAuthenticationFilter (verificación de inicio de sesión)

package com.study.test.security.filter;



import com.study.test.common.exception.BusinessException;
import com.study.test.common.utils.JwtUtils;
import com.study.test.common.utils.RedisUtil;
import com.study.test.security.entity.LogUser;

import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;


import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    
    @Autowired
    private RedisUtil redisUtil;

    //每次请求都会执行这个方法
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws BusinessException, ServletException, IOException {
    
    

        // 获取Headers上的token,我命名为token
        String token = request.getHeader("token");

        System.out.println("doFilterInternal:"+token);


        if (StringUtils.isEmpty(token)) {
    
    
            // token不存在 放行 并且直接return 返回
            filterChain.doFilter(request, response);
            return;
        }


        // 解析token
        String userId = null;

        try {
    
    
            DecodedJWT tokenInfo = JwtUtils.verifyToken(token);

            //token过期时间
            Date expiresAt = tokenInfo.getExpiresAt();
            SimpleDateFormat ymdhms = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            System.out.println("token过期时间:"+ymdhms.format(expiresAt));

            //其实这里后端可以做token是否快过期的处理,然后返回新的token给前端
            //或者新写一个刷新tokena接口给前端,让前端自己刷新



            userId = tokenInfo.getClaim("userId").asString();

        } catch (Exception e) {
    
    

            if(e instanceof TokenExpiredException){
    
    
                throw new RuntimeException("登录已过期!");
            }else {
    
    
                throw new RuntimeException("token非法");
            }


        }

        // 获取userid 从redis中获取用户信息
        String redisKey = "login:" + userId;
        LogUser loginUser = (LogUser)redisUtil.get(redisKey);
        if (Objects.isNull(loginUser)) {
    
    
            throw new RuntimeException("用户未登录");
        }

        //将用户信息存入到SecurityContextHolder
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        // 放行
        filterChain.doFilter(request, response);
    }
}

Configuración de seguridad (núcleo)

package com.study.test.security.config;//我的目录



import com.study.test.security.filter.JwtAuthenticationFilter;
import com.study.test.security.service.AccessDeniedHandlerImpl;
import com.study.test.security.service.AuthenticationEntryPointImpl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@Configuration
//@EnableWebSecurity //因为我引入了spring-boot-starter-security,所以不用@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    


    //将authenticationManager注入容器中,再自定义登录接口中获取进行认证
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Autowired
    private AccessDeniedHandlerImpl accessDeniedHandler;

    @Autowired
    private AuthenticationEntryPointImpl authenticationEntryPoint;



    //注入加密方式--后面就会使用这种方式进行对密码的对比(明文与密码的对比是否匹配)
    // 而不使用默认的密码验证
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }


    //配置放行的规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    

        http.csrf().disable() // 关闭csrf验证(防止跨站请求伪造攻击)由于我们的资源都会收到SpringSecurity的保护,所以想要跨域访问还要让SpringSecurity运行跨域访问
        // 不通过session 获取SecurityContext(基于Token不需要session)
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
         //开启权限拦截
        .authorizeRequests()
        // 允许登录接口匿名访问
        .antMatchers("/sysUser/login", "/sysUser/test","/test/**").anonymous()
        .antMatchers("/**.html","/js/**","/css/**","/img/**").permitAll()//放行静态资源
        // 其他请求都需要认证
        .anyRequest().authenticated();

        //将jwtAuthenticationTokenFilter过滤器注入到UsernamePasswordAuthenticationFilter过滤器之前
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

        // 认证授权异常自定义处理
        http.exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)//自定义认证失败异常处理类
                .accessDeniedHandler(accessDeniedHandler);//自定义授权失败异常处理类


        // 禁用缓存
        http.headers().cacheControl();

        // 跨域请求配置
        http.cors();
    }



}


Cuando reinicie nuevamente, no verá la contraseña de salida de la consola.
inserte la descripción de la imagen aquí
Estructura de mi proyecto.
inserte la descripción de la imagen aquí

inicio de sesión personalizado, cierre de sesión

Parámetro de inicio de sesión personalizado loginUserParam

package com.study.test.web.param;//我的目录

import lombok.Data;

@Data
public class LoginUserParam {
    
    

    //用户名
    private String userName;

    //用户密码
    private String password;
}

Servicio de registro

package com.study.test.web.service;//我的目录


import com.study.test.common.api.ApiResult;
import com.study.test.web.param.LoginUserParam;

public interface LogService {
    
    

    ApiResult login(LoginUserParam param);

    ApiResult logOut();
}

LogServiceImpl

package com.study.test.web.service.impl;//我的目录


import com.study.test.common.api.ApiResult;
import com.study.test.common.utils.JwtUtils;
import com.study.test.common.utils.RedisUtil;
import com.study.test.security.entity.LogUser;
import com.study.test.web.param.LoginUserParam;
import com.study.test.web.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service
public class LogServiceImpl implements LogService {
    
    

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private AuthenticationManager authenticationManager;


    @Override
    public ApiResult login(LoginUserParam param) {
    
    

        // 1 获取AuthenticationManager 对象 然后调用 authenticate() 方法
        // UsernamePasswordAuthenticationToken 实现了Authentication 接口
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(param.getUserName(), param.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        //2 认证没通过 提示认证失败
        if (Objects.isNull(authenticate)) {
    
    
            throw new RuntimeException("认证失败用户信息不存在");
        }


        //认证通过 使用userid 生成jwt token令牌
        LogUser loginUser = (LogUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getUserId().toString();

        Map<String, String> payloadMap = new HashMap<>();
        payloadMap.put("userId", userId);
        payloadMap.put("userName", loginUser.getUser().getUserName());
        payloadMap.put("token", JwtUtils.generateToken(payloadMap));

        boolean resultRedis = redisUtil.set("login:" + userId, loginUser);

        if(!resultRedis){
    
    
            throw new RuntimeException("redis连接不上,登录失败");
        }


        return ApiResult.success(payloadMap);
    }

    @Override
    public ApiResult logOut() {
    
    
        // 1 获取 SecurityContextHolder 中的用户id
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LogUser loginUser = (LogUser)authentication.getPrincipal();
        //2 删除redis 中的缓存信
        String key = "login:"+loginUser.getUser().getUserId().toString();
        redisUtil.del(key);
        return ApiResult.success("退出成功!");

    }

}

SysUserController

package com.study.test.web.controller;



import com.study.test.common.api.ApiResult;
import com.study.test.web.param.LoginUserParam;
import com.study.test.web.service.LogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/sysUser")
public class SysUserController {
    
    

    @Autowired
    private LogService logService;


    /**
     * 自定义登录
     * @param param 登录传参
     * @return
     */
    @PostMapping("/login")
    public ApiResult login(@RequestBody LoginUserParam param) {
    
    

        return logService.login(param);

    }


    /**
     * 自定义登出
     * @return
     */
    @PostMapping("/logOut")
    public ApiResult logOut() {
    
    

        return logService.logOut();

    }





}

3. Cerrar sesión e iniciar sesión Prueba de cartero

Mi solicitud de inicio de sesión personalizada es /sysUser/login, por lo que debe verificar si ha permitido esta solicitud; de lo contrario, se le solicitará acceso no autorizado.

inserte la descripción de la imagen aquí


inserte la descripción de la imagen aquí
No se pudo iniciar sesión ni
inserte la descripción de la imagen aquí
cerrar sesión. Cerré sesión exitosamente
inserte la descripción de la imagen aquí

4. Personalice el método de verificación de permisos de seguridad.

Configurar un método de verificación de permisos personalizado

package com.study.test.security.handler;//我的目录


import com.study.test.security.entity.LogUser;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.List;

//自定义security权限校验方法
@Component("syex")
public class SecurityPermissionsExpression {
    
    
    public boolean hasAuthority(String authority){
    
    
        //获取当前用户的权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LogUser loginUser = (LogUser) authentication.getPrincipal();
        List<String> permissions = loginUser.getPermissions();
        //判断用户权限集合中是否存在authority
        return permissions.contains(authority);
    }
}

En este punto, la configuración de seguridad básicamente ha terminado: la siguiente es mi estructura de directorios y
inserte la descripción de la imagen aquí
mis permisos [sys:queryUser]
inserte la descripción de la imagen aquí

Configurar permisos en la capa de control

package com.study.test.web.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/testPreAuthorize")
public class TestPreAuthorizeController {
    
    


    @PostMapping("/hello")
    // 只有sys:queryUser 权限才能访问
    //@PreAuthorize("hasAuthority('sys:queryUser')") //这是没有自定义权限校验方法的默认写法
    @PreAuthorize("@syex.hasAuthority('sys:queryUser')")
    public String hello(){
    
    

        return "hello";
    }
    
    @PostMapping("/hello2")
    // 只有sys:queryUser2 权限才能访问
    @PreAuthorize("@syex.hasAuthority('sys:queryUser2')")
    public String hello2(){
    
    

        return "hello2";
    }
}

Permisos de prueba de cartero

inserte la descripción de la imagen aquí

inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq407995680/article/details/132312399
Recomendado
Clasificación