用户登录信息缓存 (#411)

warn: 用户登录缓存 ,待验证”用户及角色“更新时的用户缓存更新,不推荐本地IP 查询,三方库可能存在bug
1、用户登录缓存,前后端交互时 数据库的访问次数
2、修复 本地IP 查询的空指针异常

* #
1、完善用户登录缓存

* #
1、redis清空时 ,用户登录缓存清空

* #
# 代码合并
pull/416/head
廖金龙 2020-06-20 13:25:16 +08:00 committed by GitHub
parent c6c0c52f3c
commit ed715490d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 753 additions and 197 deletions

View File

@ -0,0 +1,56 @@
/*
* 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.utils;
/**
* @author: liaojinlong
* @date: 2020/6/11 15:49
* @apiNote: Key
*/
public interface CacheKey {
/**
* key
*/
String USER_MODIFY_TIME_KEY = "user:modify:time:key:";
String APP_MODIFY_TIME_KEY = "app:modify:time:key:";
String JOB_MODIFY_TIME_KEY = "job:modify:time:key:";
String MENU_MODIFY_TIME_KEY = "menu:modify:time:key:";
String ROLE_MODIFY_TIME_KEY = "role:modify:time:key:";
String DEPT_MODIFY_TIME_KEY = "dept:modify:time:key:";
/**
*
*/
String USER_ID = "user::id:";
String USER_NAME = "user::username:";
/**
* s
*/
String DATE_USER = "data::user:";
/**
*
*/
String MENU_USER = "menu::user:";
/**
*
*/
String ROLE_AUTH = "role::auth:";
String ROLE_ID = "role::id:";
}

View File

@ -0,0 +1,169 @@
/*
* 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.utils;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
/**
* @author: liaojinlong
* @date: 2020/6/11 16:28
* @apiNote: JDK 8
*/
public class DateUtil {
public static final DateTimeFormatter dfyMdHms = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter dfyMd = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* LocalDateTime
*
* @param localDateTime
* @return/
*/
public static Long getTimeStamp(LocalDateTime localDateTime) {
return localDateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
}
/**
* LocalDateTime
*
* @param timeStamp
* @return/
*/
public static LocalDateTime fromTimeStamp(Long timeStamp) {
return LocalDateTime.ofEpochSecond(timeStamp, 0, OffsetDateTime.now().getOffset());
}
/**
* LocalDateTime Date
* Jdk8 使 {@link Date} Date
*
* @param localDateTime
* @return/
*/
public static Date toDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* LocalDate Date
* Jdk8 使 {@link Date} Date
*
* @param localDate
* @return/
*/
public static Date toDate(LocalDate localDate) {
return toDate(localDate.atTime(LocalTime.now(ZoneId.systemDefault())));
}
/**
* Date LocalDateTime
* Jdk8 使 {@link Date} Date
*
* @param date
* @return/
*/
public static LocalDateTime toLocalDateTime(Date date) {
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
}
/**
*
*
* @param localDateTime
* @param patten
* @return/
*/
public static String localDateTimeFormat(LocalDateTime localDateTime, String patten) {
DateTimeFormatter df = DateTimeFormatter.ofPattern(patten);
return df.format(localDateTime);
}
/**
*
*
* @param localDateTime
* @param df
* @return/
*/
public static String localDateTimeFormat(LocalDateTime localDateTime, DateTimeFormatter df) {
return df.format(localDateTime);
}
/**
* yyyy-MM-dd HH:mm:ss
*
* @param localDateTime
* @return/
*/
public static String localDateTimeFormatyMdHms(LocalDateTime localDateTime) {
return dfyMdHms.format(localDateTime);
}
/**
* yyyy-MM-dd
*
* @param localDateTime
* @return/
*/
public String localDateTimeFormatyMd(LocalDateTime localDateTime) {
return dfyMd.format(localDateTime);
}
/**
* LocalDateTime yyyy-MM-dd
*
* @param localDateTime
* @return/
*/
public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, String pattern) {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
}
/**
* LocalDateTime yyyy-MM-dd
*
* @param localDateTime
* @return/
*/
public static LocalDateTime parseLocalDateTimeFormat(String localDateTime, DateTimeFormatter dateTimeFormatter) {
return LocalDateTime.from(dateTimeFormatter.parse(localDateTime));
}
/**
* LocalDateTime yyyy-MM-dd
*
* @param localDateTime
* @return/
*/
public static LocalDateTime parseLocalDateTimeFormatyMd(String localDateTime) {
return LocalDateTime.from(dfyMd.parse(localDateTime));
}
/**
* LocalDateTime yyyy-MM-dd HH:mm:ss
*
* @param localDateTime
* @return/
*/
public static LocalDateTime parseLocalDateTimeFormatyMdHms(String localDateTime) {
return LocalDateTime.from(dfyMdHms.parse(localDateTime));
}
}

