Commit 284c25a1 authored by dqjdda's avatar dqjdda
Browse files

Merge branch '2.4dev' into 2.4opt

parents 175a2eb6 ccca30fc
......@@ -61,10 +61,8 @@ public @interface Query {
* 适用于简单连接查询,复杂的请自定义该注解,或者使用sql查询
*/
enum Join {
/** jie 2019-6-4 13:18:30 左连接 */
LEFT
/** jie 2019-6-4 13:18:30 右连接 */
, RIGHT
/** jie 2019-6-4 13:18:30 左右连接 */
LEFT, RIGHT
}
}
......
......@@ -14,11 +14,6 @@ import java.util.stream.Collectors;
public class ElPermissionConfig {
public Boolean check(String ...permissions){
// 如果是匿名访问的,就放行
String anonymous = "anonymous";
if(Arrays.asList(permissions).contains(anonymous)){
return true;
}
// 获取当前用户的所有权限
List<String> elPermissions = SecurityUtils.getUserDetails().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// 判断当前用户的所有权限是否包含接口上定义的权限
......
......@@ -39,6 +39,9 @@ public class SwaggerConfig {
@Value("${jwt.header}")
private String tokenHeader;
@Value("${jwt.token-start-with}")
private String tokenStartWith;
@Value("${swagger.enabled}")
private Boolean enabled;
......@@ -50,7 +53,7 @@ public class SwaggerConfig {
ticketPar.name(tokenHeader).description("token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.defaultValue("Bearer ")
.defaultValue(tokenStartWith + " ")
.required(true)
.build();
pars.add(ticketPar.build());
......
......@@ -32,16 +32,6 @@ public class GlobalExceptionHandler {
return buildResponseEntity(ApiError.error(e.getMessage()));
}
/**
* 处理 接口无权访问异常AccessDeniedException
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity handleAccessDeniedException(AccessDeniedException e){
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
return buildResponseEntity(ApiError.error(FORBIDDEN.value(),e.getMessage()));
}
/**
* 处理自定义异常
*/
......
package me.zhengjie.utils;
import org.springframework.util.DigestUtils;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
......@@ -82,11 +81,4 @@ public class EncryptUtils {
}
return b2;
}
/**
* 密码加密
*/
public static String encryptPassword(String password){
return DigestUtils.md5DigestAsHex(password.getBytes());
}
}
......@@ -16,7 +16,6 @@ public class PageUtil extends cn.hutool.core.util.PageUtil {
public static List toPage(int page, int size , List list) {
int fromIndex = page * size;
int toIndex = page * size + size;
if(fromIndex > list.size()){
return new ArrayList();
} else if(toIndex >= list.size()) {
......@@ -43,7 +42,6 @@ public class PageUtil extends cn.hutool.core.util.PageUtil {
Map<String,Object> map = new LinkedHashMap<>(2);
map.put("content",object);
map.put("totalElements",totalElements);
return map;
}
......
package me.zhengjie.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* @author /
*/
@Component
@SuppressWarnings({"unchecked","all"})
public class RedisUtils {
private RedisTemplate<Object, Object> redisTemplate;
@Value("${jwt.online-key}")
private String onlineKey;
public RedisUtils(RedisTemplate<Object, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据 key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(Object key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 查找匹配key
* @param pattern key
* @return /
*/
public List<String> scan(String pattern) {
ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection rc = Objects.requireNonNull(factory).getConnection();
Cursor<byte[]> cursor = rc.scan(options);
List<String> result = new ArrayList<>();
while (cursor.hasNext()) {
result.add(new String(cursor.next()));
}
try {
RedisConnectionUtils.releaseConnection(rc, factory);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 分页查询 key
* @param patternKey key
* @param page 页码
* @param size 每页数目
* @return /
*/
public List<String> findKeysForPage(String patternKey, int page, int size) {
ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection rc = Objects.requireNonNull(factory).getConnection();
Cursor<byte[]> cursor = rc.scan(options);
List<String> result = new ArrayList<>(size);
int tmpIndex = 0;
int fromIndex = page * size;
int toIndex = page * size + size;
while (cursor.hasNext()) {
if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
result.add(new String(cursor.next()));
tmpIndex++;
continue;
}
// 获取到满足条件的数据后,就可以退出了
if(tmpIndex >=toIndex) {
break;
}
tmpIndex++;
cursor.next();
}
try {
RedisConnectionUtils.releaseConnection(rc, factory);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 批量获取
* @param keys
* @return
*/
public List<Object> multiGet(List<String> keys) {
Object obj = redisTemplate.opsForValue().multiGet(Collections.singleton(keys));
return null;
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间
* @param timeUnit 类型
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return /
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
......@@ -3,6 +3,7 @@ package me.zhengjie.utils;
import cn.hutool.json.JSONObject;
import me.zhengjie.exception.BadRequestException;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
/**
......@@ -15,7 +16,7 @@ public class SecurityUtils {
public static UserDetails getUserDetails() {
UserDetails userDetails;
try {
userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal();
userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (Exception e) {
throw new BadRequestException(HttpStatus.UNAUTHORIZED, "登录状态过期");
}
......@@ -30,13 +31,4 @@ public class SecurityUtils {
Object obj = getUserDetails();
return new JSONObject(obj).get("username", String.class);
}
/**
* 获取系统用户id
* @return 系统用户id
*/
public static Long getUserId(){
Object obj = getUserDetails();
return new JSONObject(obj).get("id", Long.class);
}
}
......@@ -2,6 +2,7 @@ package me.zhengjie.utils;
import cn.hutool.core.util.ObjectUtil;
import me.zhengjie.exception.BadRequestException;
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
/**
* 验证工具
......@@ -23,11 +24,7 @@ public class ValidationUtil{
/**
* 验证是否为邮箱
*/
public static boolean isEmail(String string) {
if (string == null){
return false;
}
String regEx1 = "^([a-z0-9A-Z]+[-|.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$";
return string.matches(regEx1);
public static boolean isEmail(String email) {
return new EmailValidator().isValid(email, null);
}
}
......@@ -13,7 +13,7 @@
<name>核心模块</name>
<properties>
<jjwt.version>0.9.1</jjwt.version>
<jjwt.version>0.10.6</jjwt.version>
</properties>
<dependencies>
......@@ -45,7 +45,17 @@
<!--jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>
......
package me.zhengjie.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.nio.file.Paths;
/**
* WebMvcConfigurer
......@@ -24,20 +26,22 @@ public class ConfigurerAdapter implements WebMvcConfigurer {
@Value("${file.avatar}")
private String avatar;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedHeaders("*")
.allowedOrigins("*")
.allowedMethods("GET","POST","PUT","DELETE");
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
String avatarUtl = Paths.get(avatar).normalize().toUri().toASCIIString();
String pathUtl = Paths.get(path).normalize().toUri().toASCIIString();
String avatarUtl = "file:" + avatar.replace("\\","/");
String pathUtl = "file:" + path.replace("\\","/");
registry.addResourceHandler("/avatar/**").addResourceLocations(avatarUtl).setCachePeriod(0);
registry.addResourceHandler("/file/**").addResourceLocations(pathUtl).setCachePeriod(0);
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/").setCachePeriod(0);
......
......@@ -4,11 +4,9 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.annotation.Limit;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.atomic.AtomicInteger;
/**
......@@ -26,7 +24,7 @@ public class LimitController {
* 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test,
*/
@GetMapping
@PreAuthorize("@el.check('anonymous')")
@AnonymousAccess
@ApiOperation("测试")
@Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")
public int testLimit() {
......
package me.zhengjie.modules.monitor.rest;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.zhengjie.aop.log.Log;
import me.zhengjie.modules.monitor.domain.vo.RedisVo;
import me.zhengjie.modules.monitor.service.RedisService;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Zheng Jie
* @date 2018-12-10
*/
@RestController
@RequestMapping("/api/redis")
@Api(tags = "系统:Redis缓存管理")
public class RedisController {
private final RedisService redisService;
public RedisController(RedisService redisService) {
this.redisService = redisService;
}
@Log("查询Redis缓存")
@GetMapping
@ApiOperation("查询Redis缓存")
@PreAuthorize("@el.check('redis:list')")
public ResponseEntity getRedis(String key, Pageable pageable){
return new ResponseEntity<>(redisService.findByKey(key,pageable), HttpStatus.OK);
}
@Log("导出数据")
@ApiOperation("导出数据")
@GetMapping(value = "/download")
@PreAuthorize("@el.check('redis:list')")
public void download(HttpServletResponse response, String key) throws IOException {
redisService.download(redisService.findByKey(key), response);
}
@Log("删除Redis缓存")
@DeleteMapping
@ApiOperation("删除Redis缓存")
@PreAuthorize("@el.check('redis:del')")
public ResponseEntity delete(@RequestBody RedisVo resources){
redisService.delete(resources.getKey());
return new ResponseEntity(HttpStatus.OK);
}
@Log("清空Redis缓存")
@DeleteMapping(value = "/all")
@ApiOperation("清空Redis缓存")
@PreAuthorize("@el.check('redis:del')")
public ResponseEntity deleteAll(){
redisService.deleteAll();
return new ResponseEntity(HttpStatus.OK);
}
}
package me.zhengjie.modules.monitor.service;
import me.zhengjie.modules.monitor.domain.vo.RedisVo;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
/**
* 可自行扩展
* @author Zheng Jie
* @date 2018-12-10
*/
public interface RedisService {
/**
* 根据KEY查询
* @param key 键
* @param pageable 分页参数
* @return /
*/
Page findByKey(String key, Pageable pageable);
/**
* findById
* @param key 键
* @return /
*/
List<RedisVo> findByKey(String key);
/**
* 查询验证码的值
* @param key 键
* @return /
*/
String getCodeVal(String key);
/**
* 保存验证码
* @param key 键
* @param val 值
*/
void saveCode(String key, Object val);
/**
* delete
* @param key 键
*/
void delete(String key);
/**
* 清空缓存
*/
void deleteAll();
/**
* 导出数据
* @param redisVos /
* @param response /
* @throws IOException /
*/
void download(List<RedisVo> redisVos, HttpServletResponse response) throws IOException;
}
package me.zhengjie.modules.monitor.service.impl;
import cn.hutool.core.util.ObjectUtil;
import me.zhengjie.modules.monitor.domain.vo.RedisVo;
import me.zhengjie.modules.monitor.service.RedisService;
import me.zhengjie.utils.FileUtil;
import me.zhengjie.utils.PageUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @author Zheng Jie
* @date 2018-12-10
*/
@Service
@SuppressWarnings({"unchecked","all"})
public class RedisServiceImpl implements RedisService {
private final RedisTemplate redisTemplate;
@Value("${loginCode.expiration}")
private Long expiration;
@Value("${jwt.online}")
private String onlineKey;
@Value("${jwt.codeKey}")
private String codeKey;
public RedisServiceImpl(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public Page<RedisVo> findByKey(String key, Pageable pageable){
List<RedisVo> redisVos = findByKey(key);
return new PageImpl<RedisVo>(
PageUtil.toPage(pageable.getPageNumber(),pageable.getPageSize(),redisVos),
pageable,
redisVos.size());
}
@Override
public List<RedisVo> findByKey(String key) {
List<RedisVo> redisVos = new ArrayList<>();
if(!"*".equals(key)){
key = "*" + key + "*";
}
Set<String> keys = redisTemplate.keys(key);
for (String s : keys) {
// 过滤掉权限的缓存
if (s.contains("role::loadPermissionByUser") || s.contains("user::loadUserByUsername") || s.contains(onlineKey) || s.contains(codeKey)) {
continue;
}
RedisVo redisVo = new RedisVo(s, Objects.requireNonNull(redisTemplate.opsForValue().get(s)).toString());
redisVos.add(redisVo);
}
return redisVos;
}
@Override
public void delete(String key) {
redisTemplate.delete(key);
}
@Override
public void deleteAll() {
Set<String> keys = redisTemplate.keys( "*");
redisTemplate.delete(keys.stream().filter(s -> !s.contains(onlineKey)).filter(s -> !s.contains(codeKey)).collect(Collectors.toList()));
}
@Override
public String getCodeVal(String key) {
try {
return Objects.requireNonNull(redisTemplate.opsForValue().get(key)).toString();
}catch (Exception e){
return "";
}
}
@Override
public void saveCode(String key, Object val) {
redisTemplate.opsForValue().set(key,val);
redisTemplate.expire(key,expiration, TimeUnit.MINUTES);
}
@Override
public void download(List<RedisVo> redisVos, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (RedisVo redisVo : redisVos) {
Map<String,Object> map = new LinkedHashMap<>();
map.put("key", redisVo.getKey());
map.put("value", redisVo.getValue());
list.add(map);
}
FileUtil.downloadExcel(list, response);
}
}
package me.zhengjie.modules.security.config;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.modules.security.security.JwtAuthenticationEntryPoint;
import me.zhengjie.modules.security.security.JwtAuthorizationTokenFilter;
import me.zhengjie.modules.security.service.JwtUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import me.zhengjie.modules.security.security.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
......@@ -22,6 +15,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
......@@ -34,103 +28,98 @@ import java.util.Set;
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint unauthorizedHandler;
private final JwtUserDetailsServiceImpl jwtUserDetailsService;
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
/** 自定义基于JWT的安全过滤器 */
private final JwtAuthorizationTokenFilter authenticationTokenFilter;
@Value("${jwt.header}")
private String tokenHeader;
public SecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtUserDetailsServiceImpl jwtUserDetailsService, JwtAuthorizationTokenFilter authenticationTokenFilter, ApplicationContext applicationContext) {
this.unauthorizedHandler = unauthorizedHandler;
this.jwtUserDetailsService = jwtUserDetailsService;
this.authenticationTokenFilter = authenticationTokenFilter;
public SecurityConfig(TokenProvider tokenProvider, CorsFilter corsFilter, JwtAuthenticationEntryPoint authenticationErrorHandler, JwtAccessDeniedHandler jwtAccessDeniedHandler, ApplicationContext applicationContext) {
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.authenticationErrorHandler = authenticationErrorHandler;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
this.applicationContext = applicationContext;
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(jwtUserDetailsService)
.passwordEncoder(passwordEncoderBean());
}
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
// Remove the ROLE_ prefix
// 去除 ROLE_ 前缀
return new GrantedAuthorityDefaults("");
}
@Bean
public PasswordEncoder passwordEncoderBean() {
public PasswordEncoder passwordEncoder() {
// 密码加密方式
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 搜寻 匿名标记 url: PreAuthorize("hasAnyRole('anonymous')") 和 PreAuthorize("@el.check('anonymous')") 和 AnonymousAccess
// 搜寻匿名标记 url: @AnonymousAccess
Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Set<String> anonymousUrls = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
PreAuthorize preAuthorize = handlerMethod.getMethodAnnotation(PreAuthorize.class);
if (null != preAuthorize && preAuthorize.value().toLowerCase().contains("anonymous")) {
anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
} else if (null != anonymousAccess && null == preAuthorize) {
if (null != anonymousAccess) {
anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
}
}
httpSecurity
// 禁用 CSRF
.csrf().disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
// 授权异常
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.exceptionHandling()
.authenticationEntryPoint(authenticationErrorHandler)
.accessDeniedHandler(jwtAccessDeniedHandler)
// 防止iframe 造成跨域
.and()
.headers()
.frameOptions()
.disable()
// 不创建会话
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 静态资源等等
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/webSocket/**"
).anonymous()
// swagger start
"/webSocket/**"
).permitAll()
// swagger 文档
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/*/api-docs").permitAll()
// swagger end
// 文件
.antMatchers("/avatar/**").permitAll()
.antMatchers("/file/**").permitAll()
// 阿里巴巴 druid
.antMatchers("/druid/**").permitAll()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/druid/**").permitAll()
// 自定义匿名访问所有url放行 : 允许 匿名和带权限以及登录用户访问
// 自定义匿名访问所有url放行 : 允许匿名和带权限以及登录用户访问
.antMatchers(anonymousUrls.toArray(new String[0])).permitAll()
// 所有请求都需要认证
.anyRequest().authenticated()
// 防止iframe 造成跨域
.and().headers().frameOptions().disable();
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
.and().apply(securityConfigurerAdapter());
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider);
}
}
package me.zhengjie.modules.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* Jwt参数配置
* @author Zheng Jie
* @date 2019年11月28日
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class SecurityProperties {
/** Request Headers : Authorization */
private String header;
/** 令牌前缀,最后留个空格 Bearer */
private String tokenStartWith;
/** 必须使用最少88位的Base64对该令牌进行编码 */
private String base64Secret;
/** 令牌过期时间 此处单位/毫秒 */
private Long tokenValidityInSeconds;
/** 在线用户 key,根据 key 查询 redis 中在线用户的数据 */
private String onlineKey;
/** 验证码 key */
private String codeKey;
public String getTokenStartWith() {
return tokenStartWith + " ";
}
}
......@@ -8,25 +8,28 @@ import lombok.extern.slf4j.Slf4j;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.aop.log.Log;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.monitor.service.RedisService;
import me.zhengjie.modules.security.security.AuthInfo;
import me.zhengjie.modules.security.security.AuthUser;
import me.zhengjie.modules.security.security.ImgResult;
import me.zhengjie.modules.security.security.JwtUser;
import me.zhengjie.modules.security.config.SecurityProperties;
import me.zhengjie.modules.security.security.TokenProvider;
import me.zhengjie.modules.security.security.vo.AuthUser;
import me.zhengjie.modules.security.security.vo.JwtUser;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.utils.EncryptUtils;
import me.zhengjie.modules.security.utils.JwtTokenUtil;
import me.zhengjie.utils.RedisUtils;
import me.zhengjie.utils.SecurityUtils;
import me.zhengjie.utils.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author Zheng Jie
......@@ -37,24 +40,24 @@ import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/auth")
@Api(tags = "系统:系统授权接口")
public class AuthenticationController {
@Value("${jwt.codeKey}")
private String codeKey;
private final JwtTokenUtil jwtTokenUtil;
private final RedisService redisService;
public class AuthController {
@Value("${loginCode.expiration}")
private Long expiration;
private final SecurityProperties properties;
private final RedisUtils redisUtils;
private final UserDetailsService userDetailsService;
private final OnlineUserService onlineUserService;
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthenticationController(JwtTokenUtil jwtTokenUtil, RedisService redisService, @Qualifier("jwtUserDetailsServiceImpl") UserDetailsService userDetailsService, OnlineUserService onlineUserService) {
this.jwtTokenUtil = jwtTokenUtil;
this.redisService = redisService;
public AuthController(SecurityProperties properties, RedisUtils redisUtils, UserDetailsService userDetailsService, OnlineUserService onlineUserService, TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.properties = properties;
this.redisUtils = redisUtils;
this.userDetailsService = userDetailsService;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@Log("用户登录")
......@@ -62,32 +65,32 @@ public class AuthenticationController {
@AnonymousAccess
@PostMapping(value = "/login")
public ResponseEntity login(@Validated @RequestBody AuthUser authUser, HttpServletRequest request){
// 查询验证码
String code = redisService.getCodeVal(authUser.getUuid());
String code = (String) redisUtils.get(authUser.getUuid());
// 清除验证码
redisService.delete(authUser.getUuid());
redisUtils.del(authUser.getUuid());
if (StringUtils.isBlank(code)) {
throw new BadRequestException("验证码已过期");
throw new BadRequestException("验证码不存在或已过期");
}
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {
throw new BadRequestException("验证码错误");
}
final JwtUser jwtUser = (JwtUser) userDetailsService.loadUserByUsername(authUser.getUsername());
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), authUser.getPassword());
if(!jwtUser.getPassword().equals(EncryptUtils.encryptPassword(authUser.getPassword()))){
throw new AccountExpiredException("密码错误");
}
if(!jwtUser.isEnabled()){
throw new AccountExpiredException("账号已停用,请联系管理员");
}
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌
final String token = jwtTokenUtil.generateToken(jwtUser);
String token = tokenProvider.createToken(authentication);
final JwtUser jwtUser = (JwtUser) authentication.getPrincipal();
// 保存在线信息
onlineUserService.save(jwtUser, token, request);
// 返回 token
return ResponseEntity.ok(new AuthInfo(token,jwtUser));
// 返回 token 与 用户信息
Map<String,Object> authInfo = new HashMap<String,Object>(2){{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUser);
}};
return ResponseEntity.ok(authInfo);
}
@ApiOperation("获取用户信息")
......@@ -97,26 +100,32 @@ public class AuthenticationController {
return ResponseEntity.ok(jwtUser);
}
@ApiOperation("获取验证码")
@AnonymousAccess
@ApiOperation("获取验证码")
@GetMapping(value = "/code")
public ImgResult getCode(){
public ResponseEntity getCode(){
// 算术类型 https://gitee.com/whvse/EasyCaptcha
ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
// 几位数运算,默认是两位
captcha.setLen(2);
// 获取运算的结果:5
// 获取运算的结果
String result = captcha.text();
String uuid = codeKey + IdUtil.simpleUUID();
redisService.saveCode(uuid,result);
return new ImgResult(captcha.toBase64(),uuid);
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();
// 保存
redisUtils.set(uuid, result, expiration, TimeUnit.MINUTES);
// 验证码信息
Map<String,Object> imgResult = new HashMap<String,Object>(2){{
put("img", captcha.toBase64());
put("uuid", uuid);
}};
return ResponseEntity.ok(imgResult);
}
@ApiOperation("退出登录")
@AnonymousAccess
@DeleteMapping(value = "/logout")
public ResponseEntity logout(HttpServletRequest request){
onlineUserService.logout(jwtTokenUtil.getToken(request));
onlineUserService.logout(tokenProvider.getToken(request));
return new ResponseEntity(HttpStatus.OK);
}
}
package me.zhengjie.modules.security.security;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.Serializable;
/**
* @author Zheng Jie
* @date 2018-11-23
* 返回token
*/
@Getter
@AllArgsConstructor
public class AuthInfo implements Serializable {
private final String token;
private final JwtUser user;
}
package me.zhengjie.modules.security.security;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* @author Zheng Jie
* @date 2019-6-5 17:29:57
*/
@Data
@AllArgsConstructor
public class ImgResult {
private String img;
private String uuid;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment