当前位置:   article > 正文

基于spring boot的RBAC超详细实现_springboot rbac

springboot rbac

基于spring boot + mybatis + jwt + shiro + redis + postgresql的RBAC实现

码云源码地址:https://gitee.com/loveleaveslove/rbac-demo.git


一、搭建数据库

1、用户表

  1. -- auto-generated definition
  2. create table t_user
  3. (
  4.   id       bigint       not null
  5.        constraint "T_USER_pkey"
  6.            primary key,
  7.   username varchar(64)  not null,
  8.   password varchar(128) not null,
  9.   name     varchar(64),
  10.   roleid   bigint       not null,
  11.   age      integer,
  12.   icon     varchar(128),
  13.   sex      varchar(2),
  14.   phone    varchar(11),
  15.   email    varchar(64),
  16.   area     varchar(128)
  17. );
  18. comment on table t_user is '用户表';
  19. comment on column t_user.id is '用户ID';
  20. comment on column t_user.username is '用户名';
  21. comment on column t_user.password is '用户密码';
  22. comment on column t_user.roleid is '角色ID';
  23. alter table t_user owner to postgres;
字段类型备注
idserial4用户编号
usernamevarchar用户名
passwordsvarchar用户密码
roleidint4用户权限编号

2、角色表(由于是简单实现其功能,所以此处简单代替)

  1. create table t_role
  2. (
  3.   id       bigint not null
  4.        constraint "T_ROLE_pkey"
  5.            primary key,
  6.   name     varchar(64),
  7.   auths    varchar(128),
  8.   parentid bigint
  9. );
  10. comment on table t_role is '角色表';
  11. comment on column t_role.id is '角色ID ';
  12. comment on column t_role.name is '角色编号';
  13. comment on column t_role.auths is '权限ID';
  14. comment on column t_role.parentid is '父级角色ID';
  15. alter table t_role owner to postgres;
字段类型备注
idserial4角色编号
rolenamevarchar角色名称
authsvarchar角色描述
parentidInteger父级角色ID

二、pom.xml中导入依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3.         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4.    <modelVersion>4.0.0</modelVersion>
  5.    <parent>
  6.        <groupId>org.springframework.boot</groupId>
  7.        <artifactId>spring-boot-starter-parent</artifactId>
  8.        <version>2.3.7.RELEASE</version>
  9.        <relativePath/> <!-- lookup parent from repository -->
  10.    </parent>
  11.    <groupId>com.leaves.auth</groupId>
  12.    <artifactId>rbac-demo</artifactId>
  13.    <version>0.0.1-SNAPSHOT</version>
  14.    <name>rbac-demo</name>
  15.    <description>Demo project for Spring Boot</description>
  16.    <properties>
  17.        <java.version>1.8</java.version>
  18.    </properties>
  19.    <dependencies>
  20.        <!--spring-web-->
  21.        <dependency>
  22.            <groupId>org.springframework.boot</groupId>
  23.            <artifactId>spring-boot-starter-web</artifactId>
  24.        </dependency>
  25.        <!--spring-reids-->
  26.        <dependency>
  27.            <groupId>org.springframework.boot</groupId>
  28.            <artifactId>spring-boot-starter-data-redis</artifactId>
  29.        </dependency>
  30.        <!--JDBC-->
  31.        <dependency>
  32.            <groupId>org.springframework.boot</groupId>
  33.            <artifactId>spring-boot-starter-jdbc</artifactId>
  34.        </dependency>
  35.        <!--mybatis-->
  36.        <dependency>
  37.            <groupId>org.mybatis.spring.boot</groupId>
  38.            <artifactId>mybatis-spring-boot-starter</artifactId>
  39.            <version>2.1.4</version>
  40.        </dependency>
  41.        <!--postgresql-->
  42.        <dependency>
  43.            <groupId>org.postgresql</groupId>
  44.            <artifactId>postgresql</artifactId>
  45.            <scope>runtime</scope>
  46.        </dependency>
  47.        <!--lombok-->
  48.        <dependency>
  49.            <groupId>org.projectlombok</groupId>
  50.            <artifactId>lombok</artifactId>
  51.            <optional>true</optional>
  52.        </dependency>
  53.        <!--druid-->
  54.        <dependency>
  55.            <groupId>com.alibaba</groupId>
  56.            <artifactId>druid</artifactId>
  57.            <version>1.1.21</version>
  58.        </dependency>
  59.        <!--redis-Jedis-->
  60.        <dependency>
  61.            <groupId>redis.clients</groupId>
  62.            <artifactId>jedis</artifactId>
  63.            <version>2.9.0</version>
  64.        </dependency>
  65.        <!--shiro-reids-->
  66.        <dependency>
  67.            <groupId>org.crazycake</groupId>
  68.            <artifactId>shiro-redis</artifactId>
  69.            <version>3.2.3</version>
  70.        </dependency>
  71.        <!-- Shiro -->
  72.        <dependency>
  73.            <groupId>org.apache.shiro</groupId>
  74.            <artifactId>shiro-spring</artifactId>
  75.            <version>1.3.2</version>
  76.        </dependency>
  77.        <dependency>
  78.            <groupId>org.apache.shiro</groupId>
  79.            <artifactId>shiro-ehcache</artifactId>
  80.            <version>1.2.5</version>
  81.        </dependency>
  82.        <!-- JWT -->
  83.        <dependency>
  84.            <groupId>com.auth0</groupId>
  85.            <artifactId>java-jwt</artifactId>
  86.            <version>3.5.0</version>
  87.        </dependency>
  88.        <!--slf4j-->
  89.        <dependency>
  90.            <groupId>org.slf4j</groupId>
  91.            <artifactId>slf4j-api</artifactId>
  92.            <version>1.7.25</version>
  93.        </dependency>
  94.        <!--上传下载文件-->
  95.        <dependency>
  96.            <groupId>commons-fileupload</groupId>
  97.            <artifactId>commons-fileupload</artifactId>
  98.            <version>1.3.1</version>
  99.        </dependency>
  100.        <dependency>
  101.            <groupId>commons-io</groupId>
  102.            <artifactId>commons-io</artifactId>
  103.            <version>1.4</version>
  104.        </dependency>
  105.        <!--添加通用mapper依赖-->
  106.        <dependency>
  107.            <groupId>tk.mybatis</groupId>
  108.            <artifactId>mapper-spring-boot-starter</artifactId>
  109.            <version>2.1.5</version>
  110.        </dependency>
  111.        <!--添加分页依赖-->
  112.        <dependency>
  113.            <groupId>com.github.pagehelper</groupId>
  114.            <artifactId>pagehelper-spring-boot-starter</artifactId>
  115.            <version>1.2.5</version>
  116.        </dependency>
  117.        <!--工具库hutool库-->
  118.        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
  119.        <dependency>
  120.            <groupId>cn.hutool</groupId>
  121.            <artifactId>hutool-all</artifactId>
  122.            <version>5.4.3</version>
  123.        </dependency>
  124.        <dependency>
  125.            <groupId>org.springframework.boot</groupId>
  126.            <artifactId>spring-boot-starter-test</artifactId>
  127.            <scope>test</scope>
  128.            <exclusions>
  129.                <exclusion>
  130.                    <groupId>org.junit.vintage</groupId>
  131.                    <artifactId>junit-vintage-engine</artifactId>
  132.                </exclusion>
  133.            </exclusions>
  134.        </dependency>
  135.    </dependencies>
  136.    <build>
  137.        <finalName>RBAC-DEMO</finalName>
  138.        <plugins>
  139.            <plugin>
  140.                <groupId>org.springframework.boot</groupId>
  141.                <artifactId>spring-boot-maven-plugin</artifactId>
  142.                <configuration>
  143.                    <excludes>
  144.                        <exclude>
  145.                            <groupId>org.projectlombok</groupId>
  146.                            <artifactId>lombok</artifactId>
  147.                        </exclude>
  148.                    </excludes>
  149.                </configuration>
  150.            </plugin>
  151.        </plugins>
  152.    </build>
  153. </project>

三、配置application.yml

  1. #端口号
  2. server:
  3. port: 8088
  4. spring:
  5.  #数据库连接配置
  6. datasource:
  7.   driver-class-name: org.postgresql.Driver
  8.   type: com.alibaba.druid.pool.DruidDataSource
  9.   url: jdbc:postgresql://localhost:5432/rbac?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&socketTimeout=30000
  10.   username: rbac
  11.   password: 123456
  12.   jackson:
  13.     date-format: yyyy-MM-dd HH:mm:ss
  14.     time-zone: GMT+8
  15.  #允许运行程序中存在多个main函数
  16. main:
  17.   allow-bean-definition-overriding: true
  18.  #Redis
  19. redis:
  20.   host: localhost
  21.   port: 6379
  22.   database: 0
  23.   jedis:
  24.     pool:
  25.       max-active: 8
  26.       max-idle: 8
  27.       max-wait: -1
  28.       min-idle: 0
  29.   timeout: 3000ms
  30.    #文件上传配置
  31.   autoconfigure:
  32.     exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
  33. #mybatis配置
  34. mybatis:
  35. type-aliases-package: com.leaves.auth.entity
  36. mapper-locations: classpath*:/mapper/*.xml
  37. #日志等级
  38. logging:
  39. level:
  40.   com:
  41.     leaves:
  42.       auth:
  43.         mapper: debug
  44. config: classpath:logback-spring.xml
  45. #文件上传保存地址
  46. upload:
  47. local:
  48.   path: D:/uploadFile/file

四、工具类redisUitls包

1、Constant

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description:   常量
  6. */
  7. private Constant() {}
  8.    /**
  9.     * redis-OK
  10.     */
  11.    public static final String OK = "OK";
  12.    /**
  13.     * redis过期时间,以秒为单位,一分钟
  14.     */
  15.    public static final int EXRP_MINUTE = 60;
  16.    /**
  17.     * redis过期时间,以秒为单位,一小时
  18.     */
  19.    public static final int EXRP_HOUR = 60 * 60;
  20.    /**
  21.     * redis过期时间,以秒为单位,一天
  22.     */
  23.    public static final int EXRP_DAY = 60 * 60 * 24;
  24.    /**
  25.     * redis-key-前缀-shiro:cache:
  26.     */
  27.    public static final String PREFIX_SHIRO_CACHE = "shiro:cache:";
  28.    /**
  29.     * redis-key-前缀-shiro:access_token:
  30.     */
  31.    public static final String PREFIX_SHIRO_ACCESS_TOKEN = "shiro:access_token:";
  32.    /**
  33.     * redis-key-前缀-shiro:refresh_token:
  34.     */
  35.    public static final String PREFIX_SHIRO_REFRESH_TOKEN = "shiro:refresh_token:";
  36.    /**
  37.     * JWT-account:
  38.     */
  39.    public static final String ACCOUNT = "account";
  40.    /**
  41.     * JWT-currentTimeMillis:
  42.     */
  43.    public static final String CURRENT_TIME_MILLIS = "currentTimeMillis";
  44.    /**
  45.     * PASSWORD_MAX_LEN
  46.     */
  47.    public static final Integer PASSWORD_MAX_LEN = 8;

2、SerializableUtil

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description:   Serializable工具(JDK)(也可以使用Protobuf自行百度)
  6. */
  7. @Slf4j
  8. public class SerializableUtil {
  9.    private SerializableUtil() {}
  10.    /**
  11.     * 序列化
  12.     */
  13.    public static byte[] serializable(Object object) {
  14.        ByteArrayOutputStream baos = null;
  15.        ObjectOutputStream oos = null;
  16.        try {
  17.            baos = new ByteArrayOutputStream();
  18.            oos = new ObjectOutputStream(baos);
  19.            oos.writeObject(object);
  20.            return baos.toByteArray();
  21.       } catch (IOException e) {
  22.            log.error("SerializableUtil工具类序列化出现IOException异常:{}", e.getMessage());
  23.            throw new CustomException("SerializableUtil工具类序列化出现IOException异常:" + e.getMessage());
  24.       } finally {
  25.            try {
  26.                if (oos != null) {
  27.                    oos.close();
  28.               }
  29.                if (baos != null) {
  30.                    baos.close();
  31.               }
  32.           } catch (IOException e) {
  33.                log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
  34.                throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
  35.           }
  36.       }
  37.   }
  38.    /**
  39.     * 反序列化
  40.     */
  41.    public static Object unserializable(byte[] bytes) {
  42.        ByteArrayInputStream bais = null;
  43.        ObjectInputStream ois = null;
  44.        try {
  45.            bais = new ByteArrayInputStream(bytes);
  46.            ois = new ObjectInputStream(bais);
  47.            return ois.readObject();
  48.       } catch (ClassNotFoundException e) {
  49.            log.error("SerializableUtil工具类反序列化出现ClassNotFoundException异常:{}", e.getMessage());
  50.            throw new CustomException("SerializableUtil工具类反序列化出现ClassNotFoundException异常:" + e.getMessage());
  51.       } catch (IOException e) {
  52.            log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
  53.            throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
  54.       } finally {
  55.            try {
  56.                if (ois != null) {
  57.                    ois.close();
  58.               }
  59.                if (bais != null) {
  60.                    bais.close();
  61.               }
  62.           } catch (IOException e) {
  63.                log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
  64.                throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
  65.           }
  66.       }
  67.   }
  68. }

3、StringUtil

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description:   String工具
  6. */
  7. public class StringUtil {
  8.    private StringUtil() {}
  9.    /**
  10.     * 定义下划线
  11.     */
  12.    private static final char UNDERLINE = '_';
  13.    /**
  14.     * String为空判断(不允许空格)
  15.     * @param str
  16.     * @return
  17.     */
  18.    public static boolean isBlank(String str) {
  19.        return str == null || "".equals(str.trim());
  20.   }
  21.    /**
  22.     * String不为空判断(不允许空格)
  23.     * @param str
  24.     * @return
  25.     */
  26.    public static boolean isNotBlank(String str) {
  27.        return !isBlank(str);
  28.   }
  29.    /**
  30.     * Byte数组为空判断
  31.     * @param bytes
  32.     * @return boolean
  33.     */
  34.    public static boolean isNull(byte[] bytes) {
  35.        // 根据byte数组长度为0判断
  36.        return bytes == null || bytes.length == 0;
  37.   }
  38.    /**
  39.     * Byte数组不为空判断
  40.     * @param bytes
  41.     * @return boolean
  42.     */
  43.    public static boolean isNotNull(byte[] bytes) {
  44.        return !isNull(bytes);
  45.   }
  46.    /**
  47.     * 驼峰转下划线工具
  48.     * @param param
  49.     * @return java.lang.String
  50.     */
  51.    public static String camelToUnderline(String param) {
  52.        if (isNotBlank(param)) {
  53.            int len = param.length();
  54.            StringBuilder sb = new StringBuilder(len);
  55.            for (int i = 0; i < len; i++) {
  56.                char c = param.charAt(i);
  57.                if (Character.isUpperCase(c)) {
  58.                    sb.append(UNDERLINE);
  59.                    sb.append(Character.toLowerCase(c));
  60.               } else {
  61.                    sb.append(c);
  62.               }
  63.           }
  64.            return sb.toString();
  65.       } else {
  66.            return "";
  67.       }
  68.   }
  69.    /**
  70.     * 下划线转驼峰工具
  71.     * @param param
  72.     * @return java.lang.String
  73.     */
  74.    public static String underlineToCamel(String param) {
  75.        if (isNotBlank(param)) {
  76.            int len = param.length();
  77.            StringBuilder sb = new StringBuilder(len);
  78.            for (int i = 0; i < len; i++) {
  79.                char c = param.charAt(i);
  80.                if (c == 95) {
  81.                    i++;
  82.                    if (i < len) {
  83.                        sb.append(Character.toUpperCase(param.charAt(i)));
  84.                   }
  85.               } else {
  86.                    sb.append(c);
  87.               }
  88.           }
  89.            return sb.toString();
  90.       } else {
  91.            return "";
  92.       }
  93.   }
  94.    /**
  95.     * 在字符串两周添加''
  96.     * @param param
  97.     * @return java.lang.String
  98.     */
  99.    public static String addSingleQuotes(String param) {
  100.        return "\'" + param + "\'";
  101.   }
  102. }

五、配置类redis

1、JedisConfig

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年11月02日 15时22分18秒
  4. * @Version 1.0
  5. */
  6. @Configuration
  7. public class JedisConfig {
  8.    @Value("${spring.redis.host}")
  9.    private String host;
  10.    @Value("${spring.redis.port}")
  11.    private int port ;
  12.    @Bean
  13.    public Jedis getJedis(){
  14.       return  new Jedis("127.0.0.1",6379);
  15.   }
  16. //   封装jedispool对象(将配置对象注入其中)
  17.    @Bean
  18.    public JedisPool jedisPool(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig ){
  19.        JedisPool pool = new JedisPool(jedisPoolConfig,host,port);
  20.        return pool;
  21.   }
  22. //   封装jedispool配置对象
  23.    @Bean("jedisPoolConfig")
  24.    public JedisPoolConfig jedisPoolConfig(){
  25.        JedisPoolConfig poolConfig = new JedisPoolConfig();
  26.        poolConfig.setMaxIdle(1);
  27.        poolConfig.setMaxTotal(10);
  28.        return poolConfig;
  29.   }
  30. }

2、JedisUtil

  1. /**
  2. * 封装常见的jedis操作接口
  3. * @Author: LEAVES
  4. * @Date: 2020年11月02日 15时22分18秒
  5. * @Version 1.0
  6. */
  7. @Component
  8. public class JedisUtil {
  9. /**
  10. * 静态注入JedisPool连接池
  11. * 本来是正常注入JedisUtil,可以在Controller和Service层使用,但是重写Shiro的CustomCache无法注入JedisUtil
  12. * 现在改为静态注入JedisPool连接池,JedisUtil直接调用静态方法即可
  13. * https://blog.csdn.net/W_Z_W_888/article/details/79979103
  14. */
  15. private static JedisPool jedisPool;
  16. @Autowired
  17. public void setJedisPool(JedisPool jedisPool) {
  18. JedisUtil.jedisPool = jedisPool;
  19. }
  20. /**
  21. * 获取Jedis实例
  22. * @param
  23. * @return redis.clients.jedis.Jedis
  24. * @Author: LEAVES
  25. * @Date: 2020年11月02日 15时22分18秒
  26. */
  27. public static synchronized Jedis getJedis() {
  28. try {
  29. if (jedisPool != null) {
  30. return jedisPool.getResource();
  31. } else {
  32. return null;
  33. }
  34. } catch (Exception e) {
  35. throw new CustomException("获取Jedis资源异常:" + e.getMessage());
  36. }
  37. }
  38. /**
  39. * 释放Jedis资源
  40. * @param
  41. * @return void
  42. * @Author: LEAVES
  43. * @Date: 2020年11月02日 15时22分18秒
  44. */
  45. public static void closePool() {
  46. try {
  47. jedisPool.close();
  48. } catch (Exception e) {
  49. throw new CustomException("释放Jedis资源异常:" + e.getMessage());
  50. }
  51. }
  52. /**
  53. * 获取redis键值-object
  54. * @param key
  55. * @return java.lang.Object
  56. * @Author: LEAVES
  57. * @Date: 2020年11月02日 15时22分18秒
  58. */
  59. public static Object getObject(String key) {
  60. try (Jedis jedis = jedisPool.getResource()) {
  61. byte[] bytes = jedis.get(key.getBytes());
  62. if (StringUtil.isNotNull(bytes)) {
  63. return SerializableUtil.unserializable(bytes);
  64. }
  65. } catch (Exception e) {
  66. throw new CustomException("获取Redis键值getObject方法异常:key=" + key + " cause=" + e.getMessage());
  67. }
  68. return null;
  69. }
  70. /**
  71. * 设置redis键值-object
  72. * @param key
  73. * @param value
  74. * @return java.lang.String
  75. * @Author: LEAVES
  76. * @Date: 2020年11月02日 15时22分18秒
  77. */
  78. public static String setObject(String key, Object value) {
  79. try (Jedis jedis = jedisPool.getResource()) {
  80. return jedis.set(key.getBytes(), SerializableUtil.serializable(value));
  81. } catch (Exception e) {
  82. throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
  83. }
  84. }
  85. /**
  86. * 设置redis键值-object-expiretime
  87. * @param key
  88. * @param value
  89. * @param expiretime
  90. * @return java.lang.String
  91. * @Author: LEAVES
  92. * @Date: 2020年11月02日 15时22分18秒
  93. */
  94. public static String setObject(String key, Object value, int expiretime) {
  95. String result;
  96. try (Jedis jedis = jedisPool.getResource()) {
  97. result = jedis.set(key.getBytes(), SerializableUtil.serializable(value));
  98. if (Constant.OK.equals(result)) {
  99. jedis.expire(key.getBytes(), expiretime);
  100. }
  101. return result;
  102. } catch (Exception e) {
  103. throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
  104. }
  105. }
  106. /**
  107. * 获取redis键值-Json
  108. * @param key
  109. * @return java.lang.Object
  110. * @Author: LEAVES
  111. * @Date: 2020年11月02日 15时22分18秒
  112. */
  113. public static String getJson(String key) {
  114. try (Jedis jedis = jedisPool.getResource()) {
  115. return jedis.get(key);
  116. } catch (Exception e) {
  117. throw new CustomException("获取Redis键值getJson方法异常:key=" + key + " cause=" + e.getMessage());
  118. }
  119. }
  120. /**
  121. * 设置redis键值-Json
  122. * @param key
  123. * @param value
  124. * @return java.lang.String
  125. * @Author: LEAVES
  126. * @Date: 2020年11月02日 15时22分18秒
  127. */
  128. public static String setJson(String key, String value) {
  129. try (Jedis jedis = jedisPool.getResource()) {
  130. return jedis.set(key, value);
  131. } catch (Exception e) {
  132. throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
  133. }
  134. }
  135. /**
  136. * 设置redis键值-Json-expiretime
  137. * @param key
  138. * @param value
  139. * @param expiretime
  140. * @return java.lang.String
  141. * @Author: LEAVES
  142. * @Date: 2020年11月02日 15时22分18秒
  143. */
  144. public static String setJson(String key, String value, int expiretime) {
  145. String result;
  146. try (Jedis jedis = jedisPool.getResource()) {
  147. result = jedis.set(key, value);
  148. if (Constant.OK.equals(result)) {
  149. jedis.expire(key, expiretime);
  150. }
  151. return result;
  152. } catch (Exception e) {
  153. throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
  154. }
  155. }
  156. /**
  157. * 删除key
  158. * @param key
  159. * @return java.lang.Long
  160. * @Author: LEAVES
  161. * @Date: 2020年11月02日 15时22分18秒
  162. */
  163. public static Long delKey(String key) {
  164. try (Jedis jedis = jedisPool.getResource()) {
  165. return jedis.del(key.getBytes());
  166. } catch (Exception e) {
  167. throw new CustomException("删除Redis的键delKey方法异常:key=" + key + " cause=" + e.getMessage());
  168. }
  169. }
  170. /**
  171. * key是否存在
  172. * @param key
  173. * @return java.lang.Boolean
  174. * @Author: LEAVES
  175. * @Date: 2020年11月02日 15时22分18秒
  176. */
  177. public static Boolean exists(String key) {
  178. try (Jedis jedis = jedisPool.getResource()) {
  179. return jedis.exists(key.getBytes());
  180. } catch (Exception e) {
  181. throw new CustomException("查询Redis的键是否存在exists方法异常:key=" + key + " cause=" + e.getMessage());
  182. }
  183. }
  184. /**
  185. * 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用)
  186. * @param key
  187. * @return java.util.Set<java.lang.String>
  188. * @Author: LEAVES
  189. * @Date: 2020年11月02日 15时22分18秒
  190. */
  191. public static Set<String> keysS(String key) {
  192. try (Jedis jedis = jedisPool.getResource()) {
  193. return jedis.keys(key);
  194. } catch (Exception e) {
  195. throw new CustomException("模糊查询Redis的键集合keysS方法异常:key=" + key + " cause=" + e.getMessage());
  196. }
  197. }
  198. /**
  199. * 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用)
  200. * @param key
  201. * @return java.util.Set<java.lang.String>
  202. * @Author: LEAVES
  203. * @Date: 2020年11月02日 15时22分18秒
  204. */
  205. public static Set<byte[]> keysB(String key) {
  206. try (Jedis jedis = jedisPool.getResource()) {
  207. return jedis.keys(key.getBytes());
  208. } catch (Exception e) {
  209. throw new CustomException("模糊查询Redis的键集合keysB方法异常:key=" + key + " cause=" + e.getMessage());
  210. }
  211. }
  212. /**
  213. * 获取过期剩余时间
  214. * @param key
  215. * @return java.lang.String
  216. * @Author: LEAVES
  217. * @Date: 2020年11月02日 15时22分18秒
  218. */
  219. public static Long ttl(String key) {
  220. Long result = -2L;
  221. try (Jedis jedis = jedisPool.getResource()) {
  222. result = jedis.ttl(key);
  223. return result;
  224. } catch (Exception e) {
  225. throw new CustomException("获取Redis键过期剩余时间ttl方法异常:key=" + key + " cause=" + e.getMessage());
  226. }
  227. }
  228. }

3、RedisOperator

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年11月02日 15时22分18秒
  4. * @Version 1.0
  5. * @Description: 使用redisTemplate的操作实现类
  6. */
  7. @Component
  8. public class RedisOperator {
  9. @Resource
  10. private StringRedisTemplate redisTemplate;
  11. // Key(键),简单的key-value操作
  12. /**
  13. * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
  14. *
  15. * @param key
  16. * @return
  17. */
  18. public long ttl(String key) {
  19. return redisTemplate.getExpire(key);
  20. }
  21. /**
  22. * 实现命令:expire 设置过期时间,单位秒
  23. *
  24. * @param key
  25. * @return
  26. */
  27. public void expire(String key, long timeout) {
  28. redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
  29. }
  30. /**
  31. * 实现命令:INCR key,增加key一次
  32. *
  33. * @param key
  34. * @return
  35. */
  36. public long incr(String key, long delta) {
  37. return redisTemplate.opsForValue().increment(key, delta);
  38. }
  39. /**
  40. * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
  41. */
  42. public Set<String> keys(String pattern) {
  43. return redisTemplate.keys(pattern);
  44. }
  45. /**
  46. * 实现命令:DEL key,删除一个key
  47. *
  48. * @param key
  49. */
  50. public void del(String key) {
  51. redisTemplate.delete(key);
  52. }
  53. // String(字符串)
  54. /**
  55. * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
  56. *
  57. * @param key
  58. * @param value
  59. */
  60. public void set(String key, String value) {
  61. redisTemplate.opsForValue().set(key, value);
  62. }
  63. /**
  64. * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
  65. *
  66. * @param key
  67. * @param value
  68. * @param timeout
  69. * (以秒为单位)
  70. */
  71. public void set(String key, String value, long timeout) {
  72. redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
  73. }
  74. /**
  75. * 实现命令:GET key,返回 key所关联的字符串值。
  76. *
  77. * @param key
  78. * @return value
  79. */
  80. public String get(String key) {
  81. return (String)redisTemplate.opsForValue().get(key);
  82. }
  83. // Hash(哈希表)
  84. /**
  85. * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
  86. *
  87. * @param key
  88. * @param field
  89. * @param value
  90. */
  91. public void hset(String key, String field, Object value) {
  92. redisTemplate.opsForHash().put(key, field, value);
  93. }
  94. /**
  95. * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
  96. *
  97. * @param key
  98. * @param field
  99. * @return
  100. */
  101. public String hget(String key, String field) {
  102. return (String) redisTemplate.opsForHash().get(key, field);
  103. }
  104. /**
  105. * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
  106. *
  107. * @param key
  108. * @param fields
  109. */
  110. public void hdel(String key, Object... fields) {
  111. redisTemplate.opsForHash().delete(key, fields);
  112. }
  113. /**
  114. * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
  115. *
  116. * @param key
  117. * @return
  118. */
  119. public Map<Object, Object> hgetall(String key) {
  120. return redisTemplate.opsForHash().entries(key);
  121. }
  122. // List(列表)
  123. /**
  124. * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
  125. *
  126. * @param key
  127. * @param value
  128. * @return 执行 LPUSH命令后,列表的长度。
  129. */
  130. public long lpush(String key, String value) {
  131. return redisTemplate.opsForList().leftPush(key, value);
  132. }
  133. /**
  134. * 实现命令:LPOP key,移除并返回列表 key的头元素。
  135. *
  136. * @param key
  137. * @return 列表key的头元素。
  138. */
  139. public String lpop(String key) {
  140. return (String)redisTemplate.opsForList().leftPop(key);
  141. }
  142. /**
  143. * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
  144. *
  145. * @param key
  146. * @param value
  147. * @return 执行 LPUSH命令后,列表的长度。
  148. */
  149. public long rpush(String key, String value) {
  150. return redisTemplate.opsForList().rightPush(key, value);
  151. }
  152. //zset
  153. /**
  154. * 实现命令:zset存入值
  155. * @param key
  156. * @param value
  157. * @param score
  158. */
  159. public void zSet(String key, String value, double score){
  160. redisTemplate.opsForZSet().add(key,value,score);
  161. }
  162. public Set<String> zGet(String key, long start, long end){
  163. return redisTemplate.opsForZSet().range(key,start,end);
  164. }
  165. }

六、统一异常处理Exception

1、CustomException

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description: 自定义异常(CustomException)
  6. */
  7. public class CustomException extends AuthenticationException {
  8. public CustomException(String msg){
  9. super(msg);
  10. }
  11. public CustomException() {
  12. super();
  13. }
  14. }

2、DefaultExceptionHandler

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description: 自定义异常处理器
  6. */
  7. @Slf4j
  8. @ControllerAdvice //不指定包默认加了@Controller和@RestController都能控制
  9. public class DefaultExceptionHandler {
  10. /**
  11. * @param ex
  12. * @Description: 运行时异常
  13. */
  14. @ExceptionHandler(com.leaves.auth.exception.CustomException.class)
  15. public ResponseData CustomExceptionHandler(com.leaves.auth.exception.CustomException ex) {
  16. log.error(ex.getMessage(), ex);
  17. ResponseData responseData=new ResponseData<>();
  18. responseData.setCode(400);
  19. responseData.setMsg(ex.getMessage());
  20. return responseData;
  21. }
  22. /**
  23. * @param ex
  24. * @Description: 权限认证异常
  25. */
  26. @ExceptionHandler(UnauthorizedException.class)
  27. @ResponseBody
  28. public ResponseData unauthorizedExceptionHandler(Exception ex) {
  29. log.error(ex.getMessage(), ex);
  30. return ResponseDataUtil.fail("对不起,您没有相关权限!");
  31. }
  32. @ExceptionHandler(UnauthenticatedException.class)
  33. @ResponseBody
  34. public ResponseData unauthenticatedException(UnauthenticatedException ex) {
  35. log.error(ex.getMessage(), ex);
  36. return ResponseDataUtil.fail("对不起,您未登录!");
  37. }
  38. /**
  39. *
  40. * @param ex
  41. * @return
  42. */
  43. @ExceptionHandler(AuthorizationException.class)
  44. @ResponseBody
  45. public ResponseData authorizationException(AuthorizationException ex) {
  46. log.error(ex.getMessage(), ex);
  47. return ResponseDataUtil.fail("无效token,请重新登录!");
  48. }
  49. /**
  50. * 空指针异常
  51. * @param ex
  52. * @return
  53. */
  54. @ExceptionHandler(NullPointerException.class)
  55. @ResponseBody
  56. public ResponseData nullPointerException(NullPointerException ex) {
  57. log.error(ex.getMessage(), ex);
  58. return ResponseDataUtil.fail(500,"空指针异常!");
  59. }
  60. /**
  61. * 认证异常
  62. * @param ex
  63. * @return
  64. */
  65. @ExceptionHandler(AuthenticationException.class)
  66. @ResponseBody
  67. public ResponseData authenticationException(AuthenticationException ex) {
  68. log.error(ex.getMessage(), ex);
  69. return ResponseDataUtil.fail("token为空,请重新登录!");
  70. }
  71. /**
  72. * 文件上传异常
  73. * @param ex
  74. * @return
  75. */
  76. @ExceptionHandler(IOException.class)
  77. @ResponseBody
  78. public ResponseData ioException(IOException ex) {
  79. log.error(ex.getMessage(), ex);
  80. return ResponseDataUtil.fail("文件上传异常!");
  81. }
  82. /**
  83. * 异常统一自定义处理
  84. */
  85. @ExceptionHandler({MyException.class})
  86. @ResponseBody
  87. public ResponseData MyException(MyException e) {
  88. return ResponseDataUtil.fail(500,e.getMessage());
  89. }
  90. /**
  91. * 异常统一处理(最后的异常处理)
  92. */
  93. @ExceptionHandler({Exception.class})
  94. @ResponseStatus(HttpStatus.OK)
  95. @ResponseBody
  96. public ResponseData allException(Exception e) {
  97. log.info(e.getMessage());
  98. return ResponseDataUtil.fail(500,"系统异常!");
  99. }
  100. }

3、MyException

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description: 自定义异常
  6. */
  7. @Data
  8. public class MyException extends RuntimeException {
  9. /**
  10. * 自定义返回状态码
  11. */
  12. private Integer code;
  13. /**
  14. * 返回自定义的状态码和异常描述
  15. * @param code
  16. * @param message
  17. */
  18. public MyException(Integer code, String message) {
  19. super(message);
  20. this.code = code;
  21. }
  22. /**
  23. * 只返回异常信息(默认返回500)
  24. * @param message
  25. */
  26. public MyException(String message) {
  27. super(message);
  28. }
  29. }

七、统一返回格式response

1、ResponseData

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description: 统一返回格式
  6. */
  7. @Data
  8. public class ResponseData<T> {
  9. /**
  10. * 统一返回码
  11. */
  12. public Integer code;
  13. /**
  14. * 统一消息
  15. */
  16. public String msg;
  17. /**
  18. * 结果对象
  19. */
  20. @JsonInclude(JsonInclude.Include.NON_NULL)
  21. public T data;
  22. }

2、ResponseDataUtil

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时06分23秒
  4. * @Version 1.0
  5. * @Description: 统一返回格式封装
  6. */
  7. @Slf4j
  8. public class ResponseDataUtil {
  9. /**
  10. * 返回成功的描述 状态码、说明
  11. * @param msg
  12. * @return
  13. */
  14. public static ResponseData success(String msg){
  15. ResponseData responseData = new ResponseData();
  16. responseData.setCode(200);
  17. responseData.setMsg(msg);
  18. return responseData;
  19. }
  20. /**
  21. * 返回成功的描述 状态码、说明
  22. * @param code
  23. * @param msg
  24. * @return
  25. */
  26. public static <T> ResponseData success(Integer code, String msg){
  27. ResponseData responseData = new ResponseData();
  28. responseData.setCode(code);
  29. responseData.setMsg(msg);
  30. return responseData;
  31. }
  32. /**
  33. * 返回成功的描述 状态码、说明、数据
  34. * @param msg
  35. * @param data
  36. * @param <T>
  37. * @return
  38. */
  39. public static <T> ResponseData success(String msg, T data){
  40. ResponseData responseData = new ResponseData();
  41. responseData.setCode(200);
  42. responseData.setMsg(msg);
  43. responseData.setData(data);
  44. return responseData;
  45. }
  46. /**
  47. * 返回成功的描述 状态码、说明、令牌
  48. * @param code
  49. * @param msg
  50. * @param data
  51. * @param <T>
  52. * @return
  53. */
  54. public static <T> ResponseData success(Integer code, String msg, T data){
  55. ResponseData responseData = new ResponseData();
  56. responseData.setCode(code);
  57. responseData.setMsg(msg);
  58. responseData.setData(data);
  59. return responseData;
  60. }
  61. /**
  62. * 返回失败的描述 状态码
  63. * @param msg
  64. * @return
  65. */
  66. public static ResponseData fail(String msg){
  67. ResponseData responseData=new ResponseData();
  68. responseData.setCode(405);
  69. responseData.setMsg(msg);
  70. return responseData;
  71. }
  72. /**
  73. * 返回失败的描述 状态码、说明
  74. * @param code
  75. * @param msg
  76. * @return
  77. */
  78. public static ResponseData fail(Integer code, String msg){
  79. ResponseData responseData=new ResponseData();
  80. responseData.setCode(code);
  81. responseData.setMsg(msg);
  82. return responseData;
  83. }
  84. /**
  85. * 返回失败的描述 状态码、说明
  86. * @param code
  87. * @param msg
  88. * @param data
  89. * @param <T>
  90. * @return
  91. */
  92. public static <T> ResponseData fail(Integer code, String msg, T data){
  93. ResponseData responseData=new ResponseData();
  94. responseData.setCode(code);
  95. responseData.setMsg(msg);
  96. responseData.setData(data);
  97. return responseData;
  98. }
  99. }