View File

@ -174,19 +174,19 @@ public class RedisUtils {
if (keys != null && keys.length > 0) {
if (keys.length == 1) {
boolean result = redisTemplate.delete(keys[0]);
System.out.println("--------------------------------------------");
System.out.println(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result));
System.out.println("--------------------------------------------");
log.debug("--------------------------------------------");
log.debug(new StringBuilder("删除缓存:").append(keys[0]).append(",结果:").append(result).toString());
log.debug("--------------------------------------------");
} else {
Set<Object> keySet = new HashSet<>();
for (String key : keys) {
keySet.addAll(redisTemplate.keys(key));
}
long count = redisTemplate.delete(keySet);
System.out.println("--------------------------------------------");
System.out.println("成功删除缓存:" + keySet.toString());
System.out.println("缓存删除数量:" + count + "个");
System.out.println("--------------------------------------------");
log.debug("--------------------------------------------");
log.debug("成功删除缓存:" + keySet.toString());
log.debug("缓存删除数量:" + count + "个");
log.debug("--------------------------------------------");
}
}
}
@ -697,9 +697,9 @@ public class RedisUtils {
}
long count = redisTemplate.delete(keys);
// 此处提示可自行删除
System.out.println("--------------------------------------------");
System.out.println("成功删除缓存:" + keys.toString());
System.out.println("缓存删除数量:" + count + "个");
System.out.println("--------------------------------------------");
log.debug("--------------------------------------------");
log.debug("成功删除缓存:" + keys.toString());
log.debug("缓存删除数量:" + count + "个");
log.debug("--------------------------------------------");
}
}

View File

@ -26,6 +26,7 @@ import org.lionsoul.ip2region.DbSearcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.net.InetAddress;
@ -41,7 +42,8 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
private static final Logger log = LoggerFactory.getLogger(StringUtils.class);
private static boolean ipLocal = false;
private static DbSearcher searcher = null;
private static File file = null;
private static DbConfig config;
private static final char SEPARATOR = '_';
private static final String UNKNOWN = "unknown";
@ -54,11 +56,9 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
*/
String path = "ip2region/ip2region.db";
String name = "ip2region.db";
DbConfig config;
try {
config = new DbConfig();
File file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name);
searcher = new DbSearcher(config, file.getPath());
file = FileUtil.inputStreamToFile(new ClassPathResource(path).getInputStream(), name);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
@ -206,9 +206,10 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
*/
public static String getLocalCityInfo(String ip) {
try {
DataBlock dataBlock;
dataBlock = searcher.binarySearch(ip);
String address = dataBlock.getRegion().replace("0|", "");
DataBlock dataBlock = new DbSearcher(config, file.getPath())
.binarySearch(ip);
String region = dataBlock.getRegion();
String address = region.replace("0|", "");
char symbol = '|';
if (address.charAt(address.length() - 1) == symbol) {
address = address.substring(0, address.length() - 1);

View File

@ -0,0 +1,26 @@
package me.zhengjie.utils;
import org.junit.Test;
import java.time.LocalDateTime;
import java.util.Date;
public class DateUtilsTest {
@Test
public void test1() {
long l = System.currentTimeMillis() / 1000;
LocalDateTime localDateTime = DateUtil.fromTimeStamp(l);
System.out.printf(DateUtil.localDateTimeFormatyMdHms(localDateTime));
}
@Test
public void test2() {
LocalDateTime now = LocalDateTime.now();
System.out.println(DateUtil.localDateTimeFormatyMdHms(now));
Date date = DateUtil.toDate(now);
LocalDateTime localDateTime = DateUtil.toLocalDateTime(date);
System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime));
LocalDateTime localDateTime1 = DateUtil.fromTimeStamp(date.getTime() / 1000);
System.out.println(DateUtil.localDateTimeFormatyMdHms(localDateTime1));
}
}

View File

@ -15,6 +15,8 @@
*/
package me.zhengjie.annotation;
import me.zhengjie.annotation.type.LogActionType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@ -28,4 +30,13 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
/**
*
*
* @return
*/
boolean enable() default true;
LogActionType type() default LogActionType.SELECT;
}

