SpringSecurity-从入门到精通

SpringSecurity从入门到精通

0. 简介

Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。

​ 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多,因为相比与SpringSecurity,Shiro的上手更加的简单。

​ 一般Web应用的需要进行认证授权

认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

授权:经过认证后判断当前用户是否有权限进行某个操作

​ 而认证和授权也是SpringSecurity作为安全框架的核心功能。

1. 快速入门

1.1 准备工作

​ 我们先要搭建一个简单的SpringBoot工程

① 设置父工程 添加依赖

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.5.0version>
    parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
    dependencies>

② 创建启动类

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

@SpringBootApplication
@MapperScan("com.lxd.springsecuritydemo.mapper")
public class SpringSecurityDemoApplication {

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

}

③ 创建Controller


import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

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

1.2 引入SpringSecurity

​ 在SpringBoot项目中使用SpringSecurity我们只需要引入依赖即可实现入门案例。

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

​ 引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个SpringSecurity的默认登陆页面,默认用户名是user,密码会输出在控制台。

​ 必须登陆之后才能对接口进行访问。

2. 认证

2.1 登陆校验流程

SpringSecurity-从入门到精通_第1张图片

2.2 原理初探

​ 想要知道如何实现自己的登陆流程就必须要先知道入门案例中SpringSecurity的流程。

2.2.1 SpringSecurity完整流程

​ SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。

SpringSecurity-从入门到精通_第2张图片

​ 图中只展示了核心过滤器,其它的非核心过滤器并没有在图中展示。

UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

**ExceptionTranslationFilter:**处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException 。

**FilterSecurityInterceptor:**负责权限校验的过滤器。

​ 我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。

SpringSecurity-从入门到精通_第3张图片

2.2.2 认证流程详解

SpringSecurity-从入门到精通_第4张图片

概念速查:

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

2.3 解决问题

2.3.1 思路分析

登录

​ ①自定义登录接口

​ 调用ProviderManager的方法进行认证 如果认证通过生成jwt

​ 把用户信息存入redis中

​ ②自定义UserDetailsService

​ 在这个实现类中去查询数据库

校验:

​ ①定义Jwt认证过滤器

​ 获取token

​ 解析token获取其中的userid

​ 从redis中获取用户信息

​ 存入SecurityContextHolder

2.3.2 准备工作

①添加依赖

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.33version>
        dependency>
        
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>0.9.0version>
        dependency>

② 添加Redis相关配置


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
 * FastJson2JsonRedisSerializer
 *  Redis使用FastJson序列化
 *  
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
    private ObjectMapper objectMapper = new ObjectMapper();
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;
    
   static {
   		ParserConfig.getGlobalInstance().setAutoTypeSupport(true); 
   		//如果遇到反序列化autoType is not support错误,请添加并修改一下包名到bean文件路径
       // ParserConfig.getGlobalInstance().addAccept("com.xxxxx.xxx");
    }
    public FastJson2JsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }
    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    static {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }

}


import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lxd.springsecuritydemo.utils.FastJson2JsonRedisSerializer;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
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.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        //Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
        //使用Fastjson2JsonRedisSerializer来序列化和反序列化redis的value值 by zhengkai
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}


③ 响应类


package com.lxd.springsecuritydemo.utils;

import com.lxd.springsecuritydemo.exception.code.BaseResponseCode;
import com.lxd.springsecuritydemo.exception.code.ResponseCodeInterface;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * 返回值DataResult
 *
 * @author lxd
 * @version V1.0
 * @date 2023年3月18日
 */
@Data
public class DataResult {

    /**
     * 请求响应code,0为成功 其他为失败
     */
    @ApiModelProperty(value = "请求响应code,0为成功 其他为失败", name = "code")
    private int code;

    /**
     * 响应异常码详细信息
     */
    @ApiModelProperty(value = "响应异常码详细信息", name = "msg")
    private String msg;

    @ApiModelProperty(value = "需要返回的数据", name = "data")
    private Object data;

    public DataResult(int code, Object data) {
        this.code = code;
        this.data = data;
        this.msg = null;
    }

    public DataResult(int code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public DataResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
        this.data = null;
    }


    public DataResult() {
        this.code = BaseResponseCode.SUCCESS.getCode();
        this.msg = BaseResponseCode.SUCCESS.getMsg();
        this.data = null;
    }

    public DataResult(Object data) {
        this.data = data;
        this.code = BaseResponseCode.SUCCESS.getCode();
        this.msg = BaseResponseCode.SUCCESS.getMsg();
    }

    public DataResult(ResponseCodeInterface responseCodeInterface) {
        this.data = null;
        this.code = responseCodeInterface.getCode();
        this.msg = responseCodeInterface.getMsg();
    }

    public DataResult(ResponseCodeInterface responseCodeInterface, Object data) {
        this.data = data;
        this.code = responseCodeInterface.getCode();
        this.msg = responseCodeInterface.getMsg();
    }

    /**
     * 操作成功 data为null
     */
    public static DataResult success() {
        return new DataResult();
    }

    /**
     * 操作成功 data 不为null
     */
    public static DataResult success(Object data) {
        return new DataResult(data);
    }

    /**
     * 操作成功 data 不为null
     */
    public static DataResult success(String msg,Object data) {
        return new DataResult(BaseResponseCode.SUCCESS.getCode(),msg,data);
    }

    /**
     * 操作失败 data 不为null
     */
    public static DataResult fail(String msg) {
        return new DataResult(BaseResponseCode.OPERATION_ERRO.getCode(), msg);
    }

    /**
     * 自定义返回  data为null
     */
    public static DataResult getResult(int code, String msg) {
        return new DataResult(code, msg);
    }

    /**
     * 自定义返回 入参一般是异常code枚举 data为空
     */
    public static DataResult getResult(BaseResponseCode responseCode) {
        return new DataResult(responseCode);
    }


}

④工具类


package com.lxd.springsecuritydemo.utils;

import com.lxd.springsecuritydemo.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.Random;
import java.util.UUID;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: JWT工具类
 * @Date: 2023/6/13 0013 11:31
 */
public class JwtUtil {

    // 有效期为一个小时
    private static final Long JWT_TTL = 60 * 60 * 1000L;

    // 设置密钥明文
    private static final String JWT_KEY = "liux";

    //加密解密盐值
    private static final String SALT = "123456";

    private static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-","");
        return token;
    }

    /**
     * 生成token
     * @param user
     * @return
     */
    public static String generateToken(User user){
        return Jwts.builder()
                .setSubject(user.getId().toString())
                .setExpiration(new Date(System.currentTimeMillis()))
                .setIssuedAt(new Date())
                .setIssuer("lxd")
                .signWith(SignatureAlgorithm.HS512, SALT)// 不使用公钥私钥
                .compact();
    }

    /**
     * 生成随机的token
     *
     * @return token
     */
    private static String getRandomToken() {
        Random random = new Random();
        StringBuilder randomStr = new StringBuilder();

        // 根据length生成相应长度的随机字符串
        for (int i = 0; i < 32; i++) {
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";

            //输出字母还是数字
            if ("char".equalsIgnoreCase(charOrNum)) {
                //输出是大写字母还是小写字母
                int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
                randomStr.append((char) (random.nextInt(26) + temp));
            } else {
                randomStr.append(random.nextInt(10));
            }
        }

        return randomStr.toString();
    }

    /***
     * 功能描述:
     * 生成JWT
     * @Author: LXD
     * @Date: 2023-06-13 11:48:33
     * @Param  subject: token中要存放的数据(json格式)
     * @return: java.lang.String
     * @since: 1.0.0
     */
    public static String createJWT(String subject){
        // 设置过期时间
        JwtBuilder builder = getJwtBuilder(subject,null,getRandomToken());
        return builder.compact();
    }

    /***
     * 功能描述:
     * 生成JWT
     * @Author: LXD
     * @Date: 2023-06-13 11:48:33
     * @Param  subject: token中要存放的数据(json格式)
     * @Param ttlMillis: token超时时间
     * @return: java.lang.String
     * @since: 1.0.0
     */
    public static String createJWT(String subject,Long ttlMillis){
        // 设置过期时间
        JwtBuilder builder = getJwtBuilder(subject,ttlMillis,getRandomToken());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject,Long ttlMills,String uuid){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalkey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMills == null){
            ttlMills = JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMills;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid) // 唯一的ID
                .setSubject(subject)  // 主题 可以是JSON数据
                .setIssuer("lxd")  // 签发者
                .setIssuedAt(now)  // 签发时间
                .signWith(signatureAlgorithm,secretKey)  // 使用HS256对称加密算法签名,第二个参数为密钥
                .setExpiration(expDate);
    }

    /***
     * 功能描述:
     * 生成JWT
     * @Author: LXD
     * @Date: 2023-06-13 11:48:33
     * @Param  id:
     * @Param  subject: token中要存放的数据(json格式)
     * @Param  ttlMillis: token超时时间
     * @return: java.lang.String
     * @since: 1.0.0
     */
    public static String createJWT(String id,String subject,Long ttlMillis){
        // 设置过期时间
        JwtBuilder builder = getJwtBuilder(subject,ttlMillis,id);
        return builder.compact();
    }

    /***
     * 功能描述:
     * 生成加密后的密钥 SecretKey
     * @Author: LXD
     * @Date: 2023-06-13 11:41:14
     * @Param
     * @return: javax.crypto.SecretKey
     * @since: 1.0.0
     */
    public static SecretKey generalkey(){
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodeKey,0,encodeKey.length,"AES");
        return key;
    }

    /***
     * 功能描述:
     * 解析JWT
     * @Author: LXD
     * @Date: 2023-06-13 11:53:32
     * @Param  jwt:
     * @return: io.jsonwebtoken.Claims
     * @since: 1.0.0
     */
    public static Claims parseJWT(String jwt) throws Exception{
        SecretKey secretKey = generalkey();
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
    }

    public static void main(String[] args) throws Exception {
//        String jwt = createJWT("123456678");
//        System.out.println(jwt);

        Claims claims = parseJWT("eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4Vko0NjA5OXptNzZFNDRJOVY2ZW8wOTBaODQ2eTZ1MCIsInN1YiI6IjEyMzQ1NjY3OCIsImlzcyI6Imx4ZCIsImlhdCI6MTY4NjY0ODM1NywiZXhwIjoxNjg2NjUxOTU3fQ.NlPkXi4aCD_69e04yNi1qUcTCDC19ZVS7PkOQa1-wnw");
        String subject = claims.getSubject();
        System.out.println(subject);

    }
}


package com.lxd.springsecuritydemo.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: rediscache
 * @Date: 2023/6/13 0013 14:29
 */
@SuppressWarnings(value = {"unchecked","rawtypes","unused"}) // 抑制没有进行类型检查操作的警告 抑制使用generics时忽略没有指定相应的类型的警告
@Component
public class RedisCache {

    @Autowired
    public RedisTemplate redisTemplate;

    /***
     * 功能描述:
     * 缓存基本的对象
     * @Author: LXD
     * @Date: 2023-06-13 14:32:06
     * @Param  key: 缓存的键值
     * @Param value: 缓存的值
     * @return: void
     * @since: 1.0.0
     */
    public <T> void setCacheObject(final String key,final T value){
        redisTemplate.opsForValue().set(key,value);
    }

    /***
     * 功能描述:
     * 缓存基本的对象
     * @Author: LXD
     * @Date: 2023-06-13 14:34:09
     * @Param  key: 缓存的键值
     * @Param value: 缓存的值
     * @Param timeout: 过期时间
     * @Param timeUnit: 时间单位
     * @return: void
     * @since: 1.0.0
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit){
        redisTemplate.opsForValue().set(key,value,timeout,timeUnit);
    }

    /***
     * 功能描述:
     * 判断key是否存在
     * @Author: LXD
     * @Date: 2023-06-13 15:13:42
     * @Param  key:
     * @return: boolean
     * @since: 1.0.0
     */
    public boolean exists(String key) {
        return Boolean.TRUE.equals(this.redisTemplate.hasKey(key));
    }

    /***
     * 功能描述:
     * 设置KEY的有效时间
     * @Author: LXD
     * @Date: 2023-06-13 14:37:12
     * @Param  key: 缓存的键值
     * @Param timeout: 超时时间
     * @return: boolean
     * @since: 1.0.0
     */
    public boolean expire(final String key, final long timeout){
        return expire(key,timeout,TimeUnit.SECONDS);
    }

    /***
     * 功能描述:
     * 设置KEY的有效时间
     * @Author: LXD
     * @Date: 2023-06-13 14:37:45
     * @Param  key: 缓存的键值
     * @Param timeout: 超时时间
     * @Param unit: 时间单位
     * @return: boolean
     * @since: 1.0.0
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit){
        return expire(key,timeout,unit);
    }

    /***
     * 功能描述:
     * 获得缓存的基本对象
     * @Author: LXD
     * @Date: 2023-06-13 14:40:12
     * @Param  key: 缓存的键值
     * @return: T 缓存数据
     * @since: 1.0.0
     */
    public <T> T getCacheObject(final String key){
        ValueOperations<String,T> operations = redisTemplate.opsForValue();
        return operations.get(key);
    }

    /***
     * 功能描述:
     * 删除单个对象
     * @Author: LXD
     * @Date: 2023-06-13 14:41:20
     * @Param  key:
     * @return: boolean
     * @since: 1.0.0
     */
    public boolean deleteObject(final String key){
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    /***
     * 功能描述:
     * 删除集合对象
     * @Author: LXD
     * @Date: 2023-06-13 14:42:19
     * @Param  collection: 多个对象
     * @return: long
     * @since: 1.0.0
     */
    public long deleteObject(final Collection collection){
        return redisTemplate.delete(collection);
    }

    /***
     * 功能描述:
     * 缓存List集合对象
     * @Author: LXD
     * @Date: 2023-06-13 14:44:28
     * @Param  key: 缓存的键值
     * @Param dataList: 缓存的List集合对象
     * @return: long
     * @since: 1.0.0
     */
    public <T> long setCacheList(final String key, final List<T>dataList){
        Long count = redisTemplate.opsForList().rightPushAll(key,dataList);
        return count == null ? 0 : count;
    }

    /***
     * 功能描述:
     * 获得缓存的List对象
     * @Author: LXD
     * @Date: 2023-06-13 14:47:58
     * @Param  key: 缓存的键值
     * @return: java.util.List 缓存的List集合对象
     * @since: 1.0.0
     */
    public <T> List<T> getCacheList(final String key){
        return redisTemplate.opsForList().range(key,0,-1);
    }

    /***
     * 功能描述:
     * 缓存Set
     * @Author: LXD
     * @Date: 2023-06-13 14:51:53
     * @Param  key: 缓存的键值
     * @Param dataSet: 缓存的Set集合对象
     * @return: org.springframework.data.redis.core.BoundSetOperations
     * @since: 1.0.0
     */
    public <T> BoundSetOperations<String,T> setCacheSet(final String key, final Set<T>dataSet){
        BoundSetOperations<String,T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while(it.hasNext()){
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 功能描述:
     * 获得缓存的Set
     * @Author: LXD
     * @Date: 2023-06-13 14:53:23
     * @Param  key: 缓存的键值
     * @return: java.util.Set 缓存的Set集合对象
     * @since: 1.0.0
     */
    public <T> Set<T> getCacheSet(final String key){
        return redisTemplate.opsForSet().members(key);
    }

    /***
     * 功能描述:
     * 缓存Map
     * @Author: LXD
     * @Date: 2023-06-13 14:55:13
     * @Param  key: 缓存的键值
     * @Param dataMap: 缓存的Map集合对象
     * @return: void
     * @since: 1.0.0
     */
    public <T> void setCacheMap(final String key,final Map<String,T> dataMap){
        if(dataMap != null){
            redisTemplate.opsForHash().putAll(key,dataMap);
        }
    }

    /***
     * 功能描述:
     * 获得缓存的Map
     * @Author: LXD
     * @Date: 2023-06-13 14:56:44
     * @Param  key: 缓存的键值
     * @return: java.util.Map 缓存的Map集合对象
     * @since: 1.0.0
     */
    public <T> Map<String,T> getCacheMap(final String key){
        return redisTemplate.opsForHash().entries(key);
    }

    /***
     * 功能描述:
     * 往hash中存入数据
     * @Author: LXD
     * @Date: 2023-06-13 14:58:17
     * @Param  key: 缓存的键值
     * @Param hkey: 缓存的hash键值
     * @Param value: 数据
     * @return: void
     * @since: 1.0.0
     */
    public <T> void setCacheMapValue(final String key,final String hkey,final T value){
        redisTemplate.opsForHash().put(key,hkey,value);
    }

    /***
     * 功能描述:
     * 获得Hash中的数据
     * @Author: LXD
     * @Date: 2023-06-13 15:01:02
     * @Param  key: redis键值
     * @Param hkey: Hash键值
     * @return: T Hash中的对象
     * @since: 1.0.0
     */
    public <T> T getCacheMapValue(final String key,final String hkey){
        HashOperations<String,String,T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key,hkey);
    }

    /***
     * 功能描述:
     * 删除hash中的数据
     * @Author: LXD
     * @Date: 2023-06-13 15:03:58
     * @Param  key: redis键值
     * @Param hkey: Hash键值
     * @return: void
     * @since: 1.0.0
     */
    public void delCacheMapValue(final String key,final String hkey){
        HashOperations hashOperations = redisTemplate.opsForHash();
        hashOperations.delete(key,hkey);
    }

    /***
     * 功能描述:
     * 获取多个Hash中的数据
     * @Author: LXD
     * @Date: 2023-06-13 15:07:13
     * @Param  key: redis键值
     * @Param hkeys: Hash键值
     * @return: java.util.List 对象集合
     * @since: 1.0.0
     */
    public <T> List<T> getMultiCacheMapValue(final String key,final Collection<Object> hkeys){
        return redisTemplate.opsForHash().multiGet(key,hkeys);
    }

    /***
     * 功能描述:
     * 获得缓存的基本对象列表
     * @Author: LXD
     * @Date: 2023-06-13 15:10:03
     * @Param  pattern: key的前缀
     * @return: java.util.Collection 对象列表
     * @since: 1.0.0
     */
    public Collection<String> keys(final String pattern){
        return redisTemplate.keys("*" + pattern);
    }

}


package com.lxd.springsecuritydemo.utils;

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

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/13 0013 15:47
 */
public class WebUtils {
    public static String renderString(HttpServletResponse response,String string){
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().println(string);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

⑤实体类

package com.lxd.springsecuritydemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: user
 * @Date: 2023/6/13 0013 15:52
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "sys_user")
public class User extends BaseEntity implements Serializable {

    @TableId
    private String id;

    @NotBlank(message = "账号不能为空")
    @ApiModelProperty("账户名称")
    private String username;

    @ApiModelProperty("加密盐值")
    private String salt;

    @NotBlank(message = "密码不能为空")
    @ApiModelProperty("用户密码密文")
    private String password;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String oldPwd;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String newPwd;

    @ApiModelProperty("手机号码")
    private String phone;

    @ApiModelProperty("部门id")
    private String deptId;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String deptName;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String deptNo;

    @ApiModelProperty("真实名称")
    private String realName;

    @ApiModelProperty("昵称")
    private String nickName;

    @ApiModelProperty("邮箱(唯一)")
    private String email;

    @ApiModelProperty("账户状态(1.正常 2.锁定 )")
    private Integer status;

    @ApiModelProperty("性别(1.男 2.女)")
    private Integer sex;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty("是否删除(1未删除;0已删除)")
    private Integer deleted;

    @ApiModelProperty("创建人")
    private String createId;

    @ApiModelProperty("更新人")
    private String updateId;

    @ApiModelProperty("创建来源(1.web 2.android 3.ios )")
    private Integer createWhere;

    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty("")
    private Date createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    @ApiModelProperty("")
    private Date updateTime;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String startTime;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String endTime;

    @TableField(exist = false)
    @ApiModelProperty("角色ID集合")
    private List<String> roleIds;

    @TableField(exist = false)
    @ApiModelProperty("")
    private String captcha;
}

2.3.3 实现

2.3.3.1 数据库校验用户

​ 从之前的分析我们可以知道,我们可以自定义一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。

准备工作

​ 我们先创建一个用户表, 建表语句如下:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户id',
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '账户名称',
  `salt` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加密盐值',
  `password` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户密码密文',
  `phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手机号码',
  `dept_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '部门id',
  `real_name` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '真实名称',
  `nick_name` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱(唯一)',
  `status` tinyint NULL DEFAULT NULL COMMENT '账户状态(1.正常 2.锁定 )',
  `sex` tinyint NULL DEFAULT NULL COMMENT '性别(1.男 2.女)',
  `deleted` tinyint NULL DEFAULT NULL COMMENT '是否删除(1未删除;0已删除)',
  `create_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人',
  `update_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '更新人',
  `create_where` tinyint NULL DEFAULT NULL COMMENT '创建来源(1.web 2.android 3.ios )',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', '324ce32d86224b00a02b', '$2a$10$M43Bz0MPfGqvLMms7awjy.rHKNMJ8EYzhJ3SM7ftQOwsGb7K24r.6', '13888888888', '1', '管理员', '管理员', '[email protected]', 1, 2, 1, '1', '1', 3, '2019-09-22 19:38:05', '2022-07-28 10:40:52');
INSERT INTO `sys_user` VALUES ('1523576216814956545', 'guest', '29153dc3aa034e8bb5da', '$2a$10$M43Bz0MPfGqvLMms7awjy.rHKNMJ8EYzhJ3SM7ftQOwsGb7K24r.6', '18888888888', '1', NULL, NULL, NULL, 1, NULL, 1, NULL, NULL, 1, '2022-05-09 16:10:51', '2022-05-09 16:10:51');
INSERT INTO `sys_user` VALUES ('1602932929246273537', 'test', 'df703c05ce344d7688c9', '78eab59eb5220731e291eecbf38593e2', '18888888888', '1', NULL, NULL, NULL, 1, NULL, 1, NULL, NULL, 1, '2022-12-14 15:46:06', '2022-12-14 15:46:06');

SET FOREIGN_KEY_CHECKS = 1;

​ 引入MybatisPuls和mysql驱动的依赖

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.3version>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

​ 配置数据库信息

spring:
  thymeleaf:
    cache: false
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springsecurity?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=true

​ 定义Mapper接口

public interface UserMapper extends BaseMapper<User> {
}

​ 修改User实体类

类名上加@TableName(value = "sys_user") ,id字段上加 @TableId

​ 配置Mapper扫描

@SpringBootApplication
@MapperScan("com.lxd.springsecuritydemo.mapper")
public class SpringSecurityDemoApplication {

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

}

​ 添加junit依赖

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

完整的pom文件


<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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.5.0version>
        <relativePath/> 
    parent>
    <groupId>com.lxdgroupId>
    <artifactId>SpringSecurityDemoartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>SpringSecurityDemoname>
    <description>SpringSecurityDemodescription>
    <properties>
        <java.version>1.8java.version>
        <mybatis-plus.version>3.4.0mybatis-plus.version>
        <commons.lang.version>2.6commons.lang.version>
        <com.alibaba.fastjson.version>1.2.33com.alibaba.fastjson.version>
        <io.jsonwebtoken.jjwt.version>0.9.0io.jsonwebtoken.jjwt.version>
        <io.swagger.core.version>2.2.10io.swagger.core.version>
        <druid.version>1.1.10druid.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>${mybatis-plus.version}version>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>

        <dependency>
            <groupId>commons-langgroupId>
            <artifactId>commons-langartifactId>
            <version>${commons.lang.version}version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>${com.alibaba.fastjson.version}version>
        dependency>
        
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            <version>${io.jsonwebtoken.jjwt.version}version>
        dependency>

        
        <dependency>
            <groupId>io.swagger.core.v3groupId>
            <artifactId>swagger-annotationsartifactId>
            <version>${io.swagger.core.version}version>
        dependency>
        <dependency>
            <groupId>io.swaggergroupId>
            <artifactId>swagger-annotationsartifactId>
            <version>1.5.21version>
        dependency>
        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>5.8.5version>
        dependency>
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjrtartifactId>
            <version>1.9.6version>
        dependency>
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.6version>
        dependency>
        <dependency>
            <groupId>jakarta.validationgroupId>
            <artifactId>jakarta.validation-apiartifactId>
            <version>2.0.2version>
        dependency>



    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>
    <repositories>
        <repository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        repository>
        <repository>
            <id>spring-snapshotsid>
            <name>Spring Snapshotsname>
            <url>https://repo.spring.io/snapshoturl>
            <releases>
                <enabled>falseenabled>
            releases>
        repository>
    repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        pluginRepository>
        <pluginRepository>
            <id>spring-snapshotsid>
            <name>Spring Snapshotsname>
            <url>https://repo.spring.io/snapshoturl>
            <releases>
                <enabled>falseenabled>
            releases>
        pluginRepository>
    pluginRepositories>

project>

​ 测试MP是否能正常使用

/**
 * @Author lxd
 */
@SpringBootTest
public class MapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testUserMapper(){
        List<User> users = userMapper.selectList(null);
        System.out.println(users);
    }
}
核心代码实现

创建一个类实现UserDetailsService接口,重写其中的方法。更加用户名从数据库中查询用户信息

package com.lxd.springsecuritydemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lxd.springsecuritydemo.entity.LoginUser;
import com.lxd.springsecuritydemo.entity.SysPermission;
import com.lxd.springsecuritydemo.entity.User;
import com.lxd.springsecuritydemo.mapper.UserMapper;
import com.lxd.springsecuritydemo.service.PermissionService;
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.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/13 0013 16:42
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,s);
        User user = userMapper.selectOne(queryWrapper);
        // 如果没有查询到用户就抛出异常
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        // todo 查询对应权限信息(这个地方可以先将权限信息设为空,后边将介绍如何封装权限信息)
        List<String> permissions = permissionService.getRolePermissionByUserId(user.getId()).stream().map(SysPermission::getPerms).collect(Collectors.toList());
        // 把数据封装成UserDetails返回
        return new LoginUser(user,permissions);
    }
}

