mirror of https://github.com/elunez/eladmin
[新增功能](master): 用户登录缓存改为存储到 Redis
parent
0e7c4fcbff
commit
78b7d21ddc
|
@ -20,7 +20,7 @@ import me.zhengjie.annotation.AnonymousAccess;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.security.*;
|
import me.zhengjie.modules.security.security.*;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import me.zhengjie.modules.security.service.UserCacheClean;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
import me.zhengjie.utils.enums.RequestMethodEnum;
|
import me.zhengjie.utils.enums.RequestMethodEnum;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -58,7 +58,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final UserCacheClean userCacheClean;
|
private final UserCacheManager userCacheManager;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||||
|
@ -138,7 +138,7 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TokenConfigurer securityConfigurerAdapter() {
|
private TokenConfigurer securityConfigurerAdapter() {
|
||||||
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean);
|
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
|
private Map<String, Set<String>> getAnonymousUrl(Map<RequestMappingInfo, HandlerMethod> handlerMethodMap) {
|
||||||
|
|
|
@ -20,7 +20,6 @@ import com.wf.captcha.base.Captcha;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import me.zhengjie.exception.BadConfigurationException;
|
import me.zhengjie.exception.BadConfigurationException;
|
||||||
import me.zhengjie.utils.StringUtils;
|
import me.zhengjie.utils.StringUtils;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -40,19 +39,12 @@ public class LoginProperties {
|
||||||
|
|
||||||
private LoginCode loginCode;
|
private LoginCode loginCode;
|
||||||
|
|
||||||
/**
|
public static final String cacheKey = "USER-LOGIN-DATA";
|
||||||
* 用户登录信息缓存
|
|
||||||
*/
|
|
||||||
private boolean cacheEnable;
|
|
||||||
|
|
||||||
public boolean isSingleLogin() {
|
public boolean isSingleLogin() {
|
||||||
return singleLogin;
|
return singleLogin;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCacheEnable() {
|
|
||||||
return cacheEnable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取验证码生产类
|
* 获取验证码生产类
|
||||||
*
|
*
|
||||||
|
|
|
@ -18,7 +18,7 @@ package me.zhengjie.modules.security.security;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import me.zhengjie.modules.security.service.UserCacheClean;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||||
|
@ -33,11 +33,11 @@ public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFi
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final UserCacheClean userCacheClean;
|
private final UserCacheManager userCacheManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(HttpSecurity http) {
|
public void configure(HttpSecurity http) {
|
||||||
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheClean);
|
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheManager);
|
||||||
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package me.zhengjie.modules.security.security;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
import me.zhengjie.modules.security.config.bean.SecurityProperties;
|
||||||
import me.zhengjie.modules.security.service.UserCacheClean;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -27,7 +27,6 @@ import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.GenericFilterBean;
|
import org.springframework.web.filter.GenericFilterBean;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
@ -46,19 +45,19 @@ public class TokenFilter extends GenericFilterBean {
|
||||||
private final TokenProvider tokenProvider;
|
private final TokenProvider tokenProvider;
|
||||||
private final SecurityProperties properties;
|
private final SecurityProperties properties;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
private final UserCacheClean userCacheClean;
|
private final UserCacheManager userCacheManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param tokenProvider Token
|
* @param tokenProvider Token
|
||||||
* @param properties JWT
|
* @param properties JWT
|
||||||
* @param onlineUserService 用户在线
|
* @param onlineUserService 用户在线
|
||||||
* @param userCacheClean 用户缓存清理工具
|
* @param userCacheManager 用户缓存工具
|
||||||
*/
|
*/
|
||||||
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheClean userCacheClean) {
|
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheManager userCacheManager) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
this.onlineUserService = onlineUserService;
|
this.onlineUserService = onlineUserService;
|
||||||
this.tokenProvider = tokenProvider;
|
this.tokenProvider = tokenProvider;
|
||||||
this.userCacheClean = userCacheClean;
|
this.userCacheManager = userCacheManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,7 +76,7 @@ public class TokenFilter extends GenericFilterBean {
|
||||||
cleanUserCache = true;
|
cleanUserCache = true;
|
||||||
} finally {
|
} finally {
|
||||||
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
|
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
|
||||||
userCacheClean.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
|
userCacheManager.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (onlineUserDto != null && StringUtils.hasText(token)) {
|
if (onlineUserDto != null && StringUtils.hasText(token)) {
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2019-2020 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package me.zhengjie.modules.security.service;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import me.zhengjie.utils.StringUtils;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author: liaojinlong
|
|
||||||
* @date: 2020/6/11 18:01
|
|
||||||
* @apiNote: 用于清理 用户登录信息缓存,为防止Spring循环依赖与安全考虑 ,单独构成工具类
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class UserCacheClean {
|
|
||||||
|
|
||||||
private final UserCacheManager userCacheManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理特定用户缓存信息<br>
|
|
||||||
* 用户信息变更时
|
|
||||||
*
|
|
||||||
* @param userName /
|
|
||||||
*/
|
|
||||||
public void cleanUserCache(String userName) {
|
|
||||||
if (StringUtils.isNotEmpty(userName)) {
|
|
||||||
userCacheManager.remove(userName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清理所有用户的缓存信息<br>
|
|
||||||
* ,如发生角色授权信息变化,可以简便的全部失效缓存
|
|
||||||
*/
|
|
||||||
public void cleanAll() {
|
|
||||||
userCacheManager.clear();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +1,82 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2020 Zheng Jie
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
package me.zhengjie.modules.security.service;
|
package me.zhengjie.modules.security.service;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import me.zhengjie.modules.security.config.bean.LoginProperties;
|
||||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
|
import me.zhengjie.utils.RedisUtils;
|
||||||
|
import me.zhengjie.utils.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户缓存
|
* @author Zheng Jie
|
||||||
*
|
* @description 用户缓存管理
|
||||||
* @author TikiWong
|
* @date 2022-05-26
|
||||||
* @date 2022/1/27 8:23
|
|
||||||
**/
|
**/
|
||||||
@Slf4j
|
|
||||||
@Component
|
@Component
|
||||||
public class UserCacheManager {
|
public class UserCacheManager {
|
||||||
|
|
||||||
@Value("${user-cache.min-evictable-size}")
|
@Resource
|
||||||
private int minEvictableSize;
|
private RedisUtils redisUtils;
|
||||||
@Value("${user-cache.min-evictable-interval}")
|
@Value("${login.user-cache.idle-time}")
|
||||||
private long minEvictableInterval;
|
private long idleTime;
|
||||||
@Value("${user-cache.min-idle-time}")
|
|
||||||
private long minIdleTime;
|
|
||||||
|
|
||||||
private final Map<String, Node> cache = new ConcurrentHashMap<>();
|
/**
|
||||||
private final AtomicBoolean expelLock = new AtomicBoolean(true);
|
* 返回用户缓存
|
||||||
private long nextMinEvictableTime = 0;
|
* @param userName 用户名
|
||||||
|
* @return JwtUserDto
|
||||||
public Future<JwtUserDto> putIfAbsent(String username, Future<JwtUserDto> ft) {
|
*/
|
||||||
Node tryNode = new Node(ft);
|
public JwtUserDto getUserCache(String userName) {
|
||||||
Node node = cache.putIfAbsent(username, tryNode);
|
if (StringUtils.isNotEmpty(userName)) {
|
||||||
expel();
|
// 获取数据
|
||||||
return nodeToDate(node);
|
Object obj = redisUtils.hget(LoginProperties.cacheKey, userName);
|
||||||
|
if(obj != null){
|
||||||
|
return (JwtUserDto)obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 缓存回收
|
* 添加缓存到Redis
|
||||||
* 为避免超过边界后回收热点数据设置了最小生存时间
|
* @param userName 用户名
|
||||||
* 回收时会保留在最小生存时间内的数据
|
*/
|
||||||
**/
|
@Async
|
||||||
public void expel() {
|
public void addUserCache(String userName, JwtUserDto user) {
|
||||||
long now = System.currentTimeMillis();
|
if (StringUtils.isNotEmpty(userName)) {
|
||||||
if (cache.size() < minEvictableSize ||
|
// 添加数据, 避免数据同时过期
|
||||||
now < nextMinEvictableTime ||
|
long time = idleTime + RandomUtil.randomInt(900, 1800);
|
||||||
!expelLock.compareAndSet(true, false)) {
|
redisUtils.hset(LoginProperties.cacheKey, userName, user, time);
|
||||||
return;
|
|
||||||
}
|
|
||||||
long oldestTime = now;
|
|
||||||
int evictedCount = 0;
|
|
||||||
try {
|
|
||||||
Iterator<Map.Entry<String, Node>> iterator = cache.entrySet().iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
Map.Entry<String, Node> entry = iterator.next();
|
|
||||||
long nodeTime = entry.getValue().getTime();
|
|
||||||
if (nodeTime + minIdleTime < now) {
|
|
||||||
iterator.remove();
|
|
||||||
evictedCount++;
|
|
||||||
}
|
|
||||||
oldestTime = Math.min(oldestTime, nodeTime);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
this.nextMinEvictableTime = Math.max(now + minEvictableInterval, oldestTime);
|
|
||||||
expelLock.set(true);
|
|
||||||
log.info("回收掉【{}】条用户缓存, 剩余缓存数为【{}】,下次可回收时间为【{}】秒后",
|
|
||||||
evictedCount,
|
|
||||||
cache.size(),
|
|
||||||
(this.nextMinEvictableTime - now) / 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Future<JwtUserDto> get(String username) {
|
/**
|
||||||
return nodeToDate(cache.get(username));
|
* 清理用户缓存信息
|
||||||
}
|
* 用户信息变更时
|
||||||
|
* @param userName 用户名
|
||||||
public void clear() {
|
*/
|
||||||
cache.clear();
|
@Async
|
||||||
}
|
public void cleanUserCache(String userName) {
|
||||||
|
if (StringUtils.isNotEmpty(userName)) {
|
||||||
public void remove(String username) {
|
// 清除数据
|
||||||
cache.remove(username);
|
redisUtils.hdel(LoginProperties.cacheKey, userName);
|
||||||
}
|
|
||||||
|
|
||||||
private Future<JwtUserDto> nodeToDate(Node node) {
|
|
||||||
return node == null ? null : node.getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Node {
|
|
||||||
private final Future<JwtUserDto> data;
|
|
||||||
private final long time;
|
|
||||||
|
|
||||||
public Node(Future<JwtUserDto> data) {
|
|
||||||
this.data = data;
|
|
||||||
this.time = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Future<JwtUserDto> getData() {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTime() {
|
|
||||||
return time;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,50 +16,38 @@
|
||||||
package me.zhengjie.modules.security.service;
|
package me.zhengjie.modules.security.service;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.zhengjie.exception.BadRequestException;
|
import me.zhengjie.exception.BadRequestException;
|
||||||
import me.zhengjie.exception.EntityNotFoundException;
|
import me.zhengjie.exception.EntityNotFoundException;
|
||||||
import me.zhengjie.modules.security.config.bean.LoginProperties;
|
|
||||||
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
import me.zhengjie.modules.security.service.dto.JwtUserDto;
|
||||||
import me.zhengjie.modules.system.service.DataService;
|
import me.zhengjie.modules.system.service.DataService;
|
||||||
import me.zhengjie.modules.system.service.RoleService;
|
import me.zhengjie.modules.system.service.RoleService;
|
||||||
import me.zhengjie.modules.system.service.UserService;
|
import me.zhengjie.modules.system.service.UserService;
|
||||||
import me.zhengjie.modules.system.service.dto.UserDto;
|
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Zheng Jie
|
* @author Zheng Jie
|
||||||
* @date 2018-11-22
|
* @date 2018-11-22
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Service("userDetailsService")
|
@Service("userDetailsService")
|
||||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
private final RoleService roleService;
|
private final RoleService roleService;
|
||||||
private final DataService dataService;
|
private final DataService dataService;
|
||||||
private final LoginProperties loginProperties;
|
private final UserCacheManager userCacheManager;
|
||||||
|
|
||||||
private final UserCacheManager USER_DTO_CACHE;
|
|
||||||
|
|
||||||
public void setEnableCache(boolean enableCache) {
|
|
||||||
this.loginProperties.setCacheEnable(enableCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExecutorService executor = newThreadPool();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtUserDto loadUserByUsername(String username) {
|
public JwtUserDto loadUserByUsername(String username) {
|
||||||
JwtUserDto jwtUserDto = null;
|
JwtUserDto jwtUserDto = userCacheManager.getUserCache(username);
|
||||||
Future<JwtUserDto> future = USER_DTO_CACHE.get(username);
|
if(jwtUserDto == null){
|
||||||
if (!loginProperties.isCacheEnable()) {
|
UserLoginDto user;
|
||||||
UserDto user;
|
|
||||||
try {
|
try {
|
||||||
user = userService.findByName(username);
|
user = userService.getLoginData(username);
|
||||||
} catch (EntityNotFoundException e) {
|
} catch (EntityNotFoundException e) {
|
||||||
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
|
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
|
||||||
throw new UsernameNotFoundException(username, e);
|
throw new UsernameNotFoundException(username, e);
|
||||||
|
@ -75,85 +63,10 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
dataService.getDeptIds(user),
|
dataService.getDeptIds(user),
|
||||||
roleService.mapToGrantedAuthorities(user)
|
roleService.mapToGrantedAuthorities(user)
|
||||||
);
|
);
|
||||||
|
// 添加缓存数据
|
||||||
|
userCacheManager.addUserCache(username, jwtUserDto);
|
||||||
}
|
}
|
||||||
return jwtUserDto;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (future == null) {
|
|
||||||
Callable<JwtUserDto> call = () -> getJwtBySearchDb(username);
|
|
||||||
FutureTask<JwtUserDto> ft = new FutureTask<>(call);
|
|
||||||
future = USER_DTO_CACHE.putIfAbsent(username, ft);
|
|
||||||
if (future == null) {
|
|
||||||
future = ft;
|
|
||||||
executor.submit(ft);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return future.get();
|
|
||||||
} catch (CancellationException e) {
|
|
||||||
USER_DTO_CACHE.remove(username);
|
|
||||||
System.out.println("error" + Thread.currentThread().getName());
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
throw new RuntimeException(e.getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
jwtUserDto = future.get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
throw new RuntimeException(e.getMessage());
|
|
||||||
}
|
|
||||||
// 检查dataScope是否修改
|
|
||||||
List<Long> dataScopes = jwtUserDto.getDataScopes();
|
|
||||||
dataScopes.clear();
|
|
||||||
dataScopes.addAll(dataService.getDeptIds(jwtUserDto.getUser()));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return jwtUserDto;
|
return jwtUserDto;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private JwtUserDto getJwtBySearchDb(String username) {
|
|
||||||
UserDto user;
|
|
||||||
try {
|
|
||||||
user = userService.findByName(username);
|
|
||||||
} catch (EntityNotFoundException e) {
|
|
||||||
// SpringSecurity会自动转换UsernameNotFoundException为BadCredentialsException
|
|
||||||
throw new UsernameNotFoundException("", e);
|
|
||||||
}
|
|
||||||
if (user == null) {
|
|
||||||
throw new UsernameNotFoundException("");
|
|
||||||
} else {
|
|
||||||
if (!user.getEnabled()) {
|
|
||||||
throw new BadRequestException("账号未激活!");
|
|
||||||
}
|
|
||||||
return new JwtUserDto(
|
|
||||||
user,
|
|
||||||
dataService.getDeptIds(user),
|
|
||||||
roleService.mapToGrantedAuthorities(user)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ExecutorService newThreadPool() {
|
|
||||||
ThreadFactory namedThreadFactory = new ThreadFactory() {
|
|
||||||
final AtomicInteger sequence = new AtomicInteger(1);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Thread newThread(Runnable r) {
|
|
||||||
Thread thread = new Thread(r);
|
|
||||||
int seq = this.sequence.getAndIncrement();
|
|
||||||
thread.setName("future-task-thread" + (seq > 1 ? "-" + seq : ""));
|
|
||||||
if (!thread.isDaemon()) {
|
|
||||||
thread.setDaemon(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return new ThreadPoolExecutor(10, 200,
|
|
||||||
0L, TimeUnit.MILLISECONDS,
|
|
||||||
new LinkedBlockingQueue<>(1024),
|
|
||||||
namedThreadFactory,
|
|
||||||
new ThreadPoolExecutor.AbortPolicy());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ package me.zhengjie.modules.security.service.dto;
|
||||||
import com.alibaba.fastjson.annotation.JSONField;
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import me.zhengjie.modules.system.service.dto.UserDto;
|
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -33,11 +32,10 @@ import java.util.stream.Collectors;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class JwtUserDto implements UserDetails {
|
public class JwtUserDto implements UserDetails {
|
||||||
|
|
||||||
private final UserDto user;
|
private final UserLoginDto user;
|
||||||
|
|
||||||
private final List<Long> dataScopes;
|
private final List<Long> dataScopes;
|
||||||
|
|
||||||
@JSONField(serialize = false)
|
|
||||||
private final List<AuthorityDto> authorities;
|
private final List<AuthorityDto> authorities;
|
||||||
|
|
||||||
public Set<String> getRoles() {
|
public Set<String> getRoles() {
|
||||||
|
|
|
@ -17,6 +17,7 @@ package me.zhengjie.modules.system.service;
|
||||||
|
|
||||||
import me.zhengjie.modules.system.domain.User;
|
import me.zhengjie.modules.system.domain.User;
|
||||||
import me.zhengjie.modules.system.service.dto.UserDto;
|
import me.zhengjie.modules.system.service.dto.UserDto;
|
||||||
|
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||||
import me.zhengjie.modules.system.service.dto.UserQueryCriteria;
|
import me.zhengjie.modules.system.service.dto.UserQueryCriteria;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
@ -65,6 +66,13 @@ public interface UserService {
|
||||||
*/
|
*/
|
||||||
UserDto findByName(String userName);
|
UserDto findByName(String userName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名查询
|
||||||
|
* @param userName /
|
||||||
|
* @return /
|
||||||
|
*/
|
||||||
|
UserLoginDto getLoginData(String userName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改密码
|
* 修改密码
|
||||||
* @param username 用户名
|
* @param username 用户名
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2020 Zheng Jie
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package me.zhengjie.modules.system.service.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Zheng Jie
|
||||||
|
* @description 用户缓存时使用
|
||||||
|
* @date 2022-05-26
|
||||||
|
**/
|
||||||
|
public class UserLoginDto extends UserDto {
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private Boolean isAdmin;
|
||||||
|
}
|
|
@ -18,7 +18,7 @@ package me.zhengjie.modules.system.service.impl;
|
||||||
import cn.hutool.core.collection.CollectionUtil;
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import me.zhengjie.exception.BadRequestException;
|
import me.zhengjie.exception.BadRequestException;
|
||||||
import me.zhengjie.modules.security.service.UserCacheClean;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
import me.zhengjie.modules.security.service.dto.AuthorityDto;
|
import me.zhengjie.modules.security.service.dto.AuthorityDto;
|
||||||
import me.zhengjie.modules.system.domain.Menu;
|
import me.zhengjie.modules.system.domain.Menu;
|
||||||
import me.zhengjie.modules.system.domain.Role;
|
import me.zhengjie.modules.system.domain.Role;
|
||||||
|
@ -60,7 +60,7 @@ public class RoleServiceImpl implements RoleService {
|
||||||
private final RoleSmallMapper roleSmallMapper;
|
private final RoleSmallMapper roleSmallMapper;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final UserCacheClean userCacheClean;
|
private final UserCacheManager userCacheManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RoleDto> queryAll() {
|
public List<RoleDto> queryAll() {
|
||||||
|
@ -213,7 +213,7 @@ public class RoleServiceImpl implements RoleService {
|
||||||
public void delCaches(Long id, List<User> users) {
|
public void delCaches(Long id, List<User> users) {
|
||||||
users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users;
|
users = CollectionUtil.isEmpty(users) ? userRepository.findByRoleId(id) : users;
|
||||||
if (CollectionUtil.isNotEmpty(users)) {
|
if (CollectionUtil.isNotEmpty(users)) {
|
||||||
users.forEach(item -> userCacheClean.cleanUserCache(item.getUsername()));
|
users.forEach(item -> userCacheManager.cleanUserCache(item.getUsername()));
|
||||||
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
|
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
|
||||||
redisUtils.delByKeys(CacheKey.DATA_USER, userIds);
|
redisUtils.delByKeys(CacheKey.DATA_USER, userIds);
|
||||||
redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
|
redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
|
||||||
|
|
|
@ -19,16 +19,14 @@ import lombok.RequiredArgsConstructor;
|
||||||
import me.zhengjie.config.FileProperties;
|
import me.zhengjie.config.FileProperties;
|
||||||
import me.zhengjie.exception.BadRequestException;
|
import me.zhengjie.exception.BadRequestException;
|
||||||
import me.zhengjie.modules.security.service.OnlineUserService;
|
import me.zhengjie.modules.security.service.OnlineUserService;
|
||||||
import me.zhengjie.modules.security.service.UserCacheClean;
|
import me.zhengjie.modules.security.service.UserCacheManager;
|
||||||
import me.zhengjie.modules.system.domain.User;
|
import me.zhengjie.modules.system.domain.User;
|
||||||
import me.zhengjie.exception.EntityExistException;
|
import me.zhengjie.exception.EntityExistException;
|
||||||
import me.zhengjie.exception.EntityNotFoundException;
|
import me.zhengjie.exception.EntityNotFoundException;
|
||||||
import me.zhengjie.modules.system.repository.UserRepository;
|
import me.zhengjie.modules.system.repository.UserRepository;
|
||||||
import me.zhengjie.modules.system.service.UserService;
|
import me.zhengjie.modules.system.service.UserService;
|
||||||
import me.zhengjie.modules.system.service.dto.JobSmallDto;
|
import me.zhengjie.modules.system.service.dto.*;
|
||||||
import me.zhengjie.modules.system.service.dto.RoleSmallDto;
|
import me.zhengjie.modules.system.service.mapstruct.UserLoginMapper;
|
||||||
import me.zhengjie.modules.system.service.dto.UserDto;
|
|
||||||
import me.zhengjie.modules.system.service.dto.UserQueryCriteria;
|
|
||||||
import me.zhengjie.modules.system.service.mapstruct.UserMapper;
|
import me.zhengjie.modules.system.service.mapstruct.UserMapper;
|
||||||
import me.zhengjie.utils.*;
|
import me.zhengjie.utils.*;
|
||||||
import org.springframework.cache.annotation.CacheConfig;
|
import org.springframework.cache.annotation.CacheConfig;
|
||||||
|
@ -58,8 +56,9 @@ public class UserServiceImpl implements UserService {
|
||||||
private final UserMapper userMapper;
|
private final UserMapper userMapper;
|
||||||
private final FileProperties properties;
|
private final FileProperties properties;
|
||||||
private final RedisUtils redisUtils;
|
private final RedisUtils redisUtils;
|
||||||
private final UserCacheClean userCacheClean;
|
private final UserCacheManager userCacheManager;
|
||||||
private final OnlineUserService onlineUserService;
|
private final OnlineUserService onlineUserService;
|
||||||
|
private final UserLoginMapper userLoginMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object queryAll(UserQueryCriteria criteria, Pageable pageable) {
|
public Object queryAll(UserQueryCriteria criteria, Pageable pageable) {
|
||||||
|
@ -175,6 +174,16 @@ public class UserServiceImpl implements UserService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserLoginDto getLoginData(String userName) {
|
||||||
|
User user = userRepository.findByUsername(userName);
|
||||||
|
if (user == null) {
|
||||||
|
throw new EntityNotFoundException(User.class, "name", userName);
|
||||||
|
} else {
|
||||||
|
return userLoginMapper.toDto(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void updatePass(String username, String pass) {
|
public void updatePass(String username, String pass) {
|
||||||
|
@ -252,6 +261,6 @@ public class UserServiceImpl implements UserService {
|
||||||
* @param username /
|
* @param username /
|
||||||
*/
|
*/
|
||||||
private void flushCache(String username) {
|
private void flushCache(String username) {
|
||||||
userCacheClean.cleanUserCache(username);
|
userCacheManager.cleanUserCache(username);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019-2020 Zheng Jie
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package me.zhengjie.modules.system.service.mapstruct;
|
||||||
|
|
||||||
|
import me.zhengjie.base.BaseMapper;
|
||||||
|
import me.zhengjie.modules.system.domain.User;
|
||||||
|
import me.zhengjie.modules.system.service.dto.UserLoginDto;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.ReportingPolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Zheng Jie
|
||||||
|
* @date 2018-11-23
|
||||||
|
*/
|
||||||
|
@Mapper(componentModel = "spring",uses = {RoleMapper.class, DeptMapper.class, JobMapper.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)
|
||||||
|
public interface UserLoginMapper extends BaseMapper<UserLoginDto, User> {
|
||||||
|
}
|
|
@ -51,10 +51,12 @@ spring:
|
||||||
|
|
||||||
# 登录相关配置
|
# 登录相关配置
|
||||||
login:
|
login:
|
||||||
# 登录缓存
|
|
||||||
cache-enable: true
|
|
||||||
# 是否限制单用户登录
|
# 是否限制单用户登录
|
||||||
single-login: false
|
single-login: false
|
||||||
|
# Redis用户登录缓存配置
|
||||||
|
user-cache:
|
||||||
|
# 存活时间/秒
|
||||||
|
idle-time: 7200
|
||||||
# 验证码
|
# 验证码
|
||||||
login-code:
|
login-code:
|
||||||
# 验证码类型配置 查看 LoginProperties 类
|
# 验证码类型配置 查看 LoginProperties 类
|
||||||
|
|
|
@ -52,10 +52,12 @@ spring:
|
||||||
|
|
||||||
# 登录相关配置
|
# 登录相关配置
|
||||||
login:
|
login:
|
||||||
# 登录缓存
|
|
||||||
cache-enable: true
|
|
||||||
# 是否限制单用户登录
|
# 是否限制单用户登录
|
||||||
single-login: false
|
single-login: false
|
||||||
|
# Redis用户登录缓存配置
|
||||||
|
user-cache:
|
||||||
|
# 存活时间/秒
|
||||||
|
idle-time: 7200
|
||||||
# 验证码
|
# 验证码
|
||||||
login-code:
|
login-code:
|
||||||
# 验证码类型配置 查看 LoginProperties 类
|
# 验证码类型配置 查看 LoginProperties 类
|
||||||
|
|
|
@ -56,12 +56,3 @@ code:
|
||||||
#密码加密传输,前端公钥加密,后端私钥解密
|
#密码加密传输,前端公钥加密,后端私钥解密
|
||||||
rsa:
|
rsa:
|
||||||
private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
|
private_key: MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A==
|
||||||
|
|
||||||
# 内存用户缓存配置
|
|
||||||
user-cache:
|
|
||||||
# 最小回收数(当缓存数量达到此值时进行回收)
|
|
||||||
min-evictable-size: 512
|
|
||||||
# 最小回收间隔
|
|
||||||
min-evictable-interval: 1800000
|
|
||||||
# 最小存活时间 (ms)
|
|
||||||
min-idle-time: 3600000
|
|
Loading…
Reference in New Issue