赞
踩
码云源码地址:https://gitee.com/loveleaveslove/rbac-demo.git。
一、搭建数据库
1、用户表
- -- auto-generated definition
- create table t_user
- (
- id bigint not null
- constraint "T_USER_pkey"
- primary key,
- username varchar(64) not null,
- password varchar(128) not null,
- name varchar(64),
- roleid bigint not null,
- age integer,
- icon varchar(128),
- sex varchar(2),
- phone varchar(11),
- email varchar(64),
- area varchar(128)
- );
- comment on table t_user is '用户表';
- comment on column t_user.id is '用户ID';
- comment on column t_user.username is '用户名';
- comment on column t_user.password is '用户密码';
- comment on column t_user.roleid is '角色ID';
- alter table t_user owner to postgres;
字段 | 类型 | 备注 |
---|---|---|
id | serial4 | 用户编号 |
username | varchar | 用户名 |
passwords | varchar | 用户密码 |
roleid | int4 | 用户权限编号 |
2、角色表(由于是简单实现其功能,所以此处简单代替)
- create table t_role
- (
- id bigint not null
- constraint "T_ROLE_pkey"
- primary key,
- name varchar(64),
- auths varchar(128),
- parentid bigint
- );
- comment on table t_role is '角色表';
- comment on column t_role.id is '角色ID ';
- comment on column t_role.name is '角色编号';
- comment on column t_role.auths is '权限ID';
- comment on column t_role.parentid is '父级角色ID';
- alter table t_role owner to postgres;
字段 | 类型 | 备注 |
---|---|---|
id | serial4 | 角色编号 |
rolename | varchar | 角色名称 |
auths | varchar | 角色描述 |
parentid | Integer | 父级角色ID |
- <?xml version="1.0" encoding="UTF-8"?>
- <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>2.3.7.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <groupId>com.leaves.auth</groupId>
- <artifactId>rbac-demo</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <name>rbac-demo</name>
- <description>Demo project for Spring Boot</description>
- <properties>
- <java.version>1.8</java.version>
- </properties>
- <dependencies>
- <!--spring-web-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <!--spring-reids-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <!--JDBC-->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-jdbc</artifactId>
- </dependency>
- <!--mybatis-->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>2.1.4</version>
- </dependency>
- <!--postgresql-->
- <dependency>
- <groupId>org.postgresql</groupId>
- <artifactId>postgresql</artifactId>
- <scope>runtime</scope>
- </dependency>
- <!--lombok-->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- <optional>true</optional>
- </dependency>
- <!--druid-->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid</artifactId>
- <version>1.1.21</version>
- </dependency>
- <!--redis-Jedis-->
- <dependency>
- <groupId>redis.clients</groupId>
- <artifactId>jedis</artifactId>
- <version>2.9.0</version>
- </dependency>
- <!--shiro-reids-->
- <dependency>
- <groupId>org.crazycake</groupId>
- <artifactId>shiro-redis</artifactId>
- <version>3.2.3</version>
- </dependency>
- <!-- Shiro -->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring</artifactId>
- <version>1.3.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-ehcache</artifactId>
- <version>1.2.5</version>
- </dependency>
- <!-- JWT -->
- <dependency>
- <groupId>com.auth0</groupId>
- <artifactId>java-jwt</artifactId>
- <version>3.5.0</version>
- </dependency>
- <!--slf4j-->
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.7.25</version>
- </dependency>
- <!--上传下载文件-->
- <dependency>
- <groupId>commons-fileupload</groupId>
- <artifactId>commons-fileupload</artifactId>
- <version>1.3.1</version>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>1.4</version>
- </dependency>
- <!--添加通用mapper依赖-->
- <dependency>
- <groupId>tk.mybatis</groupId>
- <artifactId>mapper-spring-boot-starter</artifactId>
- <version>2.1.5</version>
- </dependency>
- <!--添加分页依赖-->
- <dependency>
- <groupId>com.github.pagehelper</groupId>
- <artifactId>pagehelper-spring-boot-starter</artifactId>
- <version>1.2.5</version>
- </dependency>
- <!--工具库hutool库-->
- <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.4.3</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- <exclusions>
- <exclusion>
- <groupId>org.junit.vintage</groupId>
- <artifactId>junit-vintage-engine</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- </dependencies>
-
- <build>
- <finalName>RBAC-DEMO</finalName>
- <plugins>
- <plugin>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-maven-plugin</artifactId>
- <configuration>
- <excludes>
- <exclude>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </exclude>
- </excludes>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
- </project>
- #端口号
- server:
- port: 8088
- spring:
- #数据库连接配置
- datasource:
- driver-class-name: org.postgresql.Driver
- type: com.alibaba.druid.pool.DruidDataSource
- url: jdbc:postgresql://localhost:5432/rbac?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&socketTimeout=30000
- username: rbac
- password: 123456
- jackson:
- date-format: yyyy-MM-dd HH:mm:ss
- time-zone: GMT+8
- #允许运行程序中存在多个main函数
- main:
- allow-bean-definition-overriding: true
- #Redis
- redis:
- host: localhost
- port: 6379
- database: 0
- jedis:
- pool:
- max-active: 8
- max-idle: 8
- max-wait: -1
- min-idle: 0
- timeout: 3000ms
- #文件上传配置
- autoconfigure:
- exclude: org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration
- #mybatis配置
- mybatis:
- type-aliases-package: com.leaves.auth.entity
- mapper-locations: classpath*:/mapper/*.xml
- #日志等级
- logging:
- level:
- com:
- leaves:
- auth:
- mapper: debug
- config: classpath:logback-spring.xml
- #文件上传保存地址
- upload:
- local:
- path: D:/uploadFile/file
1、Constant
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: 常量
- */
- private Constant() {}
-
- /**
- * redis-OK
- */
- public static final String OK = "OK";
-
- /**
- * redis过期时间,以秒为单位,一分钟
- */
- public static final int EXRP_MINUTE = 60;
-
- /**
- * redis过期时间,以秒为单位,一小时
- */
- public static final int EXRP_HOUR = 60 * 60;
-
- /**
- * redis过期时间,以秒为单位,一天
- */
- public static final int EXRP_DAY = 60 * 60 * 24;
-
- /**
- * redis-key-前缀-shiro:cache:
- */
- public static final String PREFIX_SHIRO_CACHE = "shiro:cache:";
-
- /**
- * redis-key-前缀-shiro:access_token:
- */
- public static final String PREFIX_SHIRO_ACCESS_TOKEN = "shiro:access_token:";
-
- /**
- * redis-key-前缀-shiro:refresh_token:
- */
- public static final String PREFIX_SHIRO_REFRESH_TOKEN = "shiro:refresh_token:";
-
- /**
- * JWT-account:
- */
- public static final String ACCOUNT = "account";
-
- /**
- * JWT-currentTimeMillis:
- */
- public static final String CURRENT_TIME_MILLIS = "currentTimeMillis";
-
- /**
- * PASSWORD_MAX_LEN
- */
- public static final Integer PASSWORD_MAX_LEN = 8;
2、SerializableUtil
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: Serializable工具(JDK)(也可以使用Protobuf自行百度)
- */
- @Slf4j
- public class SerializableUtil {
-
- private SerializableUtil() {}
-
- /**
- * 序列化
- */
- public static byte[] serializable(Object object) {
- ByteArrayOutputStream baos = null;
- ObjectOutputStream oos = null;
- try {
- baos = new ByteArrayOutputStream();
- oos = new ObjectOutputStream(baos);
- oos.writeObject(object);
- return baos.toByteArray();
- } catch (IOException e) {
- log.error("SerializableUtil工具类序列化出现IOException异常:{}", e.getMessage());
- throw new CustomException("SerializableUtil工具类序列化出现IOException异常:" + e.getMessage());
- } finally {
- try {
- if (oos != null) {
- oos.close();
- }
- if (baos != null) {
- baos.close();
- }
- } catch (IOException e) {
- log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
- throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
- }
- }
- }
-
- /**
- * 反序列化
- */
- public static Object unserializable(byte[] bytes) {
- ByteArrayInputStream bais = null;
- ObjectInputStream ois = null;
- try {
- bais = new ByteArrayInputStream(bytes);
- ois = new ObjectInputStream(bais);
- return ois.readObject();
- } catch (ClassNotFoundException e) {
- log.error("SerializableUtil工具类反序列化出现ClassNotFoundException异常:{}", e.getMessage());
- throw new CustomException("SerializableUtil工具类反序列化出现ClassNotFoundException异常:" + e.getMessage());
- } catch (IOException e) {
- log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
- throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
- } finally {
- try {
- if (ois != null) {
- ois.close();
- }
- if (bais != null) {
- bais.close();
- }
- } catch (IOException e) {
- log.error("SerializableUtil工具类反序列化出现IOException异常:{}", e.getMessage());
- throw new CustomException("SerializableUtil工具类反序列化出现IOException异常:" + e.getMessage());
- }
- }
- }
-
- }
3、StringUtil
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: String工具
- */
- public class StringUtil {
-
- private StringUtil() {}
-
- /**
- * 定义下划线
- */
- private static final char UNDERLINE = '_';
-
- /**
- * String为空判断(不允许空格)
- * @param str
- * @return
- */
- public static boolean isBlank(String str) {
- return str == null || "".equals(str.trim());
- }
-
- /**
- * String不为空判断(不允许空格)
- * @param str
- * @return
- */
- public static boolean isNotBlank(String str) {
- return !isBlank(str);
- }
-
- /**
- * Byte数组为空判断
- * @param bytes
- * @return boolean
- */
- public static boolean isNull(byte[] bytes) {
- // 根据byte数组长度为0判断
- return bytes == null || bytes.length == 0;
- }
-
- /**
- * Byte数组不为空判断
- * @param bytes
- * @return boolean
- */
- public static boolean isNotNull(byte[] bytes) {
- return !isNull(bytes);
- }
-
- /**
- * 驼峰转下划线工具
- * @param param
- * @return java.lang.String
- */
- public static String camelToUnderline(String param) {
- if (isNotBlank(param)) {
- int len = param.length();
- StringBuilder sb = new StringBuilder(len);
- for (int i = 0; i < len; i++) {
- char c = param.charAt(i);
- if (Character.isUpperCase(c)) {
- sb.append(UNDERLINE);
- sb.append(Character.toLowerCase(c));
- } else {
- sb.append(c);
- }
- }
- return sb.toString();
- } else {
- return "";
- }
- }
-
- /**
- * 下划线转驼峰工具
- * @param param
- * @return java.lang.String
- */
- public static String underlineToCamel(String param) {
- if (isNotBlank(param)) {
- int len = param.length();
- StringBuilder sb = new StringBuilder(len);
- for (int i = 0; i < len; i++) {
- char c = param.charAt(i);
- if (c == 95) {
- i++;
- if (i < len) {
- sb.append(Character.toUpperCase(param.charAt(i)));
- }
- } else {
- sb.append(c);
- }
- }
- return sb.toString();
- } else {
- return "";
- }
- }
-
- /**
- * 在字符串两周添加''
- * @param param
- * @return java.lang.String
- */
- public static String addSingleQuotes(String param) {
- return "\'" + param + "\'";
- }
- }
1、JedisConfig
- /**
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- * @Version 1.0
- */
- @Configuration
- public class JedisConfig {
-
- @Value("${spring.redis.host}")
- private String host;
- @Value("${spring.redis.port}")
- private int port ;
- @Bean
- public Jedis getJedis(){
- return new Jedis("127.0.0.1",6379);
- }
- // 封装jedispool对象(将配置对象注入其中)
- @Bean
- public JedisPool jedisPool(@Qualifier("jedisPoolConfig") JedisPoolConfig jedisPoolConfig ){
- JedisPool pool = new JedisPool(jedisPoolConfig,host,port);
- return pool;
- }
- // 封装jedispool配置对象
- @Bean("jedisPoolConfig")
- public JedisPoolConfig jedisPoolConfig(){
- JedisPoolConfig poolConfig = new JedisPoolConfig();
- poolConfig.setMaxIdle(1);
- poolConfig.setMaxTotal(10);
- return poolConfig;
- }
- }
2、JedisUtil
- /**
- * 封装常见的jedis操作接口
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- * @Version 1.0
- */
-
- @Component
- public class JedisUtil {
- /**
- * 静态注入JedisPool连接池
- * 本来是正常注入JedisUtil,可以在Controller和Service层使用,但是重写Shiro的CustomCache无法注入JedisUtil
- * 现在改为静态注入JedisPool连接池,JedisUtil直接调用静态方法即可
- * https://blog.csdn.net/W_Z_W_888/article/details/79979103
- */
- private static JedisPool jedisPool;
-
- @Autowired
- public void setJedisPool(JedisPool jedisPool) {
- JedisUtil.jedisPool = jedisPool;
- }
-
- /**
- * 获取Jedis实例
- * @param
- * @return redis.clients.jedis.Jedis
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static synchronized Jedis getJedis() {
- try {
- if (jedisPool != null) {
- return jedisPool.getResource();
- } else {
- return null;
- }
- } catch (Exception e) {
- throw new CustomException("获取Jedis资源异常:" + e.getMessage());
- }
- }
-
- /**
- * 释放Jedis资源
- * @param
- * @return void
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static void closePool() {
- try {
- jedisPool.close();
- } catch (Exception e) {
- throw new CustomException("释放Jedis资源异常:" + e.getMessage());
- }
- }
-
- /**
- * 获取redis键值-object
- * @param key
- * @return java.lang.Object
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static Object getObject(String key) {
- try (Jedis jedis = jedisPool.getResource()) {
- byte[] bytes = jedis.get(key.getBytes());
- if (StringUtil.isNotNull(bytes)) {
- return SerializableUtil.unserializable(bytes);
- }
- } catch (Exception e) {
- throw new CustomException("获取Redis键值getObject方法异常:key=" + key + " cause=" + e.getMessage());
- }
- return null;
- }
-
- /**
- * 设置redis键值-object
- * @param key
- * @param value
- * @return java.lang.String
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static String setObject(String key, Object value) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.set(key.getBytes(), SerializableUtil.serializable(value));
- } catch (Exception e) {
- throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 设置redis键值-object-expiretime
- * @param key
- * @param value
- * @param expiretime
- * @return java.lang.String
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static String setObject(String key, Object value, int expiretime) {
- String result;
- try (Jedis jedis = jedisPool.getResource()) {
- result = jedis.set(key.getBytes(), SerializableUtil.serializable(value));
- if (Constant.OK.equals(result)) {
- jedis.expire(key.getBytes(), expiretime);
- }
- return result;
- } catch (Exception e) {
- throw new CustomException("设置Redis键值setObject方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 获取redis键值-Json
- * @param key
- * @return java.lang.Object
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static String getJson(String key) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.get(key);
- } catch (Exception e) {
- throw new CustomException("获取Redis键值getJson方法异常:key=" + key + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 设置redis键值-Json
- * @param key
- * @param value
- * @return java.lang.String
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static String setJson(String key, String value) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.set(key, value);
- } catch (Exception e) {
- throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 设置redis键值-Json-expiretime
- * @param key
- * @param value
- * @param expiretime
- * @return java.lang.String
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static String setJson(String key, String value, int expiretime) {
- String result;
- try (Jedis jedis = jedisPool.getResource()) {
- result = jedis.set(key, value);
- if (Constant.OK.equals(result)) {
- jedis.expire(key, expiretime);
- }
- return result;
- } catch (Exception e) {
- throw new CustomException("设置Redis键值setJson方法异常:key=" + key + " value=" + value + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 删除key
- * @param key
- * @return java.lang.Long
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static Long delKey(String key) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.del(key.getBytes());
- } catch (Exception e) {
- throw new CustomException("删除Redis的键delKey方法异常:key=" + key + " cause=" + e.getMessage());
- }
- }
-
- /**
- * key是否存在
- * @param key
- * @return java.lang.Boolean
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static Boolean exists(String key) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.exists(key.getBytes());
- } catch (Exception e) {
- throw new CustomException("查询Redis的键是否存在exists方法异常:key=" + key + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用)
- * @param key
- * @return java.util.Set<java.lang.String>
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static Set<String> keysS(String key) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.keys(key);
- } catch (Exception e) {
- throw new CustomException("模糊查询Redis的键集合keysS方法异常:key=" + key + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 模糊查询获取key集合(keys的速度非常快,但在一个大的数据库中使用它仍然可能造成性能问题,生产不推荐使用)
- * @param key
- * @return java.util.Set<java.lang.String>
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static Set<byte[]> keysB(String key) {
- try (Jedis jedis = jedisPool.getResource()) {
- return jedis.keys(key.getBytes());
- } catch (Exception e) {
- throw new CustomException("模糊查询Redis的键集合keysB方法异常:key=" + key + " cause=" + e.getMessage());
- }
- }
-
- /**
- * 获取过期剩余时间
- * @param key
- * @return java.lang.String
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- */
- public static Long ttl(String key) {
- Long result = -2L;
- try (Jedis jedis = jedisPool.getResource()) {
- result = jedis.ttl(key);
- return result;
- } catch (Exception e) {
- throw new CustomException("获取Redis键过期剩余时间ttl方法异常:key=" + key + " cause=" + e.getMessage());
- }
- }
- }
3、RedisOperator
- /**
- * @Author: LEAVES
- * @Date: 2020年11月02日 15时22分18秒
- * @Version 1.0
- * @Description: 使用redisTemplate的操作实现类
- */
- @Component
- public class RedisOperator {
-
- @Resource
- private StringRedisTemplate redisTemplate;
-
- // Key(键),简单的key-value操作
-
- /**
- * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。
- *
- * @param key
- * @return
- */
- public long ttl(String key) {
- return redisTemplate.getExpire(key);
- }
-
- /**
- * 实现命令:expire 设置过期时间,单位秒
- *
- * @param key
- * @return
- */
- public void expire(String key, long timeout) {
- redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
- }
-
- /**
- * 实现命令:INCR key,增加key一次
- *
- * @param key
- * @return
- */
- public long incr(String key, long delta) {
- return redisTemplate.opsForValue().increment(key, delta);
- }
-
- /**
- * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key
- */
- public Set<String> keys(String pattern) {
- return redisTemplate.keys(pattern);
- }
-
- /**
- * 实现命令:DEL key,删除一个key
- *
- * @param key
- */
- public void del(String key) {
- redisTemplate.delete(key);
- }
-
- // String(字符串)
-
- /**
- * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key)
- *
- * @param key
- * @param value
- */
- public void set(String key, String value) {
- redisTemplate.opsForValue().set(key, value);
- }
-
- /**
- * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒)
- *
- * @param key
- * @param value
- * @param timeout
- * (以秒为单位)
- */
- public void set(String key, String value, long timeout) {
- redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
- }
-
- /**
- * 实现命令:GET key,返回 key所关联的字符串值。
- *
- * @param key
- * @return value
- */
- public String get(String key) {
- return (String)redisTemplate.opsForValue().get(key);
- }
-
- // Hash(哈希表)
-
- /**
- * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value
- *
- * @param key
- * @param field
- * @param value
- */
- public void hset(String key, String field, Object value) {
- redisTemplate.opsForHash().put(key, field, value);
- }
-
- /**
- * 实现命令:HGET key field,返回哈希表 key中给定域 field的值
- *
- * @param key
- * @param field
- * @return
- */
- public String hget(String key, String field) {
- return (String) redisTemplate.opsForHash().get(key, field);
- }
-
- /**
- * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。
- *
- * @param key
- * @param fields
- */
- public void hdel(String key, Object... fields) {
- redisTemplate.opsForHash().delete(key, fields);
- }
-
- /**
- * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。
- *
- * @param key
- * @return
- */
- public Map<Object, Object> hgetall(String key) {
- return redisTemplate.opsForHash().entries(key);
- }
-
- // List(列表)
-
- /**
- * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头
- *
- * @param key
- * @param value
- * @return 执行 LPUSH命令后,列表的长度。
- */
- public long lpush(String key, String value) {
- return redisTemplate.opsForList().leftPush(key, value);
- }
-
- /**
- * 实现命令:LPOP key,移除并返回列表 key的头元素。
- *
- * @param key
- * @return 列表key的头元素。
- */
- public String lpop(String key) {
- return (String)redisTemplate.opsForList().leftPop(key);
- }
-
- /**
- * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。
- *
- * @param key
- * @param value
- * @return 执行 LPUSH命令后,列表的长度。
- */
- public long rpush(String key, String value) {
- return redisTemplate.opsForList().rightPush(key, value);
- }
-
- //zset
-
- /**
- * 实现命令:zset存入值
- * @param key
- * @param value
- * @param score
- */
- public void zSet(String key, String value, double score){
- redisTemplate.opsForZSet().add(key,value,score);
- }
-
- public Set<String> zGet(String key, long start, long end){
- return redisTemplate.opsForZSet().range(key,start,end);
- }
- }
1、CustomException
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: 自定义异常(CustomException)
- */
- public class CustomException extends AuthenticationException {
-
- public CustomException(String msg){
- super(msg);
- }
-
- public CustomException() {
- super();
- }
- }
2、DefaultExceptionHandler
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: 自定义异常处理器
- */
- @Slf4j
- @ControllerAdvice //不指定包默认加了@Controller和@RestController都能控制
- public class DefaultExceptionHandler {
-
- /**
- * @param ex
- * @Description: 运行时异常
- */
- @ExceptionHandler(com.leaves.auth.exception.CustomException.class)
- public ResponseData CustomExceptionHandler(com.leaves.auth.exception.CustomException ex) {
- log.error(ex.getMessage(), ex);
- ResponseData responseData=new ResponseData<>();
- responseData.setCode(400);
- responseData.setMsg(ex.getMessage());
- return responseData;
- }
-
- /**
- * @param ex
- * @Description: 权限认证异常
- */
- @ExceptionHandler(UnauthorizedException.class)
- @ResponseBody
- public ResponseData unauthorizedExceptionHandler(Exception ex) {
- log.error(ex.getMessage(), ex);
- return ResponseDataUtil.fail("对不起,您没有相关权限!");
- }
-
- @ExceptionHandler(UnauthenticatedException.class)
- @ResponseBody
- public ResponseData unauthenticatedException(UnauthenticatedException ex) {
- log.error(ex.getMessage(), ex);
- return ResponseDataUtil.fail("对不起,您未登录!");
- }
-
- /**
- *
- * @param ex
- * @return
- */
- @ExceptionHandler(AuthorizationException.class)
- @ResponseBody
- public ResponseData authorizationException(AuthorizationException ex) {
- log.error(ex.getMessage(), ex);
- return ResponseDataUtil.fail("无效token,请重新登录!");
- }
-
- /**
- * 空指针异常
- * @param ex
- * @return
- */
- @ExceptionHandler(NullPointerException.class)
- @ResponseBody
- public ResponseData nullPointerException(NullPointerException ex) {
- log.error(ex.getMessage(), ex);
- return ResponseDataUtil.fail(500,"空指针异常!");
- }
-
- /**
- * 认证异常
- * @param ex
- * @return
- */
- @ExceptionHandler(AuthenticationException.class)
- @ResponseBody
- public ResponseData authenticationException(AuthenticationException ex) {
- log.error(ex.getMessage(), ex);
- return ResponseDataUtil.fail("token为空,请重新登录!");
- }
-
- /**
- * 文件上传异常
- * @param ex
- * @return
- */
- @ExceptionHandler(IOException.class)
- @ResponseBody
- public ResponseData ioException(IOException ex) {
- log.error(ex.getMessage(), ex);
- return ResponseDataUtil.fail("文件上传异常!");
- }
-
- /**
- * 异常统一自定义处理
- */
- @ExceptionHandler({MyException.class})
- @ResponseBody
- public ResponseData MyException(MyException e) {
- return ResponseDataUtil.fail(500,e.getMessage());
- }
-
- /**
- * 异常统一处理(最后的异常处理)
- */
- @ExceptionHandler({Exception.class})
- @ResponseStatus(HttpStatus.OK)
- @ResponseBody
- public ResponseData allException(Exception e) {
- log.info(e.getMessage());
- return ResponseDataUtil.fail(500,"系统异常!");
- }
- }
3、MyException
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: 自定义异常
- */
- @Data
- public class MyException extends RuntimeException {
-
- /**
- * 自定义返回状态码
- */
- private Integer code;
-
-
- /**
- * 返回自定义的状态码和异常描述
- * @param code
- * @param message
- */
- public MyException(Integer code, String message) {
- super(message);
- this.code = code;
- }
-
- /**
- * 只返回异常信息(默认返回500)
- * @param message
- */
- public MyException(String message) {
-
- super(message);
- }
- }
1、ResponseData
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: 统一返回格式
- */
- @Data
- public class ResponseData<T> {
-
- /**
- * 统一返回码
- */
- public Integer code;
-
- /**
- * 统一消息
- */
- public String msg;
-
- /**
- * 结果对象
- */
- @JsonInclude(JsonInclude.Include.NON_NULL)
- public T data;
-
- }
2、ResponseDataUtil
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- * @Description: 统一返回格式封装
- */
- @Slf4j
- public class ResponseDataUtil {
-
- /**
- * 返回成功的描述 状态码、说明
- * @param msg
- * @return
- */
- public static ResponseData success(String msg){
- ResponseData responseData = new ResponseData();
- responseData.setCode(200);
- responseData.setMsg(msg);
- return responseData;
- }
-
- /**
- * 返回成功的描述 状态码、说明
- * @param code
- * @param msg
- * @return
- */
- public static <T> ResponseData success(Integer code, String msg){
- ResponseData responseData = new ResponseData();
- responseData.setCode(code);
- responseData.setMsg(msg);
- return responseData;
- }
-
- /**
- * 返回成功的描述 状态码、说明、数据
- * @param msg
- * @param data
- * @param <T>
- * @return
- */
- public static <T> ResponseData success(String msg, T data){
- ResponseData responseData = new ResponseData();
- responseData.setCode(200);
- responseData.setMsg(msg);
- responseData.setData(data);
- return responseData;
- }
-
- /**
- * 返回成功的描述 状态码、说明、令牌
- * @param code
- * @param msg
- * @param data
- * @param <T>
- * @return
- */
- public static <T> ResponseData success(Integer code, String msg, T data){
- ResponseData responseData = new ResponseData();
- responseData.setCode(code);
- responseData.setMsg(msg);
- responseData.setData(data);
- return responseData;
- }
-
- /**
- * 返回失败的描述 状态码
- * @param msg
- * @return
- */
- public static ResponseData fail(String msg){
- ResponseData responseData=new ResponseData();
- responseData.setCode(405);
- responseData.setMsg(msg);
- return responseData;
- }
-
- /**
- * 返回失败的描述 状态码、说明
- * @param code
- * @param msg
- * @return
- */
- public static ResponseData fail(Integer code, String msg){
- ResponseData responseData=new ResponseData();
- responseData.setCode(code);
- responseData.setMsg(msg);
- return responseData;
- }
-
- /**
- * 返回失败的描述 状态码、说明
- * @param code
- * @param msg
- * @param data
- * @param <T>
- * @return
- */
- public static <T> ResponseData fail(Integer code, String msg, T data){
- ResponseData responseData=new ResponseData();
- responseData.setCode(code);
- responseData.setMsg(msg);
- responseData.setData(data);
- return responseData;
- }
- }
1、IsNotEmptyUtil
- * @Author: LEAVES
- * @Date: 2020年12月30日 14时29分15秒
- * @Version 1.0
- * @Description: 非空判断
- */
- public class IsNotEmptyUtil {
- public static boolean isEmpty(Object object) {
- if (object == null) {
- return (true);
- }
- if ("".equals(object)) {
- return (true);
- }
- if ("null".equals(object)) {
- return (true);
- }
- return (false);
- }
-
- public static boolean isNotEmpty(Object object) {
- if (object != null && !object.equals("") && !object.equals("null")) {
- return (true);
- }
- return (false);
- }
- }
2、MD5
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 17时55分06秒
- * @Version 1.0
- * @Description: MD5加密
- */
- public class MD5Util {
-
- /***
- * MD5加码 生成32位md5码
- */
- public static String string2MD5(String inStr) {
- MessageDigest md5 = null;
- try {
- md5 = MessageDigest.getInstance("MD5");
- } catch (Exception e) {
- System.out.println(e.toString());
- e.printStackTrace();
- return "";
- }
- char[] charArray = inStr.toCharArray();
- byte[] byteArray = new byte[charArray.length];
-
- for (int i = 0; i < charArray.length; i++) {
- byteArray[i] = (byte) charArray[i];
- }
- byte[] md5Bytes = md5.digest(byteArray);
- StringBuffer hexValue = new StringBuffer();
- for (int i = 0; i < md5Bytes.length; i++) {
- int val = ((int) md5Bytes[i]) & 0xff;
- if (val < 16){
- hexValue.append("0");
- }
- hexValue.append(Integer.toHexString(val));
- }
- return hexValue.toString();
-
- }
-
- /**
- * 加密解密算法 执行一次加密,两次解密
- */
- public static String convertMD5(String inStr) {
-
- char[] a = inStr.toCharArray();
- for (int i = 0; i < a.length; i++) {
- a[i] = (char) (a[i] ^ 't');
- }
- String s = new String(a);
- return s;
-
- }
- }
3、含有NULL值属性对象转JSON时使其变成空字符串
- /**
- * 处理返回值中的null值
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- */
- //@EnableWebMvc
- @Configuration
- public class JsonConfig {
-
- @Bean
- public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
-
- final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
-
- ObjectMapper mapper = converter.getObjectMapper();
-
- // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
- mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
-
- return converter;
- }
-
- }
- /**
- * 该类控制将null值处理成空集合还是空字符串
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- */
- public class MyBeanSerializerModifier extends BeanSerializerModifier {
-
- // 数组类型
- private JsonSerializer _nullArrayJsonSerializer = new MyNullArrayJsonSerializer();
- // 字符串等类型
- private JsonSerializer _nullJsonSerializer = new MyNullJsonSerializer();
- // 对象类型
- private JsonSerializer _nullObjectSerializer = new MyNullObjectJsonSerializer();
-
- @Override
- public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
- List beanProperties) {
- //循环所有的beanPropertyWriter
- for (int i = 0; i < beanProperties.size(); i++) {
- BeanPropertyWriter writer = (BeanPropertyWriter) beanProperties.get(i);
- //判断字段的类型,如果是array,list,set则注册nullSerializer
- if (isArrayType(writer)) {
- //给writer注册一个自己的nullSerializer
- writer.assignNullSerializer(this._nullArrayJsonSerializer);
- } else if(isStringOrNumberType(writer)){
- writer.assignNullSerializer(this._nullJsonSerializer);
- }else{
- writer.assignNullSerializer(this._nullObjectSerializer);
- }
- }
- return beanProperties;
- }
-
- //判断是什么类型
- protected boolean isArrayType(BeanPropertyWriter writer) {
- Class clazz = writer.getPropertyType();
- return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class);
- }
-
- //判断是什么类型
- protected boolean isStringOrNumberType(BeanPropertyWriter writer) {
- Class clazz = writer.getPropertyType();
- return clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(int.class) || clazz.equals(Double.class) || clazz.equals(Short.class)
- || clazz.equals(Long.class) || clazz.equals(short.class) || clazz.equals(double.class) || clazz.equals(long.class) || clazz.equals(Date.class)
- || clazz.equals(BigDecimal.class);
- }
- }
- /**
- * 处理数组类型的null值
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- */
- public class MyNullArrayJsonSerializer extends JsonSerializer {
-
- @Override
- public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
- if (value == null) {
- jgen.writeStartArray();
- jgen.writeEndArray();
- }
- }
- }
- /**
- * 处理字符串等类型的null值
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- */
- public class MyNullJsonSerializer extends JsonSerializer {
-
- @Override
- public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
- throws IOException, JsonProcessingException {
- jsonGenerator.writeString("");
- }
- }
- /**
- * 处理对象类型的null值
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时06分23秒
- * @Version 1.0
- */
- public class MyNullObjectJsonSerializer extends JsonSerializer {
- @Override
- public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
- throws IOException, JsonProcessingException {
- jsonGenerator.writeStartObject();
- jsonGenerator.writeEndObject();
- }
- }
1、JwtToken
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 14时25分08秒
- * @Version 1.0
- * @Description: Authentication-Token: shiro中负责把username,password生成用于验证的token的封装类,需要自定义一个对象用来包装token。
- */
- public class JwtToken implements AuthenticationToken {
-
- private String token;
-
- public JwtToken(String token){
- this.token = token;
- }
-
- @Override
- public Object getPrincipal() {
- return token;
- }
-
- @Override
- public Object getCredentials() {
- return token;
- }
- }
2、JwtUtil
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 14时25分08秒
- * @Version 1.0
- * @Description:
- */
- @Slf4j
- public class JwtUtil {
-
- //token过期时间 5小时
- private static final long EXPIRE_TIME = 1000 * 60 * 60 * 5;
- //redis中token过期时间 12小时
- public static final Integer REFRESH_EXPIRE_TIME = 60 * 60 * 12;
- //token密钥(自定义)
- private static final String TOKEN_SECRET = "^/zxc*123!@#$%^&*/";
-
-
- /**
- * 校验token是否正确
- * @param token token
- * @param username 用户名
- * @return 是否正确
- */
- public static boolean verify(String token, String username){
- log.info("JwtUtil==verify--->");
- try {
- log.info("JwtUtil==verify--->校验token是否正确");
- //根据密码生成JWT效验器
- Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); //秘钥是密码则省略
- JWTVerifier verifier = JWT.require(algorithm)
- .withClaim("username", username)
- // .withClaim("secret",secret) //秘钥是密码直接传入
- .build();
- //效验TOKEN
- DecodedJWT jwt = verifier.verify(token);
- log.info("JwtUtil==verify--->jwt = "+jwt.toString());
- log.info("JwtUtil==verify--->JwtUtil验证token成功!");
- return true;
- }catch (Exception e){
- log.error("JwtUtil==verify--->JwtUtil验证token失败!");
- return false;
- }
- }
-
- /**
- * 获取token中的信息(包含用户名)
- * @param token
- * @return
- */
- public static String getUsername(String token) {
- log.info("JwtUtil==getUsername--->");
- if (IsNotEmptyUtil.isEmpty(token)){
- log.error("JwtUtil==getUsername--->token无效或token中未包含用户信息! ");
- throw new AuthenticationException("认证失败,token无效或token中未包含用户信息!");
- }
- try {
- DecodedJWT jwt = JWT.decode(token);
- System.out.println("token = " + jwt.getToken());
- log.info("JwtUtil==getUsername--->username = "+jwt.getClaim("username").asString());
- return jwt.getClaim("username").asString();
- } catch (JWTDecodeException e) {
- log.error("JwtUtil==getUsername--->JWTDecodeException: " + e.getMessage());
- return null;
- }
- }
-
- /**
- * 生成token签名
- * EXPIRE_TIME 分钟后过期
- * @param username 用户名
- * @return 加密的token
- */
- public static String sign(String username) {
- log.info("JwtUtil==sign--->");
- Map<String, Object> header = new HashMap<>();
- header.put("type","Jwt");
- header.put("alg","HS256");
- long currentTimeMillis = System.currentTimeMillis();
- //设置token过期时间
- Date date = new Date(currentTimeMillis + EXPIRE_TIME);
- Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);//秘钥是密码则省略
- //生成签名
- String sign = JWT.create()
- .withHeader(header)
- .withExpiresAt(date)
- .withClaim("username", username)
- // .withClaim("secret",secret) //秘钥是密码直接传入
- .withClaim("currentTime", currentTimeMillis + EXPIRE_TIME)
- .sign(algorithm);
- log.info("JwtUtil==sign--->sign = " + sign);
- return sign;
- }
-
- /**
- * 获取token中用户信息
- * @param token
- * @return
- */
- public static String getAccount(String token){
- try{
- DecodedJWT decodedJWT=JWT.decode(token);
- return decodedJWT.getClaim("username").asString();
-
- }catch (JWTCreationException e){
- return null;
- }
- }
-
- /**
- * 获取token中用户密码
- * @param token
- * @return
- */
- public static String getSecret(String token){
- try{
- DecodedJWT decodedJWT=JWT.decode(token);
- return decodedJWT.getClaim("secret").asString();
-
- }catch (JWTCreationException e){
- return null;
- }
- }
-
- /**
- * 获取token的时间戳
- * @param token
- * @return
- */
- public static Long getCurrentTime(String token){
- try{
- DecodedJWT decodedJWT=JWT.decode(token);
- return decodedJWT.getClaim("currentTime").asLong();
-
- }catch (JWTCreationException e){
- return null;
- }
- }
- }
3、JwtFilter
- /**
- * 重写鉴权
- * @Author: LEAVES
- * @Date: 2020年12月30日 14时25分08秒
- * @Version 1.0
- * @Description: 执行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin 。
- */
- @Slf4j
- public class JwtFilter extends BasicHttpAuthenticationFilter {
-
- //默认需要放行的接口 shiroc处判断过,此处可写可不写
- private String[] defalutExcludeUrl = new String[] {
- "/login","/401", "/402", "/noaccess"
- //,"/formLogin",".jpg",".png",".gif",".css",".js",".jpeg"
- };
-
- /**
- * 检测用户是否想要登录
- * 检测header里面是否包含token字段即可
- * @param request
- * @param response
- * @return
- */
- @Override
- protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
- log.info("JwtFilter==isLoginAttempt--->");
- HttpServletRequest req = (HttpServletRequest) request;
- String authorization = req.getHeader("Authorization");
- if (authorization != null){
- //去掉token前缀
- authorization = authorization.substring(7);
- log.info("JwtFilter==isLoginAttempt--->authorization = " + authorization);
- log.info("JwtFilter==isLoginAttempt--->用户已经登录过了");
- return authorization != null;
- }else{
- log.info("JwtFilter==isLoginAttempt--->用户未登录");
- return authorization == null;
- }
- }
-
- /**
- * JwtToken实现了AuthenticationToken接口封装了token参数
- * 通过getSubject方法获取 subject对象
- * login()发送身份验证
- * 为什么需要在Filter中调用login,不能在controller中调用login?
- * 由于Shiro默认的验证方式是基于session的,在基于token验证的方式中,不能依赖session做为登录的判断依据.
- * 执行登录
- * @param request
- * @param response
- * @return
- * @throws Exception
- */
- @Override
- protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
- log.info("JwtFilter==executeLogin--->");
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- String authorization = httpServletRequest.getHeader("Authorization");
- //去掉token前缀
- authorization = authorization.substring(7);
- JwtToken token = new JwtToken(authorization);
- // 提交给realm进行登入,如果错误他会抛出异常并被捕获
- try {
- //触发 Shiro Realm 自身的登录控制
- getSubject(request, response).login(token);
- // 如果没有抛出异常则代表登入成功,返回true
- log.info("JwtFilter==executeLogin--->验证登入成功");
- //刷新token
- this.refreshToken(authorization, response);
- return true;
- } catch (Exception e) {
- log.error("JwtFilter==executeLogin--->没有访问权限,原因是:" + e.getMessage());
- //此处跳转到401接口返回错误信息!
- this.responseInvalid(response);
- //throw new AuthenticationException("无效token,请先登录!!!!" + e.getMessage());
- return false;
- }
- }
-
- /**
- * 这里详细说明下为什么最终返回的都是true,即允许访问
- * 例如提供一个地址 GET /article
- * 登入用户和游客看到的内容是不同的
- * 如果在这里返回了false,请求会被直接拦截,用户看不到任何东西
- * 所以在这里返回true,Controller中可以通过 subject.isAuthenticated() 来判断用户是否登入
- * 如果有些资源只有登入用户才能访问,我们只需要在方法上面加上 @RequiresAuthentication 注解即可
- * 但是这样做有一个缺点,就是不能够对GET,POST等请求进行分别过滤鉴权(因为我们重写了官方的方法),但实际上对应用影响不大
- * @param request
- * @param response
- * @param mappedValue
- * @return
- */
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
- log.info("JwtFilter==isAccessAllowed--->");
- //判断请求的请求头是否带上 "token"
- if (this.isLoginAttempt(request, response)) {
- try {
- //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确
- if (this.executeLogin(request, response)){
- String requestURL = ((HttpServletRequest) request).getRequestURL().toString();
- log.info("JwtFilter==isAccessAllowed--->requestURL="+requestURL);
- for(String excludeUrl : defalutExcludeUrl){
- if (requestURL.endsWith(excludeUrl)){
- return true;
- }
- }
- }
- } catch (Exception e) {
- log.error("JwtFilter==isAccessAllowed--->Token已失效或为空,JwtFilter过滤验证失败!");
- //此处跳转到402接口返回错误信息!
- this.responseInvalid(response);
- // throw new AuthenticationException("token为空,请重新登录!");
- }
- }
- //如果请求头不存在 token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true
- return true;
- }
-
- /**
- * 对跨域提供支持
- * @param request
- * @param response
- * @return
- * @throws Exception
- */
- @Override
- protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
- log.info("JwtFilter==preHandle--->");
- HttpServletRequest httpServletRequest = (HttpServletRequest) request;
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
- httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
- httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
- httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
- // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
- if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
- httpServletResponse.setStatus(HttpStatus.OK.value());
- return false;
- }
- return super.preHandle(request, response);
- }
-
- /**
- * 刷新token
- * @param authorization
- * @param response
- * @return
- * refreshTokens方法中,当redisToken不为空时返回true,并且如果redisToken验证成功,则将已有的token重新存入一遍,保持redis中的token不过期
- * 如果redisToken验证不通过,重新生成新的token,存入redis并返回给前端;当redisToken为空时返回false,需要重新登录.
- */
- @SneakyThrows
- protected void refreshToken(String authorization, ServletResponse response){
- log.info("-------------刷新token-------------");
- //获取token的用户名
- String account = JwtUtil.getAccount(authorization);
- //获取redis中的token
- String redisToken = JedisUtil.getJson(account);
- //判断redis中的token是否为空
- if (IsNotEmptyUtil.isNotEmpty(redisToken)){
- //将现有的token重新存入redis中
- JedisUtil.setJson(account, authorization, REFRESH_EXPIRE_TIME);
- //给前端返回新生成的token
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
- httpServletResponse.setHeader("token", authorization);
- httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
- log.info("redis中的token还未过期!");
- } else {
- //生成新的token
- String newToken = JwtUtil.sign(account);
- //将新生成的token重新存入redis中
- JedisUtil.setJson(account, newToken, REFRESH_EXPIRE_TIME);
- //给前端返回原来请求的token
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
- httpServletResponse.setHeader("token", newToken);
- httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
- log.info("redis中的token已过期!");
- }
- }
-
- /**
- * 将非法请求跳转到 /401 暂无token!
- * @param response
- */
- private void responseInvalid(ServletResponse response) {
- log.info("JwtFilter==response401--->");
- try {
- HttpServletResponse httpServletResponse = (HttpServletResponse) response;
- httpServletResponse.sendRedirect("/invalid");
- } catch (IOException e) {
- log.error(e.getMessage());
- }
- }
-
- // /**
- // * 无需转发,直接返回Response信息
- // */
- // private void response401(ServletResponse response, String msg) {
- // HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
- // httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
- // httpServletResponse.setCharacterEncoding("UTF-8");
- // httpServletResponse.setContentType("application/json; charset=utf-8");
- // try (PrintWriter out = httpServletResponse.getWriter()) {
- // String data = JsonConvertUtil.objectToJson(new ResponseBean(HttpStatus.UNAUTHORIZED.value(), "无权访问(Unauthorized):" + msg, null));
- // out.append(data);
- // } catch (IOException e) {
- // log.error("直接返回Response信息出现IOException异常:{}", e.getMessage());
- // throw new CustomException("直接返回Response信息出现IOException异常:" + e.getMessage());
- // }
- // }
- }
1、MyRealm
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 14时25分08秒
- * @Version 1.0
- * @Description:
- */
- @Slf4j
- @Component
- public class MyRealm extends AuthorizingRealm {
-
- @Resource
- private IRoleService roleService;
- @Resource
- private IUserService userService;
-
- /**
- * 必须重写此方法,否则Shiro会报错
- * @param token
- * @return
- */
- @Version
- public boolean supports(AuthenticationToken token) {
- return token instanceof JwtToken;
- }
-
- /**
- * 授权
- * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的
- * @param principals
- * @return
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) throws AuthorizationException {
- //授权
- log.info("-------------用户授权-------------");
- log.info("MyRealm==doGetAuthorizationInfo--->");
- //如果身份认证的时候没有传入User对象,这里只能取到userName
- TUser tUser = (TUser) principals.getPrimaryPrincipal();
- String username = tUser.getUsername();
- // //通过调用JwtUtil.getUsername()方法得到token中的username
- // String username = JwtUtil.getUsername(principals.toString());
- if (IsNotEmptyUtil.isEmpty(username)){
- throw new AuthorizationException("无效token,请重新登录!");
- }
- //调用业务方法获取用户的角色
- Set<String> permissions = roleService.getPermissionByUserName(username);
- // String role = userService.getRole(username);
- //调用业务方法获取用户权限
- // List<Permissions> list = roleService.getPermissionsByUsername(username);
- //每个用户可以设置新的权限
- // String permission = userService.getPermission(username);
- //将List换成set去掉重复权限
- // Set<String> stringPermissions = new HashSet<>();
- // Set<String> roleSet = new HashSet<>();
- // if (list !=null){
- // for (Permissions permissions : list){
- // log.info(username + "拥有的权限有:" + permissions);
- // stringPermissions.add(permissions.getPername());
- // }
- // }
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- //设置该用户拥有的角色和权限
- // authorizationInfo.setRoles(roleSet);
- authorizationInfo.setStringPermissions(permissions);
- // authorizationInfo.setStringPermissions(stringPermissions);
- return authorizationInfo;
- }
-
- /**
- * 认证
- * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。
- * @param auth
- * @return
- * @throws AuthenticationException
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
- log.info("-------------用户认证-------------");
- //获取用户token信息
- String token = (String) auth.getCredentials();
- // 帐号为空
- if (IsNotEmptyUtil.isEmpty(token)) {
- throw new AuthenticationException("暂无token!");
- }
- log.info("MyRealm==doGetAuthenticationInfo--->token = " + token);
- //判断token中是否包含用户信息
- String username = null;
- try {
- //这里工具类没有处理空指针等异常这里处理一下(这里处理科学一些)
- //解密获得username,用于和数据库进行对比
- username = JwtUtil.getUsername(token);
- log.info("MyRealm==doGetAuthenticationInfo--->从token中解析出的username = " + username);
- } catch (Exception e) {
- log.info("MyRealm==doGetAuthenticationInfo--->AuthenticationException:token拼写错误或者值为空!");
- throw new AuthenticationException("token拼写错误或者值为空");
- }
- if (username == null) {
- log.error("MyRealm==doGetAuthenticationInfo--->token无效(空''或者null都不行!)");
- throw new AuthenticationException("认证失败,token无效或token中未包含用户信息!");
- }
- //根据用户信息查询数据库获取后端的用户身份,转交给securityManager判定
- //调用业务方法从数据库中获取用户信息
- TUser tUser = userService.getUserByUserName(username);
- //判断从数据库中获取用户信息是否为空
- if (tUser == null) {
- log.error("MyRealm==doGetAuthenticationInfo--->用户不存在!)");
- throw new AuthenticationException("用户" + username + "不存在!");
- }
- //认证
- return new SimpleAuthenticationInfo(tUser, token, "my_realm");
- }
- }
2、ShiroConfig
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 14时23分12秒
- * @Version 1.0
- * @Description:
- */
- @Slf4j
- @Configuration
- public class ShiroConfig {
-
- @Bean
- public MyRealm myRealm() {
- return new MyRealm();
- }
-
- /**
- * 注入安全过滤器
- * @param securityManager
- * @return
- */
- @Bean("shiroFilter")
- public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
- log.info("ShiroConfig==shiroFilter--->");
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- shiroFilterFactoryBean.setSecurityManager(securityManager);
- //拦截器
- Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
- // 配置不会被拦截的链接 顺序判断
- filterChainDefinitionMap.put("/", "anon");
- filterChainDefinitionMap.put("/login", "anon");
- // 添加自己的过滤器并且取名为jwt
- Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
- filterMap.put("jwt", new JwtFilter());
- log.info("ShiroConfig==shiroFilter--->new JwtFilter() : " + new JwtFilter());
- shiroFilterFactoryBean.setFilters(filterMap);
- //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
- filterChainDefinitionMap.put("/api/**", "jwt");
- //未授权界面;
- shiroFilterFactoryBean.setUnauthorizedUrl("/noaccess");
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
- return shiroFilterFactoryBean;
- }
-
- /**
- * 注入安全管理器
- * @param myRealm
- * @return
- */
- @Bean("securityManager")
- public SecurityManager securityManager(MyRealm myRealm) {
- log.info("ShiroConfig==securityManager--->");
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- securityManager.setRealm(myRealm);
-
- /*
- * 关闭shiro自带的session,详情见文档
- * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
- */
- DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
- DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
- defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
- subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
- securityManager.setSubjectDAO(subjectDAO);
- return securityManager;
-
- }
-
- /**
- * 自动创建代理,没有这个鉴权可能会出错
- * @return
- */
- @Bean
- public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
- log.info("ShiroConfig==getDefaultAdvisorAutoProxyCreator--->");
- DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
- autoProxyCreator.setProxyTargetClass(true);
- return autoProxyCreator;
- }
- /**
- * 开启shiro aop注解支持.
- * 使用代理方式;所以需要开启代码支持;
- * @param
- * @return
- */
- @Bean
- public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
- log.info("ShiroConfig==authorizationAttributeSourceAdvisor--->");
- AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
- authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
- return authorizationAttributeSourceAdvisor;
- }
- }
(整个过程中省略了mapper与xml的实现,以及实体类的。mapper、xml、entity、service、controller都可根据实际情况进行修改。)
1、UserService以及实现类
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 13时44分34秒
- * @Version 1.0
- * @Description:
- */
- public interface IUserService {
-
- TUser getUserByUserName(String username);
-
- ResponseData login(String username, String password, HttpServletResponse response);
-
- ResponseData logout(HttpServletRequest request);
-
- ResponseData queryUserList();
-
- ResponseData queryUserByUserName(String username);
-
- }
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 13时44分45秒
- * @Version 1.0
- * @Description:
- */
- @Service
- @Slf4j
- public class UserService implements IUserService {
-
- @Resource
- private TUserMapper userMapper;
- @Resource
- private TRoleMapper roleMapper;
-
- /**
- * 根据用户名查询用户信息(程序内部调用)
- * @param username
- * @return
- */
- @Override
- public TUser getUserByUserName(String username) {
- TUser tUser = userMapper.selectByUserName(username);
- log.info("权限认证===>根据用户名查询用户信息:" + tUser.toString());
- return tUser;
- }
-
- /**
- * 登录实现
- * @param username
- * @param password
- * @param response
- * @return
- */
- @Override
- public ResponseData login(String username, String password, HttpServletResponse response) {
- log.info("用户登录");
- //为了简单实现此功能,并也未对前端传来的用户名和密码做正则校验
- //selectUserToRoleByUserName该方法是一对一查询
- TUser tUser = userMapper.selectUserToRoleByUserName(username);
- // Set<String> roleByUserName = tRoleService.getRoleByUserName(username);
- //由于前端+controller两处对username和password进行了判断,所以此处不再做判断
-
- if (tUser != null) {
- //解密数据库中用户密码
- String dbpwd = MD5Util.convertMD5(tUser.getPassword());
- //判断输入的用户名和密码是否与数据库中的用户名、密码一致
- if(tUser.getUsername().equals(username) && dbpwd.equals(password)){
- //生成token
- String token = JwtUtil.sign(tUser.getUsername());
- log.info("登录时生成的token = " + token);
- //获取当前时间的时间戳
- long currentTimeMillis = System.currentTimeMillis();
- //向redis中存入token
- JedisUtil.setJson(username, token, JwtUtil.REFRESH_EXPIRE_TIME);
- //向前端返回token
- HttpServletResponse httpServletResponse = response;
- httpServletResponse.setHeader("token", token);
- httpServletResponse.setHeader("Access-Control-Expose-Headers", "token");
- log.info("登录成功:" + tUser.toString());
- tUser.setPassword(dbpwd);
- Map<String, Object> map = new HashMap<>();
- map.put("user", tUser);
- map.put("token", token);
- return ResponseDataUtil.success("登录成功", map);
- }
- return ResponseDataUtil.fail("登录失败,用户名或密码错误!");
- }
- return ResponseDataUtil.fail("登录失败,用户不存在!");
- }
-
- /**
- * 退出登录
- * @param request
- * @return
- */
- @Override
- public ResponseData logout(HttpServletRequest request) {
- log.info("退出登录");
- //获取token
- String token = request.getHeader("Authorization");
- //去掉token前缀
- token = token.substring(7);
- //token非空判断
- if (IsNotEmptyUtil.isEmpty(token)) {
- return ResponseDataUtil.fail("无效的token!");
- }
- //获取token中的用户名
- String username = JwtUtil.getAccount(token);
- //判断该token的用户是否存在
- TUser tUser = userMapper.selectByUserName(username);
- if (tUser != null) {
- log.info(" 用户名: " + username + " 退出登录成功! ");
- if (JedisUtil.getJson(username) == null){
- return ResponseDataUtil.fail("已经退出登录过了!");
- }
- //清空redis中用户Token缓存
- Long aLong = JedisUtil.delKey(username);
- return ResponseDataUtil.success("退出登录成功!");
- }
- return ResponseDataUtil.fail("令牌失效,请重新登录!");
- }
-
- /**
- * 查询全部用户信息
- * @return
- */
- @Override
- public ResponseData queryUserList() {
- List<TUser> tUsers = userMapper.selectAll();
- if (!tUsers.isEmpty()){
- for (TUser tUser : tUsers){
- //密码解密
- tUser.setPassword(MD5Util.convertMD5(tUser.getPassword()));
- }
- log.info("获取全部用户信息:" + tUsers.toString());
- return ResponseDataUtil.success("查询成功!", tUsers);
- }
- return ResponseDataUtil.fail("暂无用户信息!");
- }
-
- /**
- * 根据用户查询用户信息(接口)
- * @param username
- * @return
- */
- @Override
- public ResponseData queryUserByUserName(String username) {
- TUser tUser = userMapper.selectByUserName(username);
- if (tUser != null){
- return ResponseDataUtil.success("查询成功!", tUser);
- }
- return ResponseDataUtil.fail("暂无该用户信息!");
- }
- }
2、RoleServicey以及实现类
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时20分48秒
- * @Version 1.0
- * @Description:
- */
- public interface IRoleService<T> {
-
- Set<String> getPermissionByUserName(String username);
-
- ResponseData queryRoleList();
- }
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 11时21分02秒
- * @Version 1.0
- * @Description:
- */
- @Service
- @Slf4j
- public class RoleService implements IRoleService {
-
- @Resource
- private TRoleMapper roleMapper;
-
- /**
- * 根据用户查询用户权限(权限认证时调用)
- * @param username
- * @return
- */
- @Override
- public Set<String> getPermissionByUserName(String username) {
- List<String> permissions = roleMapper.getPermissionByUserName(username);
- log.info("权限鉴别===>通过用户名获取用户权限信息:" + permissions.toString());
- return new HashSet<String>(permissions);
- }
-
- /**
- * 查询全部角色信息
- * @return
- */
- @Override
- public ResponseData queryRoleList() {
- List<TRole> roles = roleMapper.selectAll();
- if (!roles.isEmpty()){
- return ResponseDataUtil.success("查询成功!", roles);
- }
- return ResponseDataUtil.fail("暂无角色数据!");
- }
- }
1、RedirectController(重定向访问)
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 17时36分46秒
- * @Version 1.0
- * @Description:
- */
- @Slf4j
- @RestController
- @CrossOrigin
- public class RedirectController {
- /**
- * 暂无token
- * @return
- */
- @GetMapping("/invalid")
- public Object redirect401(){
- return ResponseDataUtil.fail("token已失效或为空,请先登录!");
- }
-
- /**
- * 权限不足
- * @return
- */
- @GetMapping("/noaccess")
- public Object redirect403(){
- return ResponseDataUtil.fail("对不起,无权限访问!");
- }
-
- }
2、LoginController
- @Slf4j
- @RestController
- @CrossOrigin
- public class LoginController {
-
- @Resource
- private IUserService userService;
-
- /**
- * 登录
- * @param username
- * @param password
- * @param response
- * @return
- */
- @PostMapping("/login")
- public Object login(@RequestParam("username")String username, @RequestParam("password")String password, HttpServletResponse response){
- //用户名和密码非空判断
- if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)){
- return userService.login(username, password, response);
- } else {
- log.info("******");
- return ResponseDataUtil.fail("登录失败,用户名或密码为空!");
-
- }
- }
- }
3、UserController
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 17时41分59秒
- * @Version 1.0
- * @Description:
- */
- @Slf4j
- @RestController
- @CrossOrigin
- @RequestMapping("/api")
- public class UserController {
-
- @Resource
- private IUserService userService;
-
- /**
- * 退出登录
- * @param request
- * @return
- */
- @GetMapping("/logout")
- public Object logout(HttpServletRequest request){
- return userService.logout(request);
- }
-
- /**
- * 查询所有用户
- * @return
- */
- @GetMapping("/user/list")
- public Object queryUserList(){
- return userService.queryUserList();
- }
-
- /**
- * 查询所有用户z
- * @return
- */
- @GetMapping("/user/username")
- @RequiresPermissions(value={"3","1"},logical = Logical.OR) //权限限制
- public Object queryUserByUserName(@RequestParam("username") String username){
- return userService.queryUserByUserName(username);
- }
- }
4、RoleController
- /**
- * @Author: LEAVES
- * @Date: 2020年12月30日 17时59分39秒
- * @Version 1.0
- * @Description:
- */
- @Slf4j
- @RestController
- @CrossOrigin
- @RequestMapping("/api")
- public class RoleController {
-
- @Resource
- private IRoleService roleService;
-
- /**
- * 查询全部角色信息
- * @return
- */
- @GetMapping("/role/list")
- public Object queryAll(){
- return roleService.queryRoleList();
- }
- }
1、登录生成token
2、带上刚刚生成的token的访问
3、不带token访问
4、权限测试
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。