因为UserDetailsService方法的返回值是UserDetails类型,所以需要定义一个类,实现该接口,把用户信息封装在其中。

package com.lxd.springsecuritydemo.entity;

import com.alibaba.fastjson.annotation.JSONField;
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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/13 0013 16:48
 */
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    private List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    // 存储SpringSecurity所需要的权限信息的集合
    @JSONField(serialize = false) // 忽略该属性序列化
    private List<SimpleGrantedAuthority> authorities;

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

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

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

    /***
     * 功能描述:
     * 帐户是否未过期
     * @Author: LXD
     * @Date: 2023-06-16 10:20:17
     * @Param
     * @return: boolean true:未过期 false: 已过期
     * @since: 1.0.0
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /***
     * 功能描述:
     * 账户是否未锁定
     * @Author: LXD
     * @Date: 2023-06-16 10:20:58
     * @Param
     * @return: boolean true:未锁定  false: 已锁定
     * @since: 1.0.0
     */
    @Override
    public boolean isAccountNonLocked() {
        return user.getStatus().equals(1);
    }

    /**
     * 密码没有过期状态
     * @return boolean (true密码没有过期,false密码已经过期)
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /***
     * 功能描述:
     * 判断账户是否可用
     * @Author: LXD
     * @Date: 2023-06-16 10:18:01
     * @Param
     * @return: boolean true:可用 false: 不可用
     * @since: 1.0.0
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

注意:如果要测试,需要往用户表中写入用户数据,并且如果你想让用户的密码是明文存储,需要在密码前加{noop}。例如 {noop}123456。

这样登陆的时候就可以用sg作为用户名,1234作为密码来登陆了。

2.3.3.2 密码加密存储

​ 实际项目中我们不会把密码明文存储在数据库中。

​ 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。

​ 我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。

​ 我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。

​ 我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。

/**
 * @Author lxd
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}
2.3.3.3 登陆接口

​ 接下我们需要自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。

​ 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。

​ 认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis,可以把用户id作为key。

@RestController
public class LoginController {

    @Autowired
    private LoginServcie loginServcie;

    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user){
        return loginServcie.login(user);
    }
}

/**
 * @Author lxd
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

@Service
public class LoginServiceImpl implements LoginServcie {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;

    @Override
    public DataResult login(User user) {
        // 通过AuthenticationManager的authenticate方法来进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // 如果认证没通过,给出相应的提示
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        // 如果认证通过,使用userid生成一个jwt,并存入DataResult返回
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String id = loginUser.getUser().getId();
        String jwt = JwtUtil.createJWT(id);
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        // 把完整的用户信息存入redis,userid作为key
        redisCache.setCacheObject("login:"+id,loginUser);
        return DataResult.success("登录成功",map);
    }
}

2.3.3.4 认证过滤器

​ 我们需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的userid。

​ 使用userid去redis中获取对应的LoginUser对象。

​ 然后封装Authentication对象存入SecurityContextHolder

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userid;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //TODO 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}
/**
 * @Author lxd
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //把token校验过滤器添加到过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

2.3.3.5 退出登陆

​ 我们只需要定义一个登陆接口,然后获取SecurityContextHolder中的认证信息,删除redis中对应的数据即可。

package com.lxd.springsecuritydemo.service.impl;

import com.lxd.springsecuritydemo.entity.LoginUser;
import com.lxd.springsecuritydemo.entity.SysPermission;
import com.lxd.springsecuritydemo.entity.User;
import com.lxd.springsecuritydemo.service.LoginService;
import com.lxd.springsecuritydemo.service.PermissionService;
import com.lxd.springsecuritydemo.utils.DataResult;
import com.lxd.springsecuritydemo.utils.JwtUtil;
import com.lxd.springsecuritydemo.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.cache.RedisCacheWriter;
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.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/14 0014 10:12
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Override
    public DataResult login(User user) {
        // 通过AuthenticationManager的authenticate方法来进行用户认证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // 如果认证没通过,给出相应的提示
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登录失败");
        }
        // 如果认证通过,使用userid生成一个jwt,并存入DataResult返回
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String id = loginUser.getUser().getId();
        String jwt = JwtUtil.createJWT(id);
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        // 把完整的用户信息存入redis,userid作为key
        redisCache.setCacheObject("login:"+id,loginUser);
        return DataResult.success("登录成功",map);
    }

    @Override
    public DataResult logout() {
        // 获取SecurityContextHolder中的用户id
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        String id = loginUser.getUser().getId();
        // 删除redis中的值
        redisCache.deleteObject("login:"+id);
        return DataResult.success("退出成功");
    }
}


3. 授权

3.0 权限系统的作用

​ 例如一个学校图书馆的管理系统,如果是普通学生登录就能看到借书还书相关的功能,不可能让他看到并且去使用添加书籍信息,删除书籍信息等功能。但是如果是一个图书馆管理员的账号登录了,应该就能看到并使用添加书籍信息,删除书籍信息等功能。

​ 总结起来就是不同的用户可以使用不同的功能。这就是权限系统要去实现的效果。

​ 我们不能只依赖前端去判断用户的权限来选择显示哪些菜单哪些按钮。因为如果只是这样,如果有人知道了对应功能的接口地址就可以不通过前端,直接去发送请求来实现相关功能操作。

​ 所以我们还需要在后台进行用户权限的判断,判断当前用户是否有相应的权限,必须具有所需权限才能进行相应的操作。

3.1 授权基本流程

​ 在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。当前用户是否拥有访问当前资源所需的权限。

​ 所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication。

​ 然后设置我们的资源所需要的权限即可。

3.2 授权实现

3.2.1 限制访问资源所需权限

​ SpringSecurity为我们提供了基于注解的权限控制方案,这也是我们项目中主要采用的方式。我们可以使用注解去指定访问对应的资源所需的权限。

​ 但是要使用它我们需要先开启相关配置。

@EnableGlobalMethodSecurity(prePostEnabled = true)

​ 然后就可以使用对应的注解。@PreAuthorize

@RestController
public class HelloController {

    @RequestMapping("/hello")
    @PreAuthorize("hasAuthority('test')")
    public String hello(){
        return "hello";
    }
}

3.2.2 封装权限信息

​ 我们前面在写UserDetailsServiceImpl的时候说过,在查询出用户后还要获取对应的权限信息,封装到UserDetails中返回。

​ 我们先直接把权限信息写死封装到UserDetails中进行测试。

​ 我们之前定义了UserDetails的实现类LoginUser,想要让其能封装权限信息就要对其进行修改。

package com.lxd.springsecuritydemo.entity;

import com.alibaba.fastjson.annotation.JSONField;
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.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/13 0013 16:48
 */
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    private List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    // 存储SpringSecurity所需要的权限信息的集合
    @JSONField(serialize = false) // 忽略该属性序列化
    private List<SimpleGrantedAuthority> authorities;

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

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

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

    /***
     * 功能描述:
     * 帐户是否未过期
     * @Author: LXD
     * @Date: 2023-06-16 10:20:17
     * @Param
     * @return: boolean true:未过期 false: 已过期
     * @since: 1.0.0
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /***
     * 功能描述:
     * 账户是否未锁定
     * @Author: LXD
     * @Date: 2023-06-16 10:20:58
     * @Param
     * @return: boolean true:未锁定  false: 已锁定
     * @since: 1.0.0
     */
    @Override
    public boolean isAccountNonLocked() {
        return user.getStatus().equals(1);
    }

    /**
     * 密码没有过期状态
     * @return boolean (true密码没有过期,false密码已经过期)
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /***
     * 功能描述:
     * 判断账户是否可用
     * @Author: LXD
     * @Date: 2023-06-16 10:18:01
     * @Param
     * @return: boolean true:可用 false: 不可用
     * @since: 1.0.0
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}


​ LoginUser修改完后我们就可以在UserDetailsServiceImpl中去把权限信息封装到LoginUser中了。我们写死权限进行测试,后面我们再从数据库中查询权限信息。

package com.lxd.springsecuritydemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lxd.springsecuritydemo.entity.LoginUser;
import com.lxd.springsecuritydemo.entity.SysPermission;
import com.lxd.springsecuritydemo.entity.User;
import com.lxd.springsecuritydemo.mapper.UserMapper;
import com.lxd.springsecuritydemo.service.PermissionService;
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.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/13 0013 16:42
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,s);
        User user = userMapper.selectOne(queryWrapper);
        // 如果没有查询到用户就抛出异常
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        // 查询对应权限信息
        List<String> permissions = permissionService.getRolePermissionByUserId(user.getId()).stream().map(SysPermission::getPerms).collect(Collectors.toList());
        // 把数据封装成UserDetails返回
        return new LoginUser(user,permissions);
    }
}


3.2.3 从数据库查询权限信息

3.2.3.1 RBAC权限模型

​ RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。

SpringSecurity-从入门到精通_第5张图片

3.2.3.2 准备工作

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `role_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色id',
  `permission_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单权限id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1566641249492742145', '1', '51', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742146', '1', '11', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742147', '1', '17', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742148', '1', '26', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742149', '1', '40', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742150', '1', '43', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742151', '1', '44', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742152', '1', '53', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742153', '1', '36', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742154', '1', '19', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742155', '1', '3', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742156', '1', '1311115974068449281', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('15666412495', '2', '6', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656706', '1', '13', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656707', '1', '39', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656708', '1', '24', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656709', '1', '23', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656710', '1', '25', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656711', '1', '10', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656712', '1', '42', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656713', '1', '52', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656714', '1', '56', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656715', '1', '57', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656716', '1', '1566629434050314241', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656717', '1', '1566614355959463937', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656718', '1', '1566616584628060162', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656719', '1', '1566615434981916674', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656720', '1', '1566616371427393537', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656721', '1', '1566616711904215041', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656722', '1', '1566616835816538114', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656723', '1', '1566631930625863682', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656724', '1', '1566633889630072833', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656725', '1', '1566634031686955010', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656726', '1', '1566634163287437314', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656727', '1', '1566634336801599490', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656728', '1', '1566634524081467393', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656729', '1', '41', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656730', '1', '22', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656731', '1', '12', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656732', '1', '38', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656733', '1', '5', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656734', '1', '9', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656735', '1', '1566638440034676737', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656736', '1', '1566638608834441217', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656737', '1', '1566638784005353474', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656738', '1', '1566638912346861569', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656739', '1', '1566639041162326017', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656740', '1', '1566639382146658305', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656741', '1', '1566639948834877441', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656742', '1', '1566640482950131713', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656743', '1', '1566640610926735361', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656744', '1', '1566640715528482817', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656745', '1', '1566640818590920706', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656746', '1', '1566640949356736513', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656747', '1', '54', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656748', '1', '15', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656749', '1', '16', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656750', '1', '1', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656751', '1', '4', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656752', '1', '20', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656753', '1', '31', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656754', '1', '33', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656755', '1', '34', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656756', '1', '32', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656757', '1', '35', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656758', '1', '45', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656759', '1', '49', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656760', '1', '48', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656761', '1', '47', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656762', '1', '46', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656763', '1', '59', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656764', '1', '61', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656765', '1', '60', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656766', '1', '62', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656767', '1', '63', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656768', '1', '55', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656769', '1', '18', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656770', '1', '14', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656771', '1', '27', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656772', '1', '30', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656773', '1', '28', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656774', '1', '29', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656775', '1', '8', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656776', '1', '58', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656777', '1', '7', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656778', '1', '21', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656779', '1', '50', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656780', '1', '2', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656781', '1', '6', '2022-09-05 12:15:55');

SET FOREIGN_KEY_CHECKS = 1;
SELECT 
	DISTINCT m.`perms`
FROM
	sys_user_role ur
	LEFT JOIN `sys_role` r ON ur.`role_id` SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `role_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色id',
  `permission_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单权限id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('1566641249492742145', '1', '51', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742146', '1', '11', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742147', '1', '17', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742148', '1', '26', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742149', '1', '40', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742150', '1', '43', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742151', '1', '44', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742152', '1', '53', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742153', '1', '36', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742154', '1', '19', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742155', '1', '3', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249492742156', '1', '1311115974068449281', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('15666412495', '2', '6', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656706', '1', '13', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656707', '1', '39', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656708', '1', '24', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656709', '1', '23', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656710', '1', '25', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656711', '1', '10', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656712', '1', '42', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656713', '1', '52', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656714', '1', '56', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656715', '1', '57', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656716', '1', '1566629434050314241', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656717', '1', '1566614355959463937', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656718', '1', '1566616584628060162', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656719', '1', '1566615434981916674', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656720', '1', '1566616371427393537', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656721', '1', '1566616711904215041', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656722', '1', '1566616835816538114', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656723', '1', '1566631930625863682', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656724', '1', '1566633889630072833', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656725', '1', '1566634031686955010', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656726', '1', '1566634163287437314', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656727', '1', '1566634336801599490', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656728', '1', '1566634524081467393', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656729', '1', '41', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656730', '1', '22', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656731', '1', '12', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656732', '1', '38', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656733', '1', '5', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656734', '1', '9', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656735', '1', '1566638440034676737', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656736', '1', '1566638608834441217', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656737', '1', '1566638784005353474', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656738', '1', '1566638912346861569', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656739', '1', '1566639041162326017', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656740', '1', '1566639382146658305', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656741', '1', '1566639948834877441', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656742', '1', '1566640482950131713', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656743', '1', '1566640610926735361', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656744', '1', '1566640715528482817', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656745', '1', '1566640818590920706', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656746', '1', '1566640949356736513', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656747', '1', '54', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656748', '1', '15', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656749', '1', '16', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656750', '1', '1', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656751', '1', '4', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656752', '1', '20', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656753', '1', '31', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656754', '1', '33', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656755', '1', '34', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656756', '1', '32', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656757', '1', '35', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656758', '1', '45', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656759', '1', '49', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656760', '1', '48', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656761', '1', '47', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656762', '1', '46', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656763', '1', '59', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656764', '1', '61', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656765', '1', '60', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656766', '1', '62', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656767', '1', '63', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656768', '1', '55', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656769', '1', '18', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656770', '1', '14', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656771', '1', '27', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656772', '1', '30', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656773', '1', '28', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656774', '1', '29', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656775', '1', '8', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656776', '1', '58', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656777', '1', '7', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656778', '1', '21', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656779', '1', '50', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656780', '1', '2', '2022-09-05 12:15:55');
INSERT INTO `sys_role_permission` VALUES ('1566641249555656781', '1', '6', '2022-09-05 12:15:55');

SET FOREIGN_KEY_CHECKS = 1;



SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户id',
  `user_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `role_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统用户角色' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1', '1', '2020-03-19 02:23:13');
INSERT INTO `sys_user_role` VALUES ('2', '1523576216814956545', '2', '2023-06-16 10:09:37');

SET FOREIGN_KEY_CHECKS = 1;




SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `name` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜单权限名称',
  `perms` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:sys:user:add,sys:user:edit)',
  `icon` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '图标',
  `url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '访问地址URL',
  `target` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'a target属性:_self _blank',
  `pid` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '父级菜单权限名称',
  `order_num` int NULL DEFAULT NULL COMMENT '排序',
  `type` tinyint NULL DEFAULT NULL COMMENT '菜单权限类型(1:目录;2:菜单;3:按钮)',
  `status` tinyint NULL DEFAULT NULL COMMENT '状态1:正常 0:禁用',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint NULL DEFAULT NULL COMMENT '是否删除(1未删除;0已删除)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统权限' ROW_FORMAT = COMPACT;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1', '删除', 'sysGenerator:delete', NULL, 'sysGenerator/delete', NULL, '15', 1, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('10', '赋予角色', 'sys:user:role:update', NULL, '/sys/user/roles/*', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('11', '菜单权限管理', 'sys:permission', NULL, '/index/menus', '_self', '51', 98, 2, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('12', '列表', 'sys:dept:list', NULL, '/sys/depts', NULL, '41', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('13', '删除', 'sys:role:deleted', NULL, '/sys/role/*', NULL, '53', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('1311115974068449281', '数据权限', 'sys:role:bindDept', '', '/sys/role/bindDept', '_self', '53', 5, 3, 1, '2020-09-30 09:29:42', NULL, 1);
INSERT INTO `sys_permission` VALUES ('14', '定时任务立即执行', 'sysJob:run', NULL, '/sysJob/run', '_self', '59', 5, 3, 1, '2020-04-22 15:47:54', NULL, 1);
INSERT INTO `sys_permission` VALUES ('15', '代码生成', 'sysGenerator', NULL, '/index/sysGenerator', '_self', '54', 1, 2, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('1566614355959463937', '教学资料管理', 'sys:teachingManagement:file', '', '/index/depts', '_self', '1566629434050314241', 100, 2, 1, '2022-09-05 10:29:03', '2022-09-05 11:29:58', 1);
INSERT INTO `sys_permission` VALUES ('1566615434981916674', '列表', 'sys:dept:list', '', '/sys/depts', '_self', '1566614355959463937', 100, 3, 1, '2022-09-05 10:33:21', '2022-09-05 11:18:37', 1);
INSERT INTO `sys_permission` VALUES ('1566616371427393537', '详情', 'sys:dept:detail', '', '/sys/dept/*', '_self', '1566614355959463937', 100, 3, 1, '2022-09-05 10:37:04', '2022-09-05 11:18:29', 1);
INSERT INTO `sys_permission` VALUES ('1566616584628060162', '删除', 'sys:dept:deleted', '', '/sys/dept/*', '_self', '1566614355959463937', 100, 3, 1, '2022-09-05 10:37:55', '2022-09-05 11:18:19', 1);
INSERT INTO `sys_permission` VALUES ('1566616711904215041', '更新', 'sys:dept:update', '', '/sys/dept', '_self', '1566614355959463937', 100, 3, 1, '2022-09-05 10:38:25', '2022-09-05 11:18:55', 1);
INSERT INTO `sys_permission` VALUES ('1566616835816538114', '新增', 'sys:dept:add', '', '/sys/dept', '_self', '1566614355959463937', 100, 3, 1, '2022-09-05 10:38:55', '2022-09-05 11:18:46', 1);
INSERT INTO `sys_permission` VALUES ('1566629434050314241', '教学管理', 'teachingManagement', 'layui-icon-template-1', '', '_self', '0', 4, 1, 1, '2022-09-05 11:28:58', '2022-09-05 11:34:02', 1);
INSERT INTO `sys_permission` VALUES ('1566631930625863682', '虚拟资源管理', 'sys:teachingManagement:virtual', '', '/index/depts', '_self', '1566629434050314241', 100, 2, 1, '2022-09-05 11:38:54', '2022-09-05 11:38:54', 1);
INSERT INTO `sys_permission` VALUES ('1566633889630072833', '新增', 'sys:dept:add', '', '/sys/dept', '_self', '1566631930625863682', 100, 3, 1, '2022-09-05 11:46:41', '2022-09-05 11:46:41', 1);
INSERT INTO `sys_permission` VALUES ('1566634031686955010', '更新', 'sys:dept:update', '', '/sys/dept', '_self', '1566631930625863682', 100, 3, 1, '2022-09-05 11:47:14', '2022-09-05 11:47:14', 1);
INSERT INTO `sys_permission` VALUES ('1566634163287437314', '删除', 'sys:dept:deleted', '', '/sys/dept/*', '_self', '1566631930625863682', 100, 3, 1, '2022-09-05 11:47:46', '2022-09-05 11:47:46', 1);
INSERT INTO `sys_permission` VALUES ('1566634336801599490', '详情', 'sys:dept:detail', '', '/sys/dept/*', '_self', '1566631930625863682', 100, 3, 1, '2022-09-05 11:48:27', '2022-09-05 11:48:27', 1);
INSERT INTO `sys_permission` VALUES ('1566634524081467393', '列表', 'sys:dept:list', '', '/sys/depts', '_self', '1566631930625863682', 100, 3, 1, '2022-09-05 11:49:12', '2022-09-05 11:49:12', 1);
INSERT INTO `sys_permission` VALUES ('1566638440034676737', '项目管理', 'sys:teachingManagement:project', '', '/index/depts', '_self', '1566629434050314241', 100, 2, 1, '2022-09-05 12:04:46', '2022-09-05 12:04:46', 1);
INSERT INTO `sys_permission` VALUES ('1566638608834441217', '新增', 'sys:dept:add', '', '/sys/dept', '_self', '1566638440034676737', 100, 3, 1, '2022-09-05 12:05:26', '2022-09-05 12:05:26', 1);
INSERT INTO `sys_permission` VALUES ('1566638784005353474', '更新', 'sys:dept:update', '', '/sys/dept', '_self', '1566638440034676737', 100, 3, 1, '2022-09-05 12:06:08', '2022-09-05 12:06:08', 1);
INSERT INTO `sys_permission` VALUES ('1566638912346861569', '列表', 'sys:dept:list', '', '/sys/depts', '_self', '1566638440034676737', 100, 3, 1, '2022-09-05 12:06:38', '2022-09-05 12:06:38', 1);
INSERT INTO `sys_permission` VALUES ('1566639041162326017', '删除', 'sys:dept:deleted', '', '/sys/dept/*', '_self', '1566638440034676737', 100, 3, 1, '2022-09-05 12:07:09', '2022-09-05 12:07:09', 1);
INSERT INTO `sys_permission` VALUES ('1566639382146658305', '详情', 'sys:dept:detail', '', '/sys/dept/*', '_self', '1566638440034676737', 100, 3, 1, '2022-09-05 12:08:30', '2022-09-05 12:08:30', 1);
INSERT INTO `sys_permission` VALUES ('1566639948834877441', '教学实验管理', 'sys:teachingManagement:experiment', '', '/index/depts', '_self', '1566629434050314241', 100, 2, 1, '2022-09-05 12:10:45', '2022-09-05 12:10:45', 1);
INSERT INTO `sys_permission` VALUES ('1566640482950131713', '列表', 'sys:dept:list', '', '/sys/depts', '_self', '1566639948834877441', 100, 3, 1, '2022-09-05 12:12:53', '2022-09-05 12:12:53', 1);
INSERT INTO `sys_permission` VALUES ('1566640610926735361', '新增', 'sys:dept:add', '', '/sys/dept', '_self', '1566639948834877441', 100, 3, 1, '2022-09-05 12:13:23', '2022-09-05 12:13:23', 1);
INSERT INTO `sys_permission` VALUES ('1566640715528482817', '更新', 'sys:dept:update', '', '/sys/dept', '_self', '1566639948834877441', 100, 3, 1, '2022-09-05 12:13:48', '2022-09-05 12:13:48', 1);
INSERT INTO `sys_permission` VALUES ('1566640818590920706', '删除', 'sys:dept:deleted', '', '/sys/dept/*', '_self', '1566639948834877441', 100, 3, 1, '2022-09-05 12:14:13', '2022-09-05 12:14:13', 1);
INSERT INTO `sys_permission` VALUES ('1566640949356736513', '详情', 'sys:dept:detail', '', '/sys/dept/*', '_self', '1566639948834877441', 100, 3, 1, '2022-09-05 12:14:44', '2022-09-05 12:14:44', 1);
INSERT INTO `sys_permission` VALUES ('16', '列表', 'sysGenerator:list', NULL, 'sysGenerator/listByPage', NULL, '15', 1, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('17', '详情', 'sys:permission:detail', NULL, '/sys/permission/*', NULL, '11', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('18', '定时任务恢复', 'sysJob:resume', NULL, '/sysJob/resume', '_self', '59', 4, 3, 1, '2020-04-22 15:48:40', NULL, 1);
INSERT INTO `sys_permission` VALUES ('19', '列表', 'sys:role:list', NULL, '/sys/roles', NULL, '53', 0, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('2', 'SQL 监控', 'sys:sql', '', '/druid/sql.html', '_self', '21', 98, 2, 1, '2020-03-19 13:29:40', '2020-05-07 13:36:59', 1);
INSERT INTO `sys_permission` VALUES ('20', '修改', 'sysGenerator:update', NULL, 'sysGenerator/update', NULL, '15', 1, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('21', '其他', 'other', 'layui-icon-list', NULL, NULL, '0', 200, 1, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('22', '详情', 'sys:dept:detail', NULL, '/sys/dept/*', NULL, '41', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('23', '列表', 'sys:user:list', NULL, '/sys/users', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('24', '用户管理', 'sys:user', NULL, '/index/users', '_self', '51', 100, 2, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('25', '详情', 'sys:user:detail', NULL, '/sys/user/*', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('26', '删除', 'sys:permission:deleted', NULL, '/sys/permission/*', NULL, '11', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('27', '文件管理', 'sys:file', '', '/index/sysFiles', '_self', '54', 10, 2, 1, NULL, '2020-06-15 16:00:29', 1);
INSERT INTO `sys_permission` VALUES ('28', '列表', 'sysFiles:list', NULL, 'sysFiles/listByPage', NULL, '27', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('29', '新增', 'sysFiles:add', NULL, 'sysFiles/add', NULL, '27', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('3', '新增', 'sys:role:add', NULL, '/sys/role', NULL, '53', 0, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('30', '删除', 'sysFiles:delete', NULL, 'sysFiles/delete', NULL, '27', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('31', '文章管理', 'sys:article', NULL, '/index/sysContent', '_self', '54', 10, 2, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('32', '列表', 'sysContent:list', NULL, 'sysContent/listByPage', NULL, '31', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('33', '新增', 'sysContent:add', NULL, 'sysContent/add', NULL, '31', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('34', '修改', 'sysContent:update', NULL, 'sysContent/update', NULL, '31', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('35', '删除', 'sysContent:delete', NULL, 'sysContent/delete', NULL, '31', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('36', '更新', 'sys:role:update', NULL, '/sys/role', NULL, '53', 0, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('38', '更新', 'sys:dept:update', NULL, '/sys/dept', NULL, '41', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('39', '详情', 'sys:role:detail', NULL, '/sys/role/*', NULL, '53', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('4', '添加', 'sysGenerator:add', NULL, 'sysGenerator/add', NULL, '15', 1, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('40', '编辑', 'sys:permission:update', NULL, '/sys/permission', NULL, '11', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('41', '课程知识点管理', 'sys:teachingManagement:sys:teachingManagement:sys:teachingManagement:sys:teachingManagement:sys:teachingManagement:knowledge', '', '/index/depts', '_self', '1566629434050314241', 100, 2, 1, '2020-03-19 13:29:40', '2022-09-05 11:29:32', 1);
INSERT INTO `sys_permission` VALUES ('42', '新增', 'sys:user:add', NULL, '/sys/user', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('43', '列表', 'sys:permission:list', NULL, '/sys/permissions', NULL, '11', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('44', '新增', 'sys:permission:add', NULL, '/sys/permission', NULL, '11', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('45', '字典管理', 'sys:dict', '', '/index/sysDict', NULL, '54', 10, 2, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('46', '列表', 'sysDict:list', NULL, 'sysDict/listByPage', NULL, '45', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('47', '新增', 'sysDict:add', NULL, 'sysDict/add', NULL, '45', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('48', '修改', 'sysDict:update', NULL, 'sysDict/update', NULL, '45', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('49', '删除', 'sysDict:delete', NULL, 'sysDict/delete', NULL, '45', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('5', '删除', 'sys:dept:deleted', NULL, '/sys/dept/*', NULL, '41', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('50', '表单构建', 'sys:order', '', '/index/build', '_self', '21', 1, 2, 1, '2020-04-22 13:09:41', '2020-05-07 13:36:47', 1);
INSERT INTO `sys_permission` VALUES ('51', '用户权限管理', 'permission', 'layui-icon-user', '', '_self', '0', 1, 1, 1, '2020-03-19 13:29:40', '2022-09-05 11:32:30', 1);
INSERT INTO `sys_permission` VALUES ('52', '拥有角色', 'sys:user:role:detail', NULL, '/sys/user/roles/*', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('53', '角色管理', 'sys:role', NULL, '/index/roles', '_self', '51', 99, 2, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('54', '系统管理', 'system', 'layui-icon-set-fill', NULL, NULL, '0', 98, 1, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('55', '定时任务暂停', 'sysJob:pause', NULL, '/sysJob/pause', '_self', '59', 1, 3, 1, '2020-04-22 15:48:18', NULL, 1);
INSERT INTO `sys_permission` VALUES ('56', '更新', 'sys:user:update', NULL, '/sys/user', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('57', '删除', 'sys:user:deleted', NULL, '/sys/user', NULL, '24', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('58', '删除', 'sys:log:deleted', NULL, '/sys/logs', NULL, '8', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('59', '定时任务', 'sysJob', NULL, '/index/sysJob', '_self', '54', 10, 2, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('6', '接口管理', 'sys:interface', '', '/doc.html', '_blank', '21', 100, 2, 1, '2020-03-19 13:29:40', '2020-05-07 13:36:02', 1);
INSERT INTO `sys_permission` VALUES ('60', '列表', 'sysJob:list', NULL, 'sysJob/listByPage', NULL, '59', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('61', '新增', 'sysJob:add', NULL, 'sysJob/add', NULL, '59', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('62', '修改', 'sysJob:update', NULL, 'sysJob/update', NULL, '59', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('63', '删除', 'sysJob:delete', NULL, 'sysJob/delete', NULL, '59', 0, 3, 1, NULL, NULL, 1);
INSERT INTO `sys_permission` VALUES ('7', '列表', 'sys:log:list', NULL, '/sys/logs', NULL, '8', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('8', '日志管理', 'sys:log', NULL, '/index/logs', '_self', '54', 97, 2, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);
INSERT INTO `sys_permission` VALUES ('9', '新增', 'sys:dept:add', NULL, '/sys/dept', NULL, '41', 100, 3, 1, '2020-03-19 13:29:40', '2020-03-19 13:29:40', 1);

SET FOREIGN_KEY_CHECKS = 1;





SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '主键',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
  `description` varchar(300) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
  `status` tinyint NULL DEFAULT NULL COMMENT '状态(1:正常0:弃用)',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `deleted` tinyint NULL DEFAULT NULL COMMENT '是否删除(1未删除;0已删除)',
  `data_scope` int NULL DEFAULT NULL COMMENT '数据范围(1:所有 2:自定义 3: 本部门及以下部门 4:仅本部门 5:自己)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统角色' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', '超级管理员', '拥有所有权限-不能删除', 1, '2019-11-01 19:26:29', '2022-09-05 12:15:55', 1, 1);
INSERT INTO `sys_role` VALUES ('2', '测试人员', '测试人员', 1, '2023-06-14 16:22:25', '2023-06-14 16:22:27', 1, 1);
INSERT INTO `sys_role` VALUES ('3', '普通用户', '普通用户', 1, '2023-06-14 16:24:37', '2023-06-14 16:24:40', 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

package com.lxd.springsecuritydemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 权限表
 * @Date: 2023/6/15 0015 13:41
 */
@Data
@TableName(value = "sys_permission")
public class SysPermission implements Serializable {
    @TableId
    private String id;

    @ApiModelProperty("菜单权限名称")
    @NotBlank(message = "菜单权限名称不能为空")
    private String name;

    @ApiModelProperty("授权(多个用逗号分隔,如:sys:user:add,sys:user:edit)")
    private String perms;

    @ApiModelProperty("访问地址URL")
    private String url;

    @ApiModelProperty("图标")
    private String icon;

    @ApiModelProperty("a target属性:_self _blank")
    private String target;

    @ApiModelProperty("父级菜单权限名称")
    @NotNull(message = "所属菜单不能为空")
    private String pid;

    @ApiModelProperty("排序")
    private Integer orderNum;

    @ApiModelProperty("菜单权限类型(1:目录;2:菜单;3:按钮)")
    @NotNull(message = "菜单权限类型不能为空")
    private Integer type;

    @ApiModelProperty("状态1:正常 0:禁用")
    private Integer status;

    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    @ApiModelProperty("是否删除(1未删除;0已删除)")
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;

    @ApiModelProperty("父级菜单权限名称")
    @TableField(exist = false)
    private String pidName;

}

package com.lxd.springsecuritydemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 角色表
 * @Date: 2023/6/15 0015 13:45
 */
@TableName(value = "sys_role")
@Data
public class SysRole implements Serializable {

    @TableId
    private String id;

    @ApiModelProperty("角色名称")
    @NotBlank(message = "名称不能为空")
    private String name;

    @ApiModelProperty("角色描述")
    private String description;

    @ApiModelProperty("状态(1:正常0:弃用)")
    private Integer status;

    @ApiModelProperty("")
    @TableField(exist = false)
    private Integer dataScope;

    @ApiModelProperty("")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    @ApiModelProperty("")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    @ApiModelProperty("是否删除(1未删除;0已删除)")
    @TableField(fill = FieldFill.INSERT)
    private Integer deleted;

    @ApiModelProperty("")
    @TableField(exist = false)
    private String startTime;

    @ApiModelProperty("")
    @TableField(exist = false)
    private String endTime;

    @ApiModelProperty("")
    @TableField(exist = false)
    private List<String> permissions;

}

package com.lxd.springsecuritydemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Date;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 角色权限关联表
 * @Date: 2023/6/15 0015 13:50
 */
@TableName(value = "sys_role_permission")
@Data
public class SysRolePermission {

    @TableId
    private String id;

    @ApiModelProperty("角色ID")
    private String roleId;

    @ApiModelProperty("权限ID")
    private String permissionId;

    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
}