View File

@ -0,0 +1,45 @@
/*
* 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.annotation.type;
/**
* @author: liaojinlong
* @date: 2020/6/11 19:47
* @apiNote:
*/
public enum LogActionType {
/**
*
*/
ADD("新增"),
SELECT("查询"),
UPDATE("更新"),
DELETE("删除");
private String value;
LogActionType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View File

@ -19,9 +19,12 @@ import lombok.RequiredArgsConstructor;
import me.zhengjie.modules.quartz.domain.QuartzJob;
import me.zhengjie.modules.quartz.repository.QuartzJobRepository;
import me.zhengjie.modules.quartz.utils.QuartzManage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.util.List;
/**
@ -31,19 +34,20 @@ import java.util.List;
@Component
@RequiredArgsConstructor
public class JobRunner implements ApplicationRunner {
private static final Logger log = LoggerFactory.getLogger(JobRunner.class);
private final QuartzJobRepository quartzJobRepository;
private final QuartzManage quartzManage;
/**
*
*
* @param applicationArguments /
*/
@Override
public void run(ApplicationArguments applicationArguments){
System.out.println("--------------------注入定时任务---------------------");
public void run(ApplicationArguments applicationArguments) {
log.info("--------------------注入定时任务---------------------");
List<QuartzJob> quartzJobs = quartzJobRepository.findByIsPauseIsFalse();
quartzJobs.forEach(quartzManage::addJob);
System.out.println("--------------------定时任务注入完成---------------------");
log.info("--------------------定时任务注入完成---------------------");
}
}

View File

@ -17,7 +17,10 @@ package me.zhengjie.modules.security.config;
import lombok.RequiredArgsConstructor;
import me.zhengjie.annotation.AnonymousAccess;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.modules.security.security.*;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.modules.security.service.UserCacheClean;
import me.zhengjie.utils.enums.RequestMethodEnum;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@ -37,6 +40,7 @@ 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;
import java.util.*;
/**
@ -53,6 +57,9 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtAuthenticationEntryPoint authenticationErrorHandler;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
private final ApplicationContext applicationContext;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
@ -144,7 +151,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
if (null != anonymousAccess) {
List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
RequestMethodEnum request = RequestMethodEnum.find(requestMethods.size() == 0 ? RequestMethodEnum.ALL.getType() : requestMethods.get(0).name());
switch (Objects.requireNonNull(request)){
switch (Objects.requireNonNull(request)) {
case GET:
get.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
break;
@ -176,6 +183,6 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
private TokenConfigurer securityConfigurerAdapter() {
return new TokenConfigurer(tokenProvider);
return new TokenConfigurer(tokenProvider, properties, onlineUserService, userCacheClean);
}
}

View File

@ -19,10 +19,12 @@ package me.zhengjie.modules.security.config.bean;
import com.wf.captcha.*;
import com.wf.captcha.base.Captcha;
import me.zhengjie.exception.BadConfigurationException;
import java.util.Objects;
/**
*
*
* @author liaojinlong
* @date loginCode.length0loginCode.length0/6/10 17:loginCode.length6
*/
@ -34,6 +36,10 @@ public class LoginProperties {
private boolean singleLogin = false;
private LoginCode loginCode;
/**
*
*/
private boolean cacheEnable;
public boolean isSingleLogin() {
return singleLogin;
@ -51,6 +57,14 @@ public class LoginProperties {
this.loginCode = loginCode;
}
public boolean isCacheEnable() {
return cacheEnable;
}
public void setCacheEnable(boolean cacheEnable) {
this.cacheEnable = cacheEnable;
}
/**
*
*

View File

@ -16,6 +16,9 @@
package me.zhengjie.modules.security.security;
import lombok.RequiredArgsConstructor;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.modules.security.service.UserCacheClean;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
@ -28,10 +31,13 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
public class TokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
@Override
public void configure(HttpSecurity http) {
TokenFilter customFilter = new TokenFilter(tokenProvider);
TokenFilter customFilter = new TokenFilter(tokenProvider, properties, onlineUserService, userCacheClean);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
}

View File

@ -17,31 +17,49 @@ package me.zhengjie.modules.security.security;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.modules.security.service.UserCacheClean;
import me.zhengjie.modules.security.service.dto.OnlineUserDto;
import me.zhengjie.modules.security.service.OnlineUserService;
import me.zhengjie.utils.SpringContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Objects;
/**
* @author /
*/
@Slf4j
@RequiredArgsConstructor
public class TokenFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(TokenFilter.class);
private final TokenProvider tokenProvider;
private final SecurityProperties properties;
private final OnlineUserService onlineUserService;
private final UserCacheClean userCacheClean;
/**
* @param tokenProvider Token
* @param properties JWT
* @param onlineUserService 线
* @param userCacheClean
*/
public TokenFilter(TokenProvider tokenProvider, SecurityProperties properties, OnlineUserService onlineUserService, UserCacheClean userCacheClean) {
this.properties = properties;
this.onlineUserService = onlineUserService;
this.tokenProvider = tokenProvider;
this.userCacheClean = userCacheClean;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
@ -49,14 +67,18 @@ public class TokenFilter extends GenericFilterBean {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String token = resolveToken(httpServletRequest);
// 对于 Token 为空的不需要去查 Redis
if(StrUtil.isNotBlank(token)){
if (StrUtil.isNotBlank(token)) {
OnlineUserDto onlineUserDto = null;
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
boolean cleanUserCache = false;
try {
OnlineUserService onlineUserService = SpringContextHolder.getBean(OnlineUserService.class);
onlineUserDto = onlineUserService.getOne(properties.getOnlineKey() + token);
} catch (ExpiredJwtException e) {
log.error(e.getMessage());
cleanUserCache = true;
} finally {
if (cleanUserCache || Objects.isNull(onlineUserDto)) {
userCacheClean.cleanUserCache(String.valueOf(tokenProvider.getClaims(token).get(TokenProvider.AUTHORITIES_KEY)));
}
}
if (onlineUserDto != null && StringUtils.hasText(token)) {
Authentication authentication = tokenProvider.getAuthentication(token);
@ -68,12 +90,19 @@ public class TokenFilter extends GenericFilterBean {
filterChain.doFilter(servletRequest, servletResponse);
}
/**
* Token
*
* @param request
* @return
*/
private String resolveToken(HttpServletRequest request) {
SecurityProperties properties = SpringContextHolder.getBean(SecurityProperties.class);
String bearerToken = request.getHeader(properties.getHeader());
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(properties.getTokenStartWith())) {
// 去掉令牌前缀
return bearerToken.replace(properties.getTokenStartWith(),"");
return bearerToken.replace(properties.getTokenStartWith(), "");
} else {
log.debug("非法Token{}", bearerToken);
}
return null;
}

View File

@ -22,7 +22,6 @@ import cn.hutool.core.util.ObjectUtil;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.security.config.bean.SecurityProperties;
import me.zhengjie.utils.RedisUtils;
@ -33,6 +32,7 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Arrays;
@ -47,40 +47,62 @@ import java.util.stream.Collectors;
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class TokenProvider implements InitializingBean {
private final SecurityProperties properties;
private final RedisUtils redisUtils;
private static final String AUTHORITIES_KEY = "auth";
public static final String AUTHORITIES_KEY = "auth";
private Key key;
private JwtParser jwtParser;
private JwtBuilder jwtBuilder;
public TokenProvider(SecurityProperties properties, RedisUtils redisUtils) {
this.properties = properties;
this.redisUtils = redisUtils;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(properties.getBase64Secret());
this.key = Keys.hmacShaKeyFor(keyBytes);
jwtParser = Jwts.parserBuilder()
.setSigningKey(key)
.build();
jwtBuilder = Jwts.builder()
.signWith(key, SignatureAlgorithm.HS512);
}
/**
* Token
* Token Redis
*
* @param authentication
* @return
*/
public String createToken(Authentication authentication) {
/**
*
*/
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
return jwtBuilder
// 加入ID确保生成的 Token 都不一致
.setId(IdUtil.simpleUUID())
.claim(AUTHORITIES_KEY, authorities)
.setSubject(authentication.getName())
.compact();
}
/**
* Token
*
* @param token
* @return
*/
Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Claims claims = getClaims(token);
// fix bug: 当前用户如果没有任何权限时在输入用户名后刷新验证码会抛IllegalArgumentException
Object authoritiesStr = claims.get(AUTHORITIES_KEY);
@ -89,29 +111,33 @@ public class TokenProvider implements InitializingBean {
Arrays.stream(authoritiesStr.toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()) : Collections.emptyList();
User principal = new User(claims.getSubject(), "", authorities);
User principal = new User(claims.getSubject(), "******", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public Claims getClaims(String token) {
return jwtParser
.parseClaimsJws(token)
.getBody();
}
/**
* @param token token
*/
public void checkRenewal(String token){
public void checkRenewal(String token) {
// 判断是否续期token,计算token的过期时间
long time = redisUtils.getExpire(properties.getOnlineKey() + token) * 1000;
Date expireDate = DateUtil.offset(new Date(), DateField.MILLISECOND, (int) time);
// 判断当前时间与过期时间的时间差
long differ = expireDate.getTime() - System.currentTimeMillis();
// 如果在续期检查的范围内,则续期
if(differ <= properties.getDetect()){
if (differ <= properties.getDetect()) {
long renew = time + properties.getRenew();
redisUtils.expire(properties.getOnlineKey() + token, renew, TimeUnit.MILLISECONDS);
}
}
public String getToken(HttpServletRequest request){
public String getToken(HttpServletRequest request) {
final String requestHeader = request.getHeader(properties.getHeader());
if (requestHeader != null && requestHeader.startsWith(properties.getTokenStartWith())) {
return requestHeader.substring(7);

View File

@ -0,0 +1,50 @@
/*
* 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 me.zhengjie.utils.StringUtils;
import org.springframework.stereotype.Component;
/**
* @author: liaojinlong
* @date: 2020/6/11 18:01
* @apiNote: Spring
*/
@Component
public class UserCacheClean {
/**
* <br>
*
*
* @param userName
*/
public void cleanUserCache(String userName) {
if (StringUtils.isNotEmpty(userName)) {
UserDetailsServiceImpl.userDtoCache.remove(userName);
}
}
/**
* <br>
* ,便
*/
public void cleanAll() {
UserDetailsServiceImpl.userDtoCache.clear();
}
}

View File

@ -18,6 +18,7 @@ package me.zhengjie.modules.security.service;
import lombok.RequiredArgsConstructor;
import me.zhengjie.exception.BadRequestException;
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.system.service.DataService;
import me.zhengjie.modules.system.service.RoleService;
@ -27,6 +28,9 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Zheng Jie
* @date 2018-11-22
@ -34,13 +38,31 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final RoleService roleService;
private final DataService dataService;
private final LoginProperties loginProperties;
public void setEnableCache(boolean enableCache) {
this.loginProperties.setCacheEnable(enableCache);
}
/**
*
*
* @see {@link UserCacheClean}
*/
static Map<String, JwtUserDto> userDtoCache = new ConcurrentHashMap<>();
@Override
public JwtUserDto loadUserByUsername(String username) {
boolean searchDb = true;
JwtUserDto jwtUserDto = null;
if (loginProperties.isCacheEnable() && userDtoCache.containsKey(username)) {
jwtUserDto = userDtoCache.get(username);
searchDb = false;
}
if (searchDb) {
UserDto user;
try {
user = userService.findByName(username);
@ -54,11 +76,14 @@ public class UserDetailsServiceImpl implements UserDetailsService {
if (!user.getEnabled()) {
throw new BadRequestException("账号未激活");
}
return new JwtUserDto(
jwtUserDto = new JwtUserDto(
user,
dataService.getDeptIds(user),
roleService.mapToGrantedAuthorities(user)
);
userDtoCache.put(username, jwtUserDto);
}
}
return jwtUserDto;
}
}

View File

@ -15,8 +15,10 @@
*/
package me.zhengjie.modules.system.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import lombok.RequiredArgsConstructor;
import me.zhengjie.exception.BadRequestException;
import me.zhengjie.modules.security.service.UserCacheClean;
import me.zhengjie.modules.system.domain.Menu;
import me.zhengjie.modules.system.domain.Role;
import me.zhengjie.exception.EntityExistException;
@ -40,6 +42,7 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
@ -59,6 +62,7 @@ public class RoleServiceImpl implements RoleService {
private final RoleSmallMapper roleSmallMapper;
private final RedisUtils redisUtils;
private final UserRepository userRepository;
private final UserCacheClean userCacheClean;
@Override
public List<RoleDto> queryAll() {
@ -68,12 +72,12 @@ public class RoleServiceImpl implements RoleService {
@Override
public List<RoleDto> queryAll(RoleQueryCriteria criteria) {
return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder)));
return roleMapper.toDto(roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
}
@Override
public Object queryAll(RoleQueryCriteria criteria, Pageable pageable) {
Page<Role> page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
Page<Role> page = roleRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
return PageUtil.toPage(page.map(roleMapper::toDto));
}
@ -82,15 +86,15 @@ public class RoleServiceImpl implements RoleService {
@Transactional(rollbackFor = Exception.class)
public RoleDto findById(long id) {
Role role = roleRepository.findById(id).orElseGet(Role::new);
ValidationUtil.isNull(role.getId(),"Role","id",id);
ValidationUtil.isNull(role.getId(), "Role", "id", id);
return roleMapper.toDto(role);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(Role resources) {
if(roleRepository.findByName(resources.getName()) != null){
throw new EntityExistException(Role.class,"username",resources.getName());
if (roleRepository.findByName(resources.getName()) != null) {
throw new EntityExistException(Role.class, "username", resources.getName());
}
roleRepository.save(resources);
}
@ -99,12 +103,12 @@ public class RoleServiceImpl implements RoleService {
@Transactional(rollbackFor = Exception.class)
public void update(Role resources) {
Role role = roleRepository.findById(resources.getId()).orElseGet(Role::new);
ValidationUtil.isNull(role.getId(),"Role","id",resources.getId());
ValidationUtil.isNull(role.getId(), "Role", "id", resources.getId());
Role role1 = roleRepository.findByName(resources.getName());
if(role1 != null && !role1.getId().equals(role.getId())){
throw new EntityExistException(Role.class,"username",resources.getName());
if (role1 != null && !role1.getId().equals(role.getId())) {
throw new EntityExistException(Role.class, "username", resources.getName());
}
role.setName(resources.getName());
role.setDescription(resources.getDescription());
@ -120,16 +124,13 @@ public class RoleServiceImpl implements RoleService {
public void updateMenu(Role resources, RoleDto roleDTO) {
Role role = roleMapper.toEntity(roleDTO);
List<User> users = userRepository.findByRoleId(role.getId());
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
// 更新菜单
role.setMenus(resources.getMenus());
// 清理缓存
redisUtils.delByKeys("menu::user:",userIds);
redisUtils.delByKeys("role::auth:",userIds);
redisUtils.del("role::id:" + resources.getId());
cleanCache(resources, users);
roleRepository.save(role);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void untiedMenu(Long menuId) {
@ -166,7 +167,7 @@ public class RoleServiceImpl implements RoleService {
public List<GrantedAuthority> mapToGrantedAuthorities(UserDto user) {
Set<String> permissions = new HashSet<>();
// 如果是管理员直接返回
if(user.getIsAdmin()){
if (user.getIsAdmin()) {
permissions.add("admin");
return permissions.stream().map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
@ -183,7 +184,7 @@ public class RoleServiceImpl implements RoleService {
public void download(List<RoleDto> roles, HttpServletResponse response) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
for (RoleDto role : roles) {
Map<String,Object> map = new LinkedHashMap<>();
Map<String, Object> map = new LinkedHashMap<>();
map.put("角色名称", role.getName());
map.put("角色级别", role.getLevel());
map.put("描述", role.getDescription());
@ -193,21 +194,9 @@ public class RoleServiceImpl implements RoleService {
FileUtil.downloadExcel(list, response);
}
/**
*
* @param id /
*/
public void delCaches(Long id){
List<User> users = userRepository.findByRoleId(id);
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
redisUtils.delByKeys("data::user:",userIds);
redisUtils.delByKeys("menu::user:",userIds);
redisUtils.delByKeys("role::auth:",userIds);
}
@Override
public void verification(Set<Long> ids) {
if(userRepository.countByRoles(ids) > 0){
if (userRepository.countByRoles(ids) > 0) {
throw new BadRequestException("所选角色存在用户关联,请解除关联再试!");
}
}
@ -216,4 +205,43 @@ public class RoleServiceImpl implements RoleService {
public List<Role> findInMenuId(List<Long> menuIds) {
return roleRepository.findInMenuId(menuIds);
}
/**
*
*
* @param id /
*/
public void delCaches(Long id) {
List<User> users = userRepository.findByRoleId(id);
if (CollectionUtil.isNotEmpty(users)) {
users.stream().forEach(item -> {
userCacheClean.cleanUserCache(item.getUsername());
});
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
redisUtils.delByKeys(CacheKey.DATE_USER, userIds);
redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds);
}
}
/**
*
*
* @param resources
* @param users
*/
private void cleanCache(Role resources, List<User> users) {
// 清理缓存
if (CollectionUtil.isNotEmpty(users)) {
users.stream().forEach(item -> {
userCacheClean.cleanUserCache(item.getUsername());
});
Set<Long> userIds = users.stream().map(User::getId).collect(Collectors.toSet());
redisUtils.delByKeys(CacheKey.MENU_USER, userIds);
redisUtils.delByKeys(CacheKey.ROLE_AUTH, userIds);
redisUtils.del(CacheKey.ROLE_ID + resources.getId());
}
}
}

View File

@ -17,6 +17,7 @@ package me.zhengjie.modules.system.service.impl;
import lombok.RequiredArgsConstructor;
import me.zhengjie.config.FileProperties;
import me.zhengjie.modules.security.service.UserCacheClean;
import me.zhengjie.modules.system.domain.User;
import me.zhengjie.exception.EntityExistException;
import me.zhengjie.exception.EntityNotFoundException;
@ -35,7 +36,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.File;
import java.io.IOException;
import java.util.*;
@ -54,16 +57,17 @@ public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
private final FileProperties properties;
private final RedisUtils redisUtils;
private final UserCacheClean userCacheClean;
@Override
public Object queryAll(UserQueryCriteria criteria, Pageable pageable) {
Page<User> page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
Page<User> page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
return PageUtil.toPage(page.map(userMapper::toDto));
}
@Override
public List<UserDto> queryAll(UserQueryCriteria criteria) {
List<User> users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder));
List<User> users = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder));
return userMapper.toDto(users);
}
@ -72,18 +76,18 @@ public class UserServiceImpl implements UserService {
@Transactional(rollbackFor = Exception.class)
public UserDto findById(long id) {
User user = userRepository.findById(id).orElseGet(User::new);
ValidationUtil.isNull(user.getId(),"User","id",id);
ValidationUtil.isNull(user.getId(), "User", "id", id);
return userMapper.toDto(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(User resources) {
if(userRepository.findByUsername(resources.getUsername())!=null){
throw new EntityExistException(User.class,"username",resources.getUsername());
if (userRepository.findByUsername(resources.getUsername()) != null) {
throw new EntityExistException(User.class, "username", resources.getUsername());
}
if(userRepository.findByEmail(resources.getEmail())!=null){
throw new EntityExistException(User.class,"email",resources.getEmail());
if (userRepository.findByEmail(resources.getEmail()) != null) {
throw new EntityExistException(User.class, "email", resources.getEmail());
}
userRepository.save(resources);
}
@ -92,22 +96,22 @@ public class UserServiceImpl implements UserService {
@Transactional(rollbackFor = Exception.class)
public void update(User resources) {
User user = userRepository.findById(resources.getId()).orElseGet(User::new);
ValidationUtil.isNull(user.getId(),"User","id",resources.getId());
ValidationUtil.isNull(user.getId(), "User", "id", resources.getId());
User user1 = userRepository.findByUsername(resources.getUsername());
User user2 = userRepository.findByEmail(resources.getEmail());
if(user1 !=null&&!user.getId().equals(user1.getId())){
throw new EntityExistException(User.class,"username",resources.getUsername());
if (user1 != null && !user.getId().equals(user1.getId())) {
throw new EntityExistException(User.class, "username", resources.getUsername());
}
if(user2!=null&&!user.getId().equals(user2.getId())){
throw new EntityExistException(User.class,"email",resources.getEmail());
if (user2 != null && !user.getId().equals(user2.getId())) {
throw new EntityExistException(User.class, "email", resources.getEmail());
}
// 如果用户的角色改变
if (!resources.getRoles().equals(user.getRoles())) {
redisUtils.del("data::user:" + resources.getId());
redisUtils.del("menu::user:" + resources.getId());
redisUtils.del("role::auth:" + resources.getId());
redisUtils.del(CacheKey.DATE_USER + resources.getId());
redisUtils.del(CacheKey.MENU_USER + resources.getId());
redisUtils.del(CacheKey.ROLE_AUTH + resources.getId());
}
// 如果用户名称修改
if(!resources.getUsername().equals(user.getUsername())){
@ -164,8 +168,9 @@ public class UserServiceImpl implements UserService {
@Override
@Transactional(rollbackFor = Exception.class)
public void updatePass(String username, String pass) {
userRepository.updatePass(username,pass,new Date());
userRepository.updatePass(username, pass, new Date());
redisUtils.del("user::username:" + username);
flushCache(username);
}
@Override
@ -177,18 +182,23 @@ public class UserServiceImpl implements UserService {
user.setAvatarPath(Objects.requireNonNull(file).getPath());
user.setAvatarName(file.getName());
userRepository.save(user);
if(StringUtils.isNotBlank(oldPath)){
if (StringUtils.isNotBlank(oldPath)) {
FileUtil.del(oldPath);
}
redisUtils.del("user::username:" + user.getUsername());
return new HashMap<String,String>(1){{put("avatar",file.getName());}};
@NotBlank String username = user.getUsername();
redisUtils.del(CacheKey.USER_NAME + username);
flushCache(username);
return new HashMap<String, String>(1) {{
put("avatar", file.getName());
}};
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEmail(String username, String email) {
userRepository.updateEmail(username,email);
redisUtils.del("user::username:" + username);
userRepository.updateEmail(username, email);
redisUtils.del(CacheKey.USER_NAME + username);
flushCache(username);
}
@Override
@ -196,7 +206,7 @@ public class UserServiceImpl implements UserService {
List<Map<String, Object>> list = new ArrayList<>();
for (UserDto userDTO : queryAll) {
List<String> roles = userDTO.getRoles().stream().map(RoleSmallDto::getName).collect(Collectors.toList());
Map<String,Object> map = new LinkedHashMap<>();
Map<String, Object> map = new LinkedHashMap<>();
map.put("用户名", userDTO.getUsername());
map.put("角色", roles);
map.put("部门", userDTO.getDept().getName());
@ -213,10 +223,21 @@ public class UserServiceImpl implements UserService {
/**
*
*
* @param id /
*/
public void delCaches(Long id, String username){
redisUtils.del("user::id:" + id);
redisUtils.del("user::username:" + username);
public void delCaches(Long id, String username) {
redisUtils.del(CacheKey.USER_ID + id);
redisUtils.del(CacheKey.USER_NAME + username);
flushCache(username);
}
/**
*
*
* @param username
*/
private void flushCache(String username) {
userCacheClean.cleanUserCache(username);
}
}

View File

@ -46,6 +46,8 @@ spring:
# 登录相关配置
login:
# 登录缓存
cache-enable: true
# 是否限制单用户登录
single: false
# 验证码

View File

@ -48,6 +48,8 @@ spring:
# 登录相关配置
login:
# 登录缓存
cache-enable: true
# 是否限制单用户登录
single: false
# 验证码
@ -83,7 +85,7 @@ jwt:
# IP 本地解析
ip:
local-parsing: true
local-parsing: false
#是否允许生成代码生产环境设置为false
generator:

View File

@ -0,0 +1,34 @@
package me.zhengjie;
import me.zhengjie.modules.security.service.UserDetailsServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class LoginCacheTest {
@Resource(name = "userDetailsService")
private UserDetailsServiceImpl userDetailsService;
private int size = 10000;
@Test
public void testCache() {
long start1 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
userDetailsService.loadUserByUsername("admin");
}
long end1 = System.currentTimeMillis();
//关闭缓存
userDetailsService.setEnableCache(false);
long start2 = System.currentTimeMillis();
for (int i = 0; i < size; i++) {
userDetailsService.loadUserByUsername("admin");
}
long end2 = System.currentTimeMillis();
System.out.printf("使用缓存:" + (end1 - start1) + "毫秒\n 不使用缓存:" + (end2 - start2) + "毫秒");
}
}