八、工具类

1、IsNotEmptyUtil

  1. * @Author: LEAVES
  2. * @Date: 20201230142915
  3. * @Version 1.0
  4. * @Description: 非空判断
  5. */
  6. public class IsNotEmptyUtil {
  7. public static boolean isEmpty(Object object) {
  8. if (object == null) {
  9. return (true);
  10. }
  11. if ("".equals(object)) {
  12. return (true);
  13. }
  14. if ("null".equals(object)) {
  15. return (true);
  16. }
  17. return (false);
  18. }
  19. public static boolean isNotEmpty(Object object) {
  20. if (object != null && !object.equals("") && !object.equals("null")) {
  21. return (true);
  22. }
  23. return (false);
  24. }
  25. }

2、MD5

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 17时55分06秒
  4. * @Version 1.0
  5. * @Description: MD5加密
  6. */
  7. public class MD5Util {
  8. /***
  9. * MD5加码 生成32位md5码
  10. */
  11. public static String string2MD5(String inStr) {
  12. MessageDigest md5 = null;
  13. try {
  14. md5 = MessageDigest.getInstance("MD5");
  15. } catch (Exception e) {
  16. System.out.println(e.toString());
  17. e.printStackTrace();
  18. return "";
  19. }
  20. char[] charArray = inStr.toCharArray();
  21. byte[] byteArray = new byte[charArray.length];
  22. for (int i = 0; i < charArray.length; i++) {
  23. byteArray[i] = (byte) charArray[i];
  24. }
  25. byte[] md5Bytes = md5.digest(byteArray);
  26. StringBuffer hexValue = new StringBuffer();
  27. for (int i = 0; i < md5Bytes.length; i++) {
  28. int val = ((int) md5Bytes[i]) & 0xff;
  29. if (val < 16){
  30. hexValue.append("0");
  31. }
  32. hexValue.append(Integer.toHexString(val));
  33. }
  34. return hexValue.toString();
  35. }
  36. /**
  37. * 加密解密算法 执行一次加密,两次解密
  38. */
  39. public static String convertMD5(String inStr) {
  40. char[] a = inStr.toCharArray();
  41. for (int i = 0; i < a.length; i++) {
  42. a[i] = (char) (a[i] ^ 't');
  43. }
  44. String s = new String(a);
  45. return s;
  46. }
  47. }

3、含有NULL值属性对象转JSON时使其变成空字符串

  1. /**
  2. * 处理返回值中的null值
  3. * @Author: LEAVES
  4. * @Date: 2020年12月30日 11时06分23秒
  5. * @Version 1.0
  6. */
  7. //@EnableWebMvc
  8. @Configuration
  9. public class JsonConfig {
  10. @Bean
  11. public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
  12. final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
  13. ObjectMapper mapper = converter.getObjectMapper();
  14. // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
  15. mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
  16. return converter;
  17. }
  18. }
  1. /**
  2. * 该类控制将null值处理成空集合还是空字符串
  3. * @Author: LEAVES
  4. * @Date: 2020年12月30日 11时06分23秒
  5. * @Version 1.0
  6. */
  7. public class MyBeanSerializerModifier extends BeanSerializerModifier {
  8. // 数组类型
  9. private JsonSerializer _nullArrayJsonSerializer = new MyNullArrayJsonSerializer();
  10. // 字符串等类型
  11. private JsonSerializer _nullJsonSerializer = new MyNullJsonSerializer();
  12. // 对象类型
  13. private JsonSerializer _nullObjectSerializer = new MyNullObjectJsonSerializer();
  14. @Override
  15. public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
  16. List beanProperties) {
  17. //循环所有的beanPropertyWriter
  18. for (int i = 0; i < beanProperties.size(); i++) {
  19. BeanPropertyWriter writer = (BeanPropertyWriter) beanProperties.get(i);
  20. //判断字段的类型,如果是array,list,set则注册nullSerializer
  21. if (isArrayType(writer)) {
  22. //给writer注册一个自己的nullSerializer
  23. writer.assignNullSerializer(this._nullArrayJsonSerializer);
  24. } else if(isStringOrNumberType(writer)){
  25. writer.assignNullSerializer(this._nullJsonSerializer);
  26. }else{
  27. writer.assignNullSerializer(this._nullObjectSerializer);
  28. }
  29. }
  30. return beanProperties;
  31. }
  32. //判断是什么类型
  33. protected boolean isArrayType(BeanPropertyWriter writer) {
  34. Class clazz = writer.getPropertyType();
  35. return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class);
  36. }
  37. //判断是什么类型
  38. protected boolean isStringOrNumberType(BeanPropertyWriter writer) {
  39. Class clazz = writer.getPropertyType();
  40. return clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(int.class) || clazz.equals(Double.class) || clazz.equals(Short.class)
  41. || clazz.equals(Long.class) || clazz.equals(short.class) || clazz.equals(double.class) || clazz.equals(long.class) || clazz.equals(Date.class)
  42. || clazz.equals(BigDecimal.class);
  43. }
  44. }
  1. /**
  2. * 处理数组类型的null值
  3. * @Author: LEAVES
  4. * @Date: 2020年12月30日 11时06分23秒
  5. * @Version 1.0
  6. */
  7. public class MyNullArrayJsonSerializer extends JsonSerializer {
  8. @Override
  9. public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
  10. if (value == null) {
  11. jgen.writeStartArray();
  12. jgen.writeEndArray();
  13. }
  14. }
  15. }
  1. /**
  2. * 处理字符串等类型的null值
  3. * @Author: LEAVES
  4. * @Date: 2020年12月30日 11时06分23秒
  5. * @Version 1.0
  6. */
  7. public class MyNullJsonSerializer extends JsonSerializer {
  8. @Override
  9. public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
  10. throws IOException, JsonProcessingException {
  11. jsonGenerator.writeString("");
  12. }
  13. }
  1. /**
  2. * 处理对象类型的null值
  3. * @Author: LEAVES
  4. * @Date: 2020年12月30日 11时06分23秒
  5. * @Version 1.0
  6. */
  7. public class MyNullObjectJsonSerializer extends JsonSerializer {
  8. @Override
  9. public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
  10. throws IOException, JsonProcessingException {
  11. jsonGenerator.writeStartObject();
  12. jsonGenerator.writeEndObject();
  13. }
  14. }

九 、JWT

1、JwtToken

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 14时25分08秒
  4. * @Version 1.0
  5. * @Description: Authentication-Token: shiro中负责把username,password生成用于验证的token的封装类,需要自定义一个对象用来包装token。
  6. */
  7. public class JwtToken implements AuthenticationToken {
  8. private String token;
  9. public JwtToken(String token){
  10. this.token = token;
  11. }
  12. @Override
  13. public Object getPrincipal() {
  14. return token;
  15. }
  16. @Override
  17. public Object getCredentials() {
  18. return token;
  19. }
  20. }

2、JwtUtil

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 14时25分08秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. @Slf4j
  8. public class JwtUtil {
  9. //token过期时间 5小时
  10. private static final long EXPIRE_TIME = 1000 * 60 * 60 * 5;
  11. //redis中token过期时间 12小时
  12. public static final Integer REFRESH_EXPIRE_TIME = 60 * 60 * 12;
  13. //token密钥(自定义)
  14. private static final String TOKEN_SECRET = "^/zxc*123!@#$%^&*/";
  15. /**
  16. * 校验token是否正确
  17. * @param token token
  18. * @param username 用户名
  19. * @return 是否正确
  20. */
  21. public static boolean verify(String token, String username){
  22. log.info("JwtUtil==verify--->");
  23. try {
  24. log.info("JwtUtil==verify--->校验token是否正确");
  25. //根据密码生成JWT效验器
  26. Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); //秘钥是密码则省略
  27. JWTVerifier verifier = JWT.require(algorithm)
  28. .withClaim("username", username)
  29. // .withClaim("secret",secret) //秘钥是密码直接传入
  30. .build();
  31. //效验TOKEN
  32. DecodedJWT jwt = verifier.verify(token);
  33. log.info("JwtUtil==verify--->jwt = "+jwt.toString());
  34. log.info("JwtUtil==verify--->JwtUtil验证token成功!");
  35. return true;
  36. }catch (Exception e){
  37. log.error("JwtUtil==verify--->JwtUtil验证token失败!");
  38. return false;
  39. }
  40. }
  41. /**
  42. * 获取token中的信息(包含用户名)
  43. * @param token
  44. * @return
  45. */
  46. public static String getUsername(String token) {
  47. log.info("JwtUtil==getUsername--->");
  48. if (IsNotEmptyUtil.isEmpty(token)){
  49. log.error("JwtUtil==getUsername--->token无效或token中未包含用户信息! ");
  50. throw new AuthenticationException("认证失败,token无效或token中未包含用户信息!");
  51. }
  52. try {
  53. DecodedJWT jwt = JWT.decode(token);
  54. System.out.println("token = " + jwt.getToken());
  55. log.info("JwtUtil==getUsername--->username = "+jwt.getClaim("username").asString());
  56. return jwt.getClaim("username").asString();
  57. } catch (JWTDecodeException e) {
  58. log.error("JwtUtil==getUsername--->JWTDecodeException: " + e.getMessage());
  59. return null;
  60. }
  61. }
  62. /**
  63. * 生成token签名
  64. * EXPIRE_TIME 分钟后过期
  65. * @param username 用户名
  66. * @return 加密的token
  67. */
  68. public static String sign(String username) {
  69. log.info("JwtUtil==sign--->");
  70. Map<String, Object> header = new HashMap<>();
  71. header.put("type","Jwt");
  72. header.put("alg","HS256");
  73. long currentTimeMillis = System.currentTimeMillis();
  74. //设置token过期时间
  75. Date date = new Date(currentTimeMillis + EXPIRE_TIME);
  76. Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);//秘钥是密码则省略
  77. //生成签名
  78. String sign = JWT.create()
  79. .withHeader(header)
  80. .withExpiresAt(date)
  81. .withClaim("username", username)
  82. // .withClaim("secret",secret) //秘钥是密码直接传入
  83. .withClaim("currentTime", currentTimeMillis + EXPIRE_TIME)
  84. .sign(algorithm);
  85. log.info("JwtUtil==sign--->sign = " + sign);
  86. return sign;
  87. }
  88. /**
  89. * 获取token中用户信息
  90. * @param token
  91. * @return
  92. */
  93. public static String getAccount(String token){
  94. try{
  95. DecodedJWT decodedJWT=JWT.decode(token);
  96. return decodedJWT.getClaim("username").asString();
  97. }catch (JWTCreationException e){
  98. return null;
  99. }
  100. }
  101. /**
  102. * 获取token中用户密码
  103. * @param token
  104. * @return
  105. */
  106. public static String getSecret(String token){
  107. try{
  108. DecodedJWT decodedJWT=JWT.decode(token);
  109. return decodedJWT.getClaim("secret").asString();
  110. }catch (JWTCreationException e){
  111. return null;
  112. }
  113. }
  114. /**
  115. * 获取token的时间戳
  116. * @param token
  117. * @return
  118. */
  119. public static Long getCurrentTime(String token){
  120. try{
  121. DecodedJWT decodedJWT=JWT.decode(token);
  122. return decodedJWT.getClaim("currentTime").asLong();
  123. }catch (JWTCreationException e){
  124. return null;
  125. }
  126. }
  127. }

3、JwtFilter

  1. /**
  2. * 重写鉴权
  3. * @Author: LEAVES
  4. * @Date: 2020年12月30日 14时25分08秒
  5. * @Version 1.0
  6. * @Description: 执行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin 。
  7. */
  8. @Slf4j
  9. public class JwtFilter extends BasicHttpAuthenticationFilter {
  10. //默认需要放行的接口 shiroc处判断过,此处可写可不写
  11. private String[] defalutExcludeUrl = new String[] {
  12. "/login","/401", "/402", "/noaccess"
  13. //,"/formLogin",".jpg",".png",".gif",".css",".js",".jpeg"
  14. };
  15. /**
  16. * 检测用户是否想要登录
  17. * 检测header里面是否包含token字段即可
  18. * @param request
  19. * @param response
  20. * @return
  21. */
  22. @Override
  23. protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
  24. log.info("JwtFilter==isLoginAttempt--->");
  25. HttpServletRequest req = (HttpServletRequest) request;
  26. String authorization = req.getHeader("Authorization");
  27. if (authorization != null){
  28. //去掉token前缀
  29. authorization = authorization.substring(7);
  30. log.info("JwtFilter==isLoginAttempt--->authorization = " + authorization);
  31. log.info("JwtFilter==isLoginAttempt--->用户已经登录过了");
  32. return authorization != null;
  33. }else{
  34. log.info("JwtFilter==isLoginAttempt--->用户未登录");
  35. return authorization == null;
  36. }
  37. }
  38. /**
  39. * JwtToken实现了AuthenticationToken接口封装了token参数
  40. * 通过getSubject方法获取 subject对象
  41. * login()发送身份验证
  42. * 为什么需要在Filter中调用login,不能在controller中调用login?
  43. * 由于Shiro默认的验证方式是基于session的,在基于token验证的方式中,不能依赖session做为登录的判断依据.
  44. * 执行登录
  45. * @param request
  46. * @param response
  47. * @return
  48. * @throws Exception
  49. */
  50. @Override
  51. protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
  52. log.info("JwtFilter==executeLogin--->");
  53. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  54. String authorization = httpServletRequest.getHeader("Authorization");
  55. //去掉token前缀
  56. authorization = authorization.substring(7);
  57. JwtToken token = new JwtToken(authorization);
  58. // 提交给realm进行登入,如果错误他会抛出异常并被捕获
  59. try {
  60. //触发 Shiro Realm 自身的登录控制
  61. getSubject(request, response).login(token);
  62. // 如果没有抛出异常则代表登入成功,返回true
  63. log.info("JwtFilter==executeLogin--->验证登入成功");
  64. //刷新token
  65. this.refreshToken(authorization, response);
  66. return true;
  67. } catch (Exception e) {
  68. log.error("JwtFilter==executeLogin--->没有访问权限,原因是:" + e.getMessage());
  69. //此处跳转到401接口返回错误信息!
  70. this.responseInvalid(response);
  71. //throw new AuthenticationException("无效token,请先登录!!!!" + e.getMessage());
  72. return false;
  73. }
  74. }
  75. /**
  76. * 这里详细说明下为什么最终返回的都是true,即允许访问
  77. * 例如提供一个地址 GET /article
  78. * 登入用户和游客看到的内容是不同的
  79. * 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西
  80. * 所以在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入
  81. * 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可
  82. * 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大
  83. * @param request
  84. * @param response
  85. * @param mappedValue
  86. * @return
  87. */
  88. @Override
  89. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  90. log.info("JwtFilter==isAccessAllowed--->");
  91. //判断请求的请求头是否带上 "token"
  92. if (this.isLoginAttempt(request, response)) {
  93. try {
  94. //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
  95. if (this.executeLogin(request, response)){
  96. String requestURL = ((HttpServletRequest) request).getRequestURL().toString();
  97. log.info("JwtFilter==isAccessAllowed--->requestURL="+requestURL);
  98. for(String excludeUrl : defalutExcludeUrl){
  99. if (requestURL.endsWith(excludeUrl)){
  100. return true;
  101. }
  102. }
  103. }
  104. } catch (Exception e) {
  105. log.error("JwtFilter==isAccessAllowed--->Token已失效或为空,JwtFilter过滤验证失败!");
  106. //此处跳转到402接口返回错误信息!
  107. this.responseInvalid(response);
  108. // throw new AuthenticationException("token为空,请重新登录!");
  109. }
  110. }
  111. //如果请求头不存在 token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
  112. return true;
  113. }
  114. /**
  115. * 对跨域提供支持
  116. * @param request
  117. * @param response
  118. * @return
  119. * @throws Exception
  120. */
  121. @Override
  122. protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
  123. log.info("JwtFilter==preHandle--->");
  124. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  125. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  126. httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
  127. httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
  128. httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
  129. // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
  130. if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
  131. httpServletResponse.setStatus(HttpStatus.OK.value());
  132. return false;
  133. }
  134. return super.preHandle(request, response);
  135. }
  136. /**
  137. * 刷新token
  138. * @param authorization
  139. * @param response
  140. * @return
  141. * refreshTokens方法中,当redisToken不为空时返回true,并且如果redisToken验证成功,则将已有的token重新存入一遍,保持redis中的token不过期
  142. * 如果redisToken验证不通过,重新生成新的token,存入redis并返回给前端;当redisToken为空时返回false,需要重新登录.
  143. */
  144. @SneakyThrows
  145. protected void refreshToken(String authorization, ServletResponse response){
  146. log.info("-------------刷新token-------------");
  147. //获取token的用户名
  148. String account = JwtUtil.getAccount(authorization);
  149. //获取redis中的token
  150. String redisToken = JedisUtil.getJson(account);
  151. //判断redis中的token是否为空
  152. if (IsNotEmptyUtil.isNotEmpty(redisToken)){
  153. //将现有的token重新存入redis中
  154. JedisUtil.setJson(account, authorization, REFRESH_EXPIRE_TIME);
  155. //给前端返回新生成的token
  156. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  157. httpServletResponse.setHeader("token", authorization);
  158. httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
  159. log.info("redis中的token还未过期!");
  160. } else {
  161. //生成新的token
  162. String newToken = JwtUtil.sign(account);
  163. //将新生成的token重新存入redis中
  164. JedisUtil.setJson(account, newToken, REFRESH_EXPIRE_TIME);
  165. //给前端返回原来请求的token
  166. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  167. httpServletResponse.setHeader("token", newToken);
  168. httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
  169. log.info("redis中的token已过期!");
  170. }
  171. }
  172. /**
  173. * 将非法请求跳转到 /401 暂无token!
  174. * @param response
  175. */
  176. private void responseInvalid(ServletResponse response) {
  177. log.info("JwtFilter==response401--->");
  178. try {
  179. HttpServletResponse httpServletResponse = (HttpServletResponse) response;
  180. httpServletResponse.sendRedirect("/invalid");
  181. } catch (IOException e) {
  182. log.error(e.getMessage());
  183. }
  184. }
  185. // /**
  186. // * 无需转发,直接返回Response信息
  187. // */
  188. // private void response401(ServletResponse response, String msg) {
  189. // HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
  190. // httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
  191. // httpServletResponse.setCharacterEncoding("UTF-8");
  192. // httpServletResponse.setContentType("application/json; charset=utf-8");
  193. // try (PrintWriter out = httpServletResponse.getWriter()) {
  194. // String data = JsonConvertUtil.objectToJson(new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):" + msg, null));
  195. // out.append(data);
  196. // } catch (IOException e) {
  197. // log.error("直接返回Response信息出现IOException异常:{}", e.getMessage());
  198. // throw new CustomException("直接返回Response信息出现IOException异常:" + e.getMessage());
  199. // }
  200. // }
  201. }

十、Shiro配置

1、MyRealm

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 14时25分08秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. @Slf4j
  8. @Component
  9. public class MyRealm extends AuthorizingRealm {
  10. @Resource
  11. private IRoleService roleService;
  12. @Resource
  13. private IUserService userService;
  14. /**
  15. * 必须重写此方法,否则Shiro会报错
  16. * @param token
  17. * @return
  18. */
  19. @Version
  20. public boolean supports(AuthenticationToken token) {
  21. return token instanceof JwtToken;
  22. }
  23. /**
  24. * 授权
  25. * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
  26. * @param principals
  27. * @return
  28. */
  29. @Override
  30. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) throws AuthorizationException {
  31. //授权
  32. log.info("-------------用户授权-------------");
  33. log.info("MyRealm==doGetAuthorizationInfo--->");
  34. //如果身份认证的时候没有传入User对象,这里只能取到userName
  35. TUser tUser = (TUser) principals.getPrimaryPrincipal();
  36. String username = tUser.getUsername();
  37. // //通过调用JwtUtil.getUsername()方法得到token中的username
  38. // String username = JwtUtil.getUsername(principals.toString());
  39. if (IsNotEmptyUtil.isEmpty(username)){
  40. throw new AuthorizationException("无效token,请重新登录!");
  41. }
  42. //调用业务方法获取用户的角色
  43. Set<String> permissions = roleService.getPermissionByUserName(username);
  44. // String role = userService.getRole(username);
  45. //调用业务方法获取用户权限
  46. // List<Permissions> list = roleService.getPermissionsByUsername(username);
  47. //每个用户可以设置新的权限
  48. // String permission = userService.getPermission(username);
  49. //将List换成set去掉重复权限
  50. // Set<String> stringPermissions = new HashSet<>();
  51. // Set<String> roleSet = new HashSet<>();
  52. // if (list !=null){
  53. // for (Permissions permissions : list){
  54. // log.info(username + "拥有的权限有:" + permissions);
  55. // stringPermissions.add(permissions.getPername());
  56. // }
  57. // }
  58. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  59. //设置该用户拥有的角色和权限
  60. // authorizationInfo.setRoles(roleSet);
  61. authorizationInfo.setStringPermissions(permissions);
  62. // authorizationInfo.setStringPermissions(stringPermissions);
  63. return authorizationInfo;
  64. }
  65. /**
  66. * 认证
  67. * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
  68. * @param auth
  69. * @return
  70. * @throws AuthenticationException
  71. */
  72. @Override
  73. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
  74. log.info("-------------用户认证-------------");
  75. //获取用户token信息
  76. String token = (String) auth.getCredentials();
  77. // 帐号为空
  78. if (IsNotEmptyUtil.isEmpty(token)) {
  79. throw new AuthenticationException("暂无token!");
  80. }
  81. log.info("MyRealm==doGetAuthenticationInfo--->token = " + token);
  82. //判断token中是否包含用户信息
  83. String username = null;
  84. try {
  85. //这里工具类没有处理空指针等异常这里处理一下(这里处理科学一些)
  86. //解密获得username,用于和数据库进行对比
  87. username = JwtUtil.getUsername(token);
  88. log.info("MyRealm==doGetAuthenticationInfo--->从token中解析出的username = " + username);
  89. } catch (Exception e) {
  90. log.info("MyRealm==doGetAuthenticationInfo--->AuthenticationException:token拼写错误或者值为空!");
  91. throw new AuthenticationException("token拼写错误或者值为空");
  92. }
  93. if (username == null) {
  94. log.error("MyRealm==doGetAuthenticationInfo--->token无效(空''或者null都不行!)");
  95. throw new AuthenticationException("认证失败,token无效或token中未包含用户信息!");
  96. }
  97. //根据用户信息查询数据库获取后端的用户身份,转交给securityManager判定
  98. //调用业务方法从数据库中获取用户信息
  99. TUser tUser = userService.getUserByUserName(username);
  100. //判断从数据库中获取用户信息是否为空
  101. if (tUser == null) {
  102. log.error("MyRealm==doGetAuthenticationInfo--->用户不存在!)");
  103. throw new AuthenticationException("用户" + username + "不存在!");
  104. }
  105. //认证
  106. return new SimpleAuthenticationInfo(tUser, token, "my_realm");
  107. }
  108. }

2、ShiroConfig

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 14时23分12秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. @Slf4j
  8. @Configuration
  9. public class ShiroConfig {
  10. @Bean
  11. public MyRealm myRealm() {
  12. return new MyRealm();
  13. }
  14. /**
  15. * 注入安全过滤器
  16. * @param securityManager
  17. * @return
  18. */
  19. @Bean("shiroFilter")
  20. public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
  21. log.info("ShiroConfig==shiroFilter--->");
  22. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  23. shiroFilterFactoryBean.setSecurityManager(securityManager);
  24. //拦截器
  25. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
  26. // 配置不会被拦截的链接 顺序判断
  27. filterChainDefinitionMap.put("/", "anon");
  28. filterChainDefinitionMap.put("/login", "anon");
  29. // 添加自己的过滤器并且取名为jwt
  30. Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
  31. filterMap.put("jwt", new JwtFilter());
  32. log.info("ShiroConfig==shiroFilter--->new JwtFilter() : " + new JwtFilter());
  33. shiroFilterFactoryBean.setFilters(filterMap);
  34. //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
  35. filterChainDefinitionMap.put("/api/**", "jwt");
  36. //未授权界面;
  37. shiroFilterFactoryBean.setUnauthorizedUrl("/noaccess");
  38. shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
  39. return shiroFilterFactoryBean;
  40. }
  41. /**
  42. * 注入安全管理器
  43. * @param myRealm
  44. * @return
  45. */
  46. @Bean("securityManager")
  47. public SecurityManager securityManager(MyRealm myRealm) {
  48. log.info("ShiroConfig==securityManager--->");
  49. DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  50. securityManager.setRealm(myRealm);
  51. /*
  52. * 关闭shiro自带的session,详情见文档
  53. * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
  54. */
  55. DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
  56. DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
  57. defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
  58. subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
  59. securityManager.setSubjectDAO(subjectDAO);
  60. return securityManager;
  61. }
  62. /**
  63. * 自动创建代理,没有这个鉴权可能会出错
  64. * @return
  65. */
  66. @Bean
  67. public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
  68. log.info("ShiroConfig==getDefaultAdvisorAutoProxyCreator--->");
  69. DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
  70. autoProxyCreator.setProxyTargetClass(true);
  71. return autoProxyCreator;
  72. }
  73. /**
  74. * 开启shiro aop注解支持.
  75. * 使用代理方式;所以需要开启代码支持;
  76. * @param
  77. * @return
  78. */
  79. @Bean
  80. public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
  81. log.info("ShiroConfig==authorizationAttributeSourceAdvisor--->");
  82. AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
  83. authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
  84. return authorizationAttributeSourceAdvisor;
  85. }
  86. }

十一、业务实现

(整个过程中省略了mapper与xml的实现,以及实体类的。mapper、xml、entity、service、controller都可根据实际情况进行修改。)

1、UserService以及实现类

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 13时44分34秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. public interface IUserService {
  8. TUser getUserByUserName(String username);
  9. ResponseData login(String username, String password, HttpServletResponse response);
  10. ResponseData logout(HttpServletRequest request);
  11. ResponseData queryUserList();
  12. ResponseData queryUserByUserName(String username);
  13. }
  14. /**
  15. * @Author: LEAVES
  16. * @Date: 2020年12月30日 13时44分45秒
  17. * @Version 1.0
  18. * @Description:
  19. */
  20. @Service
  21. @Slf4j
  22. public class UserService implements IUserService {
  23. @Resource
  24. private TUserMapper userMapper;
  25. @Resource
  26. private TRoleMapper roleMapper;
  27. /**
  28. * 根据用户名查询用户信息(程序内部调用)
  29. * @param username
  30. * @return
  31. */
  32. @Override
  33. public TUser getUserByUserName(String username) {
  34. TUser tUser = userMapper.selectByUserName(username);
  35. log.info("权限认证===>根据用户名查询用户信息:" + tUser.toString());
  36. return tUser;
  37. }
  38. /**
  39. * 登录实现
  40. * @param username
  41. * @param password
  42. * @param response
  43. * @return
  44. */
  45. @Override
  46. public ResponseData login(String username, String password, HttpServletResponse response) {
  47. log.info("用户登录");
  48. //为了简单实现此功能,并也未对前端传来的用户名和密码做正则校验
  49. //selectUserToRoleByUserName该方法是一对一查询
  50. TUser tUser = userMapper.selectUserToRoleByUserName(username);
  51. // Set<String> roleByUserName = tRoleService.getRoleByUserName(username);
  52. //由于前端+controller两处对username和password进行了判断,所以此处不再做判断
  53. if (tUser != null) {
  54. //解密数据库中用户密码
  55. String dbpwd = MD5Util.convertMD5(tUser.getPassword());
  56. //判断输入的用户名和密码是否与数据库中的用户名、密码一致
  57. if(tUser.getUsername().equals(username) && dbpwd.equals(password)){
  58. //生成token
  59. String token = JwtUtil.sign(tUser.getUsername());
  60. log.info("登录时生成的token = " + token);
  61. //获取当前时间的时间戳
  62. long currentTimeMillis = System.currentTimeMillis();
  63. //向redis中存入token
  64. JedisUtil.setJson(username, token, JwtUtil.REFRESH_EXPIRE_TIME);
  65. //向前端返回token
  66. HttpServletResponse httpServletResponse = response;
  67. httpServletResponse.setHeader("token", token);
  68. httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
  69. log.info("登录成功:" + tUser.toString());
  70. tUser.setPassword(dbpwd);
  71. Map<String, Object> map = new HashMap<>();
  72. map.put("user", tUser);
  73. map.put("token", token);
  74. return ResponseDataUtil.success("登录成功", map);
  75. }
  76. return ResponseDataUtil.fail("登录失败,用户名或密码错误!");
  77. }
  78. return ResponseDataUtil.fail("登录失败,用户不存在!");
  79. }
  80. /**
  81. * 退出登录
  82. * @param request
  83. * @return
  84. */
  85. @Override
  86. public ResponseData logout(HttpServletRequest request) {
  87. log.info("退出登录");
  88. //获取token
  89. String token = request.getHeader("Authorization");
  90. //去掉token前缀
  91. token = token.substring(7);
  92. //token非空判断
  93. if (IsNotEmptyUtil.isEmpty(token)) {
  94. return ResponseDataUtil.fail("无效的token!");
  95. }
  96. //获取token中的用户名
  97. String username = JwtUtil.getAccount(token);
  98. //判断该token的用户是否存在
  99. TUser tUser = userMapper.selectByUserName(username);
  100. if (tUser != null) {
  101. log.info(" 用户名: " + username + " 退出登录成功! ");
  102. if (JedisUtil.getJson(username) == null){
  103. return ResponseDataUtil.fail("已经退出登录过了!");
  104. }
  105. //清空redis中用户Token缓存
  106. Long aLong = JedisUtil.delKey(username);
  107. return ResponseDataUtil.success("退出登录成功!");
  108. }
  109. return ResponseDataUtil.fail("令牌失效,请重新登录!");
  110. }
  111. /**
  112. * 查询全部用户信息
  113. * @return
  114. */
  115. @Override
  116. public ResponseData queryUserList() {
  117. List<TUser> tUsers = userMapper.selectAll();
  118. if (!tUsers.isEmpty()){
  119. for (TUser tUser : tUsers){
  120. //密码解密
  121. tUser.setPassword(MD5Util.convertMD5(tUser.getPassword()));
  122. }
  123. log.info("获取全部用户信息:" + tUsers.toString());
  124. return ResponseDataUtil.success("查询成功!", tUsers);
  125. }
  126. return ResponseDataUtil.fail("暂无用户信息!");
  127. }
  128. /**
  129. * 根据用户查询用户信息(接口)
  130. * @param username
  131. * @return
  132. */
  133. @Override
  134. public ResponseData queryUserByUserName(String username) {
  135. TUser tUser = userMapper.selectByUserName(username);
  136. if (tUser != null){
  137. return ResponseDataUtil.success("查询成功!", tUser);
  138. }
  139. return ResponseDataUtil.fail("暂无该用户信息!");
  140. }
  141. }

2、RoleServicey以及实现类

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 11时20分48秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. public interface IRoleService<T> {
  8. Set<String> getPermissionByUserName(String username);
  9. ResponseData queryRoleList();
  10. }
  11. /**
  12. * @Author: LEAVES
  13. * @Date: 2020年12月30日 11时21分02秒
  14. * @Version 1.0
  15. * @Description:
  16. */
  17. @Service
  18. @Slf4j
  19. public class RoleService implements IRoleService {
  20. @Resource
  21. private TRoleMapper roleMapper;
  22. /**
  23. * 根据用户查询用户权限(权限认证时调用)
  24. * @param username
  25. * @return
  26. */
  27. @Override
  28. public Set<String> getPermissionByUserName(String username) {
  29. List<String> permissions = roleMapper.getPermissionByUserName(username);
  30. log.info("权限鉴别===>通过用户名获取用户权限信息:" + permissions.toString());
  31. return new HashSet<String>(permissions);
  32. }
  33. /**
  34. * 查询全部角色信息
  35. * @return
  36. */
  37. @Override
  38. public ResponseData queryRoleList() {
  39. List<TRole> roles = roleMapper.selectAll();
  40. if (!roles.isEmpty()){
  41. return ResponseDataUtil.success("查询成功!", roles);
  42. }
  43. return ResponseDataUtil.fail("暂无角色数据!");
  44. }
  45. }

十二、控制层的实现

1、RedirectController(重定向访问)

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 17时36分46秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. @Slf4j
  8. @RestController
  9. @CrossOrigin
  10. public class RedirectController {
  11. /**
  12. * 暂无token
  13. * @return
  14. */
  15. @GetMapping("/invalid")
  16. public Object redirect401(){
  17. return ResponseDataUtil.fail("token已失效或为空,请先登录!");
  18. }
  19. /**
  20. * 权限不足
  21. * @return
  22. */
  23. @GetMapping("/noaccess")
  24. public Object redirect403(){
  25. return ResponseDataUtil.fail("对不起,无权限访问!");
  26. }
  27. }

2、LoginController

  1. @Slf4j
  2. @RestController
  3. @CrossOrigin
  4. public class LoginController {
  5. @Resource
  6. private IUserService userService;
  7. /**
  8. * 登录
  9. * @param username
  10. * @param password
  11. * @param response
  12. * @return
  13. */
  14. @PostMapping("/login")
  15. public Object login(@RequestParam("username")String username, @RequestParam("password")String password, HttpServletResponse response){
  16. //用户名和密码非空判断
  17. if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)){
  18. return userService.login(username, password, response);
  19. } else {
  20. log.info("******");
  21. return ResponseDataUtil.fail("登录失败,用户名或密码为空!");
  22. }
  23. }
  24. }

3、UserController

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 17时41分59秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. @Slf4j
  8. @RestController
  9. @CrossOrigin
  10. @RequestMapping("/api")
  11. public class UserController {
  12. @Resource
  13. private IUserService userService;
  14. /**
  15. * 退出登录
  16. * @param request
  17. * @return
  18. */
  19. @GetMapping("/logout")
  20. public Object logout(HttpServletRequest request){
  21. return userService.logout(request);
  22. }
  23. /**
  24. * 查询所有用户
  25. * @return
  26. */
  27. @GetMapping("/user/list")
  28. public Object queryUserList(){
  29. return userService.queryUserList();
  30. }
  31. /**
  32. * 查询所有用户z
  33. * @return
  34. */
  35. @GetMapping("/user/username")
  36. @RequiresPermissions(value={"3","1"},logical = Logical.OR) //权限限制
  37. public Object queryUserByUserName(@RequestParam("username") String username){
  38. return userService.queryUserByUserName(username);
  39. }
  40. }

4、RoleController

  1. /**
  2. * @Author: LEAVES
  3. * @Date: 2020年12月30日 17时59分39秒
  4. * @Version 1.0
  5. * @Description:
  6. */
  7. @Slf4j
  8. @RestController
  9. @CrossOrigin
  10. @RequestMapping("/api")
  11. public class RoleController {
  12. @Resource
  13. private IRoleService roleService;
  14. /**
  15. * 查询全部角色信息
  16. * @return
  17. */
  18. @GetMapping("/role/list")
  19. public Object queryAll(){
  20. return roleService.queryRoleList();
  21. }
  22. }

十三、测试结果

1、登录生成token

2、带上刚刚生成的token的访问

3、不带token访问

4、权限测试

码云源码地址:https://gitee.com/loveleaveslove/rbac-demo.git

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/437030
推荐阅读
相关标签
  

闽ICP备14008679号