From cf3655adf49d6e941cfc3237c99eddfbd76a6f59 Mon Sep 17 00:00:00 2001 From: Zheng Jie <201507802@qq.com> Date: Tue, 4 Jul 2023 22:30:30 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=8C=E8=B8=A2=E5=87=BA=E7=94=A8=E6=88=B7=E6=80=A7?= =?UTF-8?q?=E8=83=BD=E4=BC=98=E5=8C=96=EF=BC=8C=E5=9C=A8=E7=BA=BF=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=9F=A5=E8=AF=A2=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20close=20https://github.com/elunez/eladmin/issues/802?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/me/zhengjie/utils/RedisUtils.java | 20 ++++- .../security/config/bean/LoginProperties.java | 2 +- .../rest/AuthorizationController.java | 9 +- .../security/rest/OnlineController.java | 14 +-- .../security/security/TokenFilter.java | 3 +- .../security/security/TokenProvider.java | 12 +++ .../security/service/OnlineUserService.java | 85 +++++-------------- .../security/service/UserCacheManager.java | 6 +- .../main/resources/config/application-dev.yml | 6 +- .../resources/config/application-prod.yml | 6 +- 10 files changed, 73 insertions(+), 90 deletions(-) diff --git a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java index 9ffbe5ca..65d02789 100644 --- a/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java +++ b/eladmin-common/src/main/java/me/zhengjie/utils/RedisUtils.java @@ -19,13 +19,11 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.data.redis.serializer.StringRedisSerializer; import org.springframework.stereotype.Component; - import java.util.*; import java.util.concurrent.TimeUnit; @@ -36,9 +34,8 @@ import java.util.concurrent.TimeUnit; @SuppressWarnings({"unchecked", "all"}) public class RedisUtils { private static final Logger log = LoggerFactory.getLogger(RedisUtils.class); + private RedisTemplate redisTemplate; - @Value("${jwt.online-key}") - private String onlineKey; public RedisUtils(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; @@ -197,6 +194,21 @@ public class RedisUtils { } } + /** + * 批量模糊删除key + * @param pattern + */ + public void scanDel(String pattern){ + ScanOptions options = ScanOptions.scanOptions().match(pattern).build(); + try (Cursor cursor = redisTemplate.executeWithStickyConnection( + (RedisCallback>) connection -> (Cursor) new ConvertingCursor<>( + connection.scan(options), redisTemplate.getKeySerializer()::deserialize))) { + while (cursor.hasNext()) { + redisTemplate.delete(cursor.next()); + } + } + } + // ============================String============================= /** diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java index 164c0073..0201a13d 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/config/bean/LoginProperties.java @@ -39,7 +39,7 @@ public class LoginProperties { private LoginCode loginCode; - public static final String cacheKey = "USER-LOGIN-DATA"; + public static final String cacheKey = "user-login-cache:"; public boolean isSingleLogin() { return singleLogin; diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthorizationController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthorizationController.java index a03e1c90..effb15f5 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthorizationController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/AuthorizationController.java @@ -98,17 +98,18 @@ public class AuthorizationController { // SecurityContextHolder.getContext().setAuthentication(authentication); String token = tokenProvider.createToken(authentication); final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal(); - // 保存在线信息 - onlineUserService.save(jwtUserDto, token, request); // 返回 token 与 用户信息 Map authInfo = new HashMap(2) {{ put("token", properties.getTokenStartWith() + token); put("user", jwtUserDto); }}; if (loginProperties.isSingleLogin()) { - //踢掉之前已经登录的token - onlineUserService.checkLoginOnUser(authUser.getUsername(), token); + // 踢掉之前已经登录的token + onlineUserService.kickOutForUsername(authUser.getUsername()); } + // 保存在线信息 + onlineUserService.save(jwtUserDto, token, request); + // 返回登录信息 return ResponseEntity.ok(authInfo); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java index cc5968cc..2e46859d 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/rest/OnlineController.java @@ -45,25 +45,25 @@ public class OnlineController { @ApiOperation("查询在线用户") @GetMapping @PreAuthorize("@el.check()") - public ResponseEntity> queryOnlineUser(String filter, Pageable pageable){ - return new ResponseEntity<>(onlineUserService.getAll(filter, pageable),HttpStatus.OK); + public ResponseEntity> queryOnlineUser(String username, Pageable pageable){ + return new ResponseEntity<>(onlineUserService.getAll(username, pageable),HttpStatus.OK); } @ApiOperation("导出数据") @GetMapping(value = "/download") @PreAuthorize("@el.check()") - public void exportOnlineUser(HttpServletResponse response, String filter) throws IOException { - onlineUserService.download(onlineUserService.getAll(filter), response); + public void exportOnlineUser(HttpServletResponse response, String username) throws IOException { + onlineUserService.download(onlineUserService.getAll(username), response); } @ApiOperation("踢出用户") @DeleteMapping @PreAuthorize("@el.check()") public ResponseEntity deleteOnlineUser(@RequestBody Set keys) throws Exception { - for (String key : keys) { + for (String token : keys) { // 解密Key - key = EncryptUtils.desDecrypt(key); - onlineUserService.kickOut(key); + token = EncryptUtils.desDecrypt(token); + onlineUserService.logout(token); } return new ResponseEntity<>(HttpStatus.OK); } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java index d235cb81..7cb8a03f 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenFilter.java @@ -70,7 +70,8 @@ public class TokenFilter extends GenericFilterBean { OnlineUserDto onlineUserDto = null; boolean cleanUserCache = false; try { - onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token); + String loginKey = tokenProvider.loginKey(token); + onlineUserDto = onlineUserService.getOne(loginKey); } catch (ExpiredJwtException e) { log.error(e.getMessage()); cleanUserCache = true; diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java index db98fba4..d2db83c0 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/security/TokenProvider.java @@ -18,6 +18,7 @@ package me.zhengjie.modules.security.security; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.crypto.digest.DigestUtil; import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; @@ -120,4 +121,15 @@ public class TokenProvider implements InitializingBean { } return null; } + + /** + * 获取登录用户RedisKey + * @param token / + * @return key + */ + public String loginKey(String token) { + Claims claims = getClaims(token); + String md5Token = DigestUtil.md5Hex(token); + return properties.getOnlineKey() + claims.getSubject() + "-" + md5Token; + } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java index b9ac48f8..ede78681 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/OnlineUserService.java @@ -15,7 +15,9 @@ */ package me.zhengjie.modules.security.service; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import me.zhengjie.modules.security.security.TokenProvider; import me.zhengjie.utils.PageResult; import me.zhengjie.modules.security.config.bean.SecurityProperties; import me.zhengjie.modules.security.service.dto.JwtUserDto; @@ -28,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.*; +import java.util.concurrent.TimeUnit; /** * @author Zheng Jie @@ -35,16 +38,13 @@ import java.util.*; */ @Service @Slf4j +@AllArgsConstructor public class OnlineUserService { private final SecurityProperties properties; + private final TokenProvider tokenProvider; private final RedisUtils redisUtils; - public OnlineUserService(SecurityProperties properties, RedisUtils redisUtils) { - this.properties = properties; - this.redisUtils = redisUtils; - } - /** * 保存在线用户信息 * @param jwtUserDto / @@ -62,17 +62,18 @@ public class OnlineUserService { } catch (Exception e) { log.error(e.getMessage(),e); } - redisUtils.set(properties.getOnlineKey() + token, onlineUserDto, properties.getTokenValidityInSeconds()/1000); + String loginKey = tokenProvider.loginKey(token); + redisUtils.set(loginKey, onlineUserDto, properties.getTokenValidityInSeconds(), TimeUnit.MILLISECONDS); } /** * 查询全部数据 - * @param filter / + * @param username / * @param pageable / * @return / */ - public PageResult getAll(String filter, Pageable pageable){ - List onlineUserDtos = getAll(filter); + public PageResult getAll(String username, Pageable pageable){ + List onlineUserDtos = getAll(username); return PageUtil.toPage( PageUtil.paging(pageable.getPageNumber(),pageable.getPageSize(), onlineUserDtos), onlineUserDtos.size() @@ -81,43 +82,29 @@ public class OnlineUserService { /** * 查询全部数据,不分页 - * @param filter / + * @param username / * @return / */ - public List getAll(String filter){ - List keys = redisUtils.scan(properties.getOnlineKey() + "*"); + public List getAll(String username){ + String loginKey = properties.getOnlineKey() + + (StringUtils.isBlank(username) ? "" : "*" + username); + List keys = redisUtils.scan(loginKey + "*"); Collections.reverse(keys); List onlineUserDtos = new ArrayList<>(); for (String key : keys) { - OnlineUserDto onlineUserDto = (OnlineUserDto) redisUtils.get(key); - if(StringUtils.isNotBlank(filter)){ - if(onlineUserDto.toString().contains(filter)){ - onlineUserDtos.add(onlineUserDto); - } - } else { - onlineUserDtos.add(onlineUserDto); - } + onlineUserDtos.add((OnlineUserDto) redisUtils.get(key)); } onlineUserDtos.sort((o1, o2) -> o2.getLoginTime().compareTo(o1.getLoginTime())); return onlineUserDtos; } - /** - * 踢出用户 - * @param key / - */ - public void kickOut(String key){ - key = properties.getOnlineKey() + key; - redisUtils.del(key); - } - /** * 退出登录 * @param token / */ public void logout(String token) { - String key = properties.getOnlineKey() + token; - redisUtils.del(key); + String loginKey = tokenProvider.loginKey(token); + redisUtils.del(loginKey); } /** @@ -150,43 +137,13 @@ public class OnlineUserService { return (OnlineUserDto)redisUtils.get(key); } - /** - * 检测用户是否在之前已经登录,已经登录踢下线 - * @param userName 用户名 - */ - public void checkLoginOnUser(String userName, String igoreToken){ - List onlineUserDtos = getAll(userName); - if(onlineUserDtos ==null || onlineUserDtos.isEmpty()){ - return; - } - for(OnlineUserDto onlineUserDto : onlineUserDtos){ - if(onlineUserDto.getUserName().equals(userName)){ - try { - String token =EncryptUtils.desDecrypt(onlineUserDto.getKey()); - if(StringUtils.isNotBlank(igoreToken)&&!igoreToken.equals(token)){ - this.kickOut(token); - }else if(StringUtils.isBlank(igoreToken)){ - this.kickOut(token); - } - } catch (Exception e) { - log.error("checkUser is error",e); - } - } - } - } - /** * 根据用户名强退用户 * @param username / */ @Async - public void kickOutForUsername(String username) throws Exception { - List onlineUsers = getAll(username); - for (OnlineUserDto onlineUser : onlineUsers) { - if (onlineUser.getUserName().equals(username)) { - String token =EncryptUtils.desDecrypt(onlineUser.getKey()); - kickOut(token); - } - } + public void kickOutForUsername(String username) { + String loginKey = properties.getOnlineKey() + username + "*"; + redisUtils.scanDel(loginKey); } } diff --git a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java index 3a8afe72..0808e65d 100644 --- a/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java +++ b/eladmin-system/src/main/java/me/zhengjie/modules/security/service/UserCacheManager.java @@ -46,7 +46,7 @@ public class UserCacheManager { public JwtUserDto getUserCache(String userName) { if (StringUtils.isNotEmpty(userName)) { // 获取数据 - Object obj = redisUtils.hget(LoginProperties.cacheKey, userName); + Object obj = redisUtils.get(LoginProperties.cacheKey + userName); if(obj != null){ return (JwtUserDto)obj; } @@ -63,7 +63,7 @@ public class UserCacheManager { if (StringUtils.isNotEmpty(userName)) { // 添加数据, 避免数据同时过期 long time = idleTime + RandomUtil.randomInt(900, 1800); - redisUtils.hset(LoginProperties.cacheKey, userName, user, time); + redisUtils.set(LoginProperties.cacheKey + userName, user, time); } } @@ -76,7 +76,7 @@ public class UserCacheManager { public void cleanUserCache(String userName) { if (StringUtils.isNotEmpty(userName)) { // 清除数据 - redisUtils.hdel(LoginProperties.cacheKey, userName); + redisUtils.del(LoginProperties.cacheKey + userName); } } } \ No newline at end of file diff --git a/eladmin-system/src/main/resources/config/application-dev.yml b/eladmin-system/src/main/resources/config/application-dev.yml index 79373aaf..6c5c47bb 100644 --- a/eladmin-system/src/main/resources/config/application-dev.yml +++ b/eladmin-system/src/main/resources/config/application-dev.yml @@ -56,7 +56,7 @@ login: # Redis用户登录缓存配置 user-cache: # 存活时间/秒 - idle-time: 7200 + idle-time: 21600 # 验证码 login-code: # 验证码类型配置 查看 LoginProperties 类 @@ -84,9 +84,9 @@ jwt: # 令牌过期时间 此处单位/毫秒 ,默认4小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html token-validity-in-seconds: 14400000 # 在线用户key - online-key: online-token- + online-key: "online-token:" # 验证码 - code-key: code-key- + code-key: "captcha-code:" # token 续期检查时间范围(默认30分钟,单位毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 detect: 1800000 # 续期时间范围,默认1小时,单位毫秒 diff --git a/eladmin-system/src/main/resources/config/application-prod.yml b/eladmin-system/src/main/resources/config/application-prod.yml index 851b26c4..39d6a11f 100644 --- a/eladmin-system/src/main/resources/config/application-prod.yml +++ b/eladmin-system/src/main/resources/config/application-prod.yml @@ -58,7 +58,7 @@ login: # Redis用户登录缓存配置 user-cache: # 存活时间/秒 - idle-time: 7200 + idle-time: 21600 # 验证码 login-code: # 验证码类型配置 查看 LoginProperties 类 @@ -86,9 +86,9 @@ jwt: # 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html token-validity-in-seconds: 7200000 # 在线用户key - online-key: online-token- + online-key: "online-token:" # 验证码 - code-key: code-key- + code-key: "captcha-code:" # token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期 detect: 1800000 # 续期时间范围,默认 1小时,这里单位毫秒