package com.lxd.springsecuritydemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

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

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 用户角色关联表
 * @Date: 2023/6/15 0015 13:52
 */
@TableName(value = "sys_user_role")
@Data
public class SysUserRole implements Serializable {

    @TableId
    private String id;

    @ApiModelProperty("用户ID")
    private String userId;

    @ApiModelProperty("角色ID")
    private String roleId;

    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
}




3.2.3.3 代码实现

​ 我们只需要根据用户id去查询到其所对应的权限信息即可。

​ 所以我们可以先定义个mapper,其中提供一个方法可以根据userid查询权限信息。

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lxd.springsecuritydemo.entity.SysPermission;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface PermissionMapper extends BaseMapper<SysPermission> {
}


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lxd.springsecuritydemo.entity.SysRole;
import org.apache.ibatis.annotations.Mapper;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 角色
 * @Date: 2023/6/15 0015 13:59
 */
@Mapper
public interface RoleMapper extends BaseMapper<SysRole> {
}


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lxd.springsecuritydemo.entity.SysRolePermission;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface RolePermissionMapper extends BaseMapper<SysRolePermission> {
}


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lxd.springsecuritydemo.entity.SysUserRole;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserRoleMapper extends BaseMapper<SysUserRole> {
}

​ 尤其是自定义方法,根据用户ID查询所拥有的权限和角色

import com.baomidou.mybatisplus.extension.service.IService;
import com.lxd.springsecuritydemo.entity.SysPermission;

import java.util.List;

public interface PermissionService extends IService<SysPermission> {

    /***
     * 功能描述:
     * 根据用户ID查询拥有的角色以及权限
     * @Author: LXD
     * @Date: 2023-06-15 14:09:32
     * @Param  userId:
     * @return: java.util.List
     * @since: 1.0.0
     */
    List<SysPermission> getRolePermissionByUserId(String userId);
}

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lxd.springsecuritydemo.entity.SysPermission;
import com.lxd.springsecuritydemo.entity.SysRole;
import com.lxd.springsecuritydemo.entity.SysRolePermission;
import com.lxd.springsecuritydemo.mapper.PermissionMapper;
import com.lxd.springsecuritydemo.service.PermissionService;
import com.lxd.springsecuritydemo.service.RolePermissionService;
import com.lxd.springsecuritydemo.service.UserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 权限实现类
 * @Date: 2023/6/15 0015 14:02
 */
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, SysPermission> implements PermissionService {

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RolePermissionService rolePermissionService;

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List<SysPermission> getRolePermissionByUserId(String userId) {
        // 根据用户ID查询拥有的所有角色ID
        List<String> roles =  userRoleService.getRoleIdsByUserId(userId);
        if(CollectionUtils.isEmpty(roles)){
            return null;
        }
        // 根据角色ID查询权限字符串
        List<Object> permissionIds = rolePermissionService.listObjs(Wrappers.<SysRolePermission>lambdaQuery().select(SysRolePermission::getPermissionId).in(SysRolePermission::getRoleId,roles));
        if(CollectionUtils.isEmpty(permissionIds)){
            return null;
        }
        LambdaQueryWrapper<SysPermission> queryWrapper = Wrappers.<SysPermission>lambdaQuery().in(SysPermission::getId,permissionIds).orderByAsc(SysPermission::getOrderNum);
        return permissionMapper.selectList(queryWrapper);
    }
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.lxd.springsecuritydemo.entity.SysUserRole;

import java.util.List;

public interface UserRoleService extends IService<SysUserRole> {

    /**
     * 根据userId获取绑定的角色id
     *
     * @param userId userId
     * @return List
     */
    List<String> getRoleIdsByUserId(String userId);
}
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lxd.springsecuritydemo.entity.SysUserRole;
import com.lxd.springsecuritydemo.mapper.UserRoleMapper;
import com.lxd.springsecuritydemo.service.UserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 用户角色关联
 * @Date: 2023/6/15 0015 14:04
 */
@Service
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, SysUserRole> implements UserRoleService {

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Override
    public List getRoleIdsByUserId(String userId) {
        LambdaQueryWrapper<SysUserRole> queryWrapper = Wrappers.<SysUserRole>lambdaQuery().select(SysUserRole::getRoleId).eq(SysUserRole::getUserId,userId);
        return userRoleMapper.selectObjs(queryWrapper);
    }
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.lxd.springsecuritydemo.entity.SysPermission;

import java.util.List;

public interface PermissionService extends IService<SysPermission> {

    /***
     * 功能描述:
     * 根据用户ID查询拥有的角色以及权限
     * @Author: LXD
     * @Date: 2023-06-15 14:09:32
     * @Param  userId:
     * @return: java.util.List
     * @since: 1.0.0
     */
    List<SysPermission> getRolePermissionByUserId(String userId);
}

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lxd.springsecuritydemo.entity.SysPermission;
import com.lxd.springsecuritydemo.entity.SysRole;
import com.lxd.springsecuritydemo.entity.SysRolePermission;
import com.lxd.springsecuritydemo.mapper.PermissionMapper;
import com.lxd.springsecuritydemo.service.PermissionService;
import com.lxd.springsecuritydemo.service.RolePermissionService;
import com.lxd.springsecuritydemo.service.UserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 权限实现类
 * @Date: 2023/6/15 0015 14:02
 */
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, SysPermission> implements PermissionService {

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RolePermissionService rolePermissionService;

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List<SysPermission> getRolePermissionByUserId(String userId) {
        // 根据用户ID查询拥有的所有角色ID
        List<String> roles =  userRoleService.getRoleIdsByUserId(userId);
        if(CollectionUtils.isEmpty(roles)){
            return null;
        }
        // 根据角色ID查询权限字符串
        List<Object> permissionIds = rolePermissionService.listObjs(Wrappers.<SysRolePermission>lambdaQuery().select(SysRolePermission::getPermissionId).in(SysRolePermission::getRoleId,roles));
        if(CollectionUtils.isEmpty(permissionIds)){
            return null;
        }
        LambdaQueryWrapper<SysPermission> queryWrapper = Wrappers.<SysPermission>lambdaQuery().in(SysPermission::getId,permissionIds).orderByAsc(SysPermission::getOrderNum);
        return permissionMapper.selectList(queryWrapper);
    }
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.lxd.springsecuritydemo.entity.SysRole;

public interface RoleService extends IService<SysRole> {

}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lxd.springsecuritydemo.entity.SysRole;
import com.lxd.springsecuritydemo.mapper.RoleMapper;
import com.lxd.springsecuritydemo.service.RoleService;
import org.springframework.stereotype.Service;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 角色实现类
 * @Date: 2023/6/15 0015 13:58
 */
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, SysRole> implements RoleService {
}

​ 在application.yml中配置mapperXML文件的位置

# 开发环境配置
spring:
  thymeleaf:
    cache: false
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springsecurity?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=true
    xa: # 该配置用于开启读取表注释(remarks)信息
      properties:
        useInformationSchema: true
  redis:
    host: 127.0.0.1 # Redis服务器地址
    database: 0 # Redis数据库索引(默认为0)
    port: 6379 # Redis服务器连接端口
    password: # Redis服务器连接密码(默认为空)
    jedis:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-idle: 8 # 连接池中的最大空闲连接
        min-idle: 0 # 连接池中的最小空闲连接
    timeout: 3000ms # 连接超时时间(毫秒)
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/${project.database}/**/*.xml,classpath:mapper/*.xml
  global-config:
    db-config:
      logic-delete-value: 0
      logic-not-delete-value: 1
      logic-delete-field: deleted

​ 然后我们可以在UserDetailsServiceImpl中去调用该mapper的方法查询权限信息封装到LoginUser对象中即可。

package com.lxd.springsecuritydemo.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.lxd.springsecuritydemo.entity.LoginUser;
import com.lxd.springsecuritydemo.entity.SysPermission;
import com.lxd.springsecuritydemo.entity.User;
import com.lxd.springsecuritydemo.mapper.UserMapper;
import com.lxd.springsecuritydemo.service.PermissionService;
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.stereotype.Service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description:
 * @Date: 2023/6/13 0013 16:42
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PermissionService permissionService;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        // 查询用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername,s);
        User user = userMapper.selectOne(queryWrapper);
        // 如果没有查询到用户就抛出异常
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }
        // 查询对应权限信息
        List<String> permissions = permissionService.getRolePermissionByUserId(user.getId()).stream().map(SysPermission::getPerms).collect(Collectors.toList());
        // 把数据封装成UserDetails返回
        return new LoginUser(user,permissions);
    }
}

import com.lxd.springsecuritydemo.annotation.LogAnnotation;
import com.lxd.springsecuritydemo.service.UserService;
import com.lxd.springsecuritydemo.utils.DataResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 功能描述:
 *
 * @Author: liuxd
 * @description: 用户控制层
 * @Date: 2023/6/15 0015 14:31
 */
@RestController
@RequestMapping("/api/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/listUser")
    @LogAnnotation(title = "用户管理", action = "用户查询")
    @PreAuthorize("hasAuthority('sys:user:list')") // 判断是否具有sys:user:list权限
//    @PreAuthorize("hasAnyAuthority('admin','test','sys:user:update')") // 判断是否具有两个角色权限中的任意一个均可
//    @PreAuthorize("hasRole('sys:user:list')") // 用户对应的权限也要有 ROLE_ 这个前缀才可以
//    @PreAuthorize("hasAnyRole('admin','sys:user:list')") // 用户对应的权限也要有 ROLE_ 这个前缀才可以
//    @PreAuthorize("@ex.hasAuthority('system:user:list')") // 自定义权限校验方法    在SPEL表达式中使用 @ex相当于获取容器中bean的名字为ex的对象。然后再调用这个对象的hasAuthority方法
    public DataResult listUser(){
        return DataResult.success(userService.list());
    }
}

4. 自定义失败处理

​ 我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

​ 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

​ 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。

①自定义实现类

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);

    }
}


@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败请重新登录");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);
    }
}

②配置给SpringSecurity

​ 先在SecurityConfig中注入对应的处理器

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

​ 然后我们可以使用HttpSecurity对象的方法去配置。

        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).
                accessDeniedHandler(accessDeniedHandler);

5. 跨域

​ 浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

​ 前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题。

​ 所以我们就要处理一下,让前端能进行跨域请求。

①先对SpringBoot配置,运行跨域请求

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
      // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

②开启SpringSecurity的跨域访问

由于我们的资源都会收到SpringSecurity的保护,所以想要跨域访问还要让SpringSecurity运行跨域访问。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //添加过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //配置异常处理器
        http.exceptionHandling()
                //配置认证失败处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);

        //允许跨域
        http.cors();
    }

6. 遗留小问题

其它权限校验方法

​ 我们前面都是使用@PreAuthorize注解,然后在在其中使用的是hasAuthority方法进行校验。SpringSecurity还为我们提供了其它方法例如:hasAnyAuthority,hasRole,hasAnyRole等。

​ 这里我们先不急着去介绍这些方法,我们先去理解hasAuthority的原理,然后再去学习其他方法你就更容易理解,而不是死记硬背区别。并且我们也可以选择定义校验方法,实现我们自己的校验逻辑。

​ hasAuthority方法实际是执行到了SecurityExpressionRoot的hasAuthority,大家只要断点调试既可知道它内部的校验原理。

​ 它内部其实是调用authentication的getAuthorities方法获取用户的权限列表。然后判断我们存入的方法参数数据在权限列表中。

​ hasAnyAuthority方法可以传入多个权限,只有用户有其中任意一个权限都可以访问对应资源。

    @PreAuthorize("hasAnyAuthority('admin','test','system:dept:list')")
    public String hello(){
        return "hello";
    }

​ hasRole要求有对应的角色才可以访问,但是它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

    @PreAuthorize("hasRole('system:dept:list')")
    public String hello(){
        return "hello";
    }

​ hasAnyRole 有任意的角色就可以访问。它内部也会把我们传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

    @PreAuthorize("hasAnyRole('admin','system:dept:list')")
    public String hello(){
        return "hello";
    }

自定义权限校验方法

​ 我们也可以定义自己的权限校验方法,在@PreAuthorize注解中使用我们的方法。

@Component("ex")
public class ExpressionRoot {

    public boolean hasAuthority(String authority){
        //获取当前用户的权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        List<String> permissions = loginUser.getPermissions();
        //判断用户权限集合中是否存在authority
        return permissions.contains(authority);
    }
}

​ 在SPEL表达式中使用 @ex相当于获取容器中bean的名字未ex的对象。然后再调用这个对象的hasAuthority方法

    @RequestMapping("/hello")
    @PreAuthorize("@ex.hasAuthority('system:dept:list')")
    public String hello(){
        return "hello";
    }

基于配置的权限控制

​ 我们也可以在配置类中使用使用配置的方式对资源进行权限控制。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                .antMatchers("/testCors").hasAuthority("system:dept:list222")
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //添加过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //配置异常处理器
        http.exceptionHandling()
                //配置认证失败处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);

        //允许跨域
        http.cors();
    }

CSRF

​ CSRF是指跨站请求伪造(Cross-site request forgery),是web常见的攻击之一。

​ https://blog.csdn.net/freeking101/article/details/86537087

​ SpringSecurity去防止CSRF攻击的方式就是通过csrf_token。后端会生成一个csrf_token,前端发起请求的时候需要携带这个csrf_token,后端会有过滤器进行校验,如果没有携带或者是伪造的就不允许访问。

​ 我们可以发现CSRF攻击依靠的是cookie中所携带的认证信息。但是在前后端分离的项目中我们的认证信息其实是token,而token并不是存储中cookie中,并且需要前端代码去把token设置到请求头中才可以,所以CSRF攻击也就不用担心了。

认证成功处理器

​ 实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果登录成功了是会调用AuthenticationSuccessHandler的方法进行认证成功后的处理的。AuthenticationSuccessHandler就是登录成功处理器。

​ 我们也可以自己去自定义成功处理器进行成功后的相应处理。

@Component
public class SGSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("认证成功了");
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().successHandler(successHandler);

        http.authorizeRequests().anyRequest().authenticated();
    }
}

认证失败处理器

​ 实际上在UsernamePasswordAuthenticationFilter进行登录认证的时候,如果认证失败了是会调用AuthenticationFailureHandler的方法进行认证失败后的处理的。AuthenticationFailureHandler就是登录失败处理器。

​ 我们也可以自己去自定义失败处理器进行失败后的相应处理。

@Component
public class SGFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        System.out.println("认证失败了");
    }
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    private AuthenticationFailureHandler failureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
//                配置认证成功处理器
                .successHandler(successHandler)
//                配置认证失败处理器
                .failureHandler(failureHandler);

        http.authorizeRequests().anyRequest().authenticated();
    }
}

登出成功处理器

@Component
public class SGLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("注销成功");
    }
}

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler successHandler;

    @Autowired
    private AuthenticationFailureHandler failureHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
//                配置认证成功处理器
                .successHandler(successHandler)
//                配置认证失败处理器
                .failureHandler(failureHandler);

        http.logout()
                //配置注销成功处理器
                .logoutSuccessHandler(logoutSuccessHandler);

        http.authorizeRequests().anyRequest().authenticated();
    }
}

7.自定义日志切面

package com.lxd.springsecuritydemo.aspect;

import com.alibaba.fastjson.JSON;
import com.lxd.springsecuritydemo.annotation.LogAnnotation;
import com.lxd.springsecuritydemo.entity.LoginUser;
import com.lxd.springsecuritydemo.entity.SysLog;
import com.lxd.springsecuritydemo.mapper.SysLogMapper;
import com.lxd.springsecuritydemo.utils.HttpContextUtils;
import com.lxd.springsecuritydemo.utils.IPUtils;
import com.lxd.springsecuritydemo.utils.RedisCache;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;

/**
 * 日志切面
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {
    @Lazy
    @Resource
    private SysLogMapper sysLogMapper;

    @Autowired
    private RedisCache redisCache;

    /*@Lazy
    @Resource
    private HttpSessionService httpSessionService;*/

    /**
     * 此处的切点是注解的方式
     * 只要出现 @LogAnnotation注解都会进入
     */
    @Pointcut("@annotation(com.lxd.springsecuritydemo.annotation.LogAnnotation)")
    public void logPointCut() {

    }

    /**
     * 环绕增强,相当于MethodInterceptor
     */
    @Around("logPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long beginTime = System.currentTimeMillis();
        //执行方法
        Object result = point.proceed();
        //执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;

        //保存日志
        try {
            saveSysLog(point, time);
        } catch (Exception e) {
            log.error("sysLog,exception:{}", e, e);
        }

        return result;
    }

    /**
     * 把日志保存
     */
    private void saveSysLog(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        SysLog sysLog = new SysLog();
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        if (logAnnotation != null) {
            //注解上的描述
            sysLog.setOperation(logAnnotation.title() + "-" + logAnnotation.action());
        }

        //请求的方法名
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        log.info("请求{}.{}耗时{}毫秒", className, methodName, time);
        try {
            //请求的参数
            Object[] args = joinPoint.getArgs();
            String params = null;
            if (args.length != 0) {
                params = JSON.toJSONString(args);
            }

            sysLog.setParams(params);
        } catch (Exception e) {
            log.error("sysLog,exception:{}", e, e);
        }
        //获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        //设置IP地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        log.info("Ip{},接口地址{},请求方式{},入参:{}", sysLog.getIp(), request.getRequestURL(), request.getMethod(), sysLog.getParams());
        //用户名
//        String userId = httpSessionService.getCurrentUserId();
//        String username = httpSessionService.getCurrentUsername();
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String userId = (String) authentication.getPrincipal();

        if(StringUtils.isNotBlank(userId)){
            LoginUser loginUser = redisCache.getCacheObject("login:"+userId);
            if(!Objects.isNull(loginUser)){
                sysLog.setUsername(loginUser.getUser().getUsername());
                sysLog.setUserId(loginUser.getUser().getId());
            }
        }
        sysLog.setCreateTime(new Date());
        sysLog.setTime((int) time);
        log.info(sysLog.toString());
        sysLogMapper.insert(sysLog);

    }
}

import java.lang.annotation.*;

/**
 * LogAnnotation
 *
 * @author lxd
 * @version V1.0
 * @date 2023年3月18日
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    /**
     * 模块
     */
    String title() default "";

    /**
     * 功能
     */
    String action() default "";
}

package com.lxd.springsecuritydemo.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.lxd.springsecuritydemo.annotation.LogAnnotation;
import com.lxd.springsecuritydemo.entity.SysLog;
import com.lxd.springsecuritydemo.service.LogService;
import com.lxd.springsecuritydemo.utils.DataResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;

/**
 * 系统操作日志
 *
 * @author wenbin
 * @version V1.0
 * @date 2020年3月18日
 */
@RequestMapping("/sys")
@Api(tags = "系统模块-系统操作日志管理")
@RestController
public class SysLogController {
    @Resource
    private LogService logService;

    @PostMapping("/logs")
    @ApiOperation(value = "分页查询系统操作日志接口")
    @LogAnnotation(title = "系统操作日志管理", action = "分页查询系统操作日志")
    public DataResult pageInfo(@RequestBody SysLog vo) {
        Page page = new Page(vo.getPage(), vo.getLimit());
        LambdaQueryWrapper<SysLog> queryWrapper = Wrappers.lambdaQuery();
        if (!StringUtils.isEmpty(vo.getUsername())) {
            queryWrapper.like(SysLog::getUsername, vo.getUsername());
        }
        if (!StringUtils.isEmpty(vo.getOperation())) {
            queryWrapper.like(SysLog::getOperation, vo.getOperation());
        }
        if (!StringUtils.isEmpty(vo.getStartTime())) {
            queryWrapper.gt(SysLog::getCreateTime, vo.getStartTime());
        }
        if (!StringUtils.isEmpty(vo.getEndTime())) {
            queryWrapper.lt(SysLog::getCreateTime, vo.getEndTime());
        }
        queryWrapper.orderByDesc(SysLog::getCreateTime);
        return DataResult.success(logService.page(page, queryWrapper));
    }

    @DeleteMapping("/logs")
    @ApiOperation(value = "删除日志接口")
    @LogAnnotation(title = "系统操作日志管理", action = "删除系统操作日志")
    public DataResult deleted(@RequestBody List<String> logIds) {
        logService.removeByIds(logIds);
        return DataResult.success();
    }
}

在控制层请求接口的地方添加注解

 @RequestMapping("/login")
    @ApiOperation(value = "登录")
    // 添加日志注解
    @LogAnnotation(title = "用户登录", action = "用户登录")
    public DataResult login(@RequestBody User user){
        // 登录
        return loginService.login(user);
    }

源码下载地址

Demo源码下载地址: link

你可能感兴趣的:(JAVA,java,mybatis,spring)