码云源码地址: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 |
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.3.7.RELEASE
com.leaves.auth
rbac-demo
0.0.1-SNAPSHOT
rbac-demo
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-jdbc
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.4
org.postgresql
postgresql
runtime
org.projectlombok
lombok
true
com.alibaba
druid
1.1.21
redis.clients
jedis
2.9.0
org.crazycake
shiro-redis
3.2.3
org.apache.shiro
shiro-spring
1.3.2
org.apache.shiro
shiro-ehcache
1.2.5
com.auth0
java-jwt
3.5.0
org.slf4j
slf4j-api
1.7.25
commons-fileupload
commons-fileupload
1.3.1
commons-io
commons-io
1.4
tk.mybatis
mapper-spring-boot-starter
2.1.5
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.5
cn.hutool
hutool-all
5.4.3
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
RBAC-DEMO
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
#端口号
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
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Set 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
* @Author: LEAVES
* @Date: 2020年11月02日 15时22分18秒
*/
public static Set 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 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
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 {
/**
* 统一返回码
*/
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 ResponseData success(Integer code, String msg){
ResponseData responseData = new ResponseData();
responseData.setCode(code);
responseData.setMsg(msg);
return responseData;
}
/**
* 返回成功的描述 状态码、说明、数据
* @param msg
* @param data
* @param
* @return
*/
public static 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
* @return
*/
public static 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
* @return
*/
public static 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 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 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 permissions = roleService.getPermissionByUserName(username);
// String role = userService.getRole(username);
//调用业务方法获取用户权限
// List list = roleService.getPermissionsByUsername(username);
//每个用户可以设置新的权限
// String permission = userService.getPermission(username);
//将List换成set去掉重复权限
// Set stringPermissions = new HashSet<>();
// Set 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 filterChainDefinitionMap = new LinkedHashMap();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 添加自己的过滤器并且取名为jwt
Map filterMap = new HashMap(1);
filterMap.put("jwt", new JwtFilter());
log.info("ShiroConfig==shiroFilter--->new JwtFilter() : " + new JwtFilter());
shiroFilterFactoryBean.setFilters(filterMap);
//");
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 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 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 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 {
Set 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 getPermissionByUserName(String username) {
List permissions = roleMapper.getPermissionByUserName(username);
log.info("权限鉴别===>通过用户名获取用户权限信息:" + permissions.toString());
return new HashSet(permissions);
}
/**
* 查询全部角色信息
* @return
*/
@Override
public ResponseData queryRoleList() {
List 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、权限测试
码云源码地址:https://gitee.com/loveleaveslove/rbac-demo.git。