初始化
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
package com.njcn.auth.service;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.njcn.auth.pojo.bo.BusinessUser;
|
||||
import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.user.api.UserFeignClient;
|
||||
import com.njcn.user.pojo.dto.UserDTO;
|
||||
import com.njcn.web.utils.RequestUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* <p>
|
||||
* 自定义用户认证和授权
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
private final UserFeignClient userFeignClient;
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {
|
||||
String clientId = RequestUtil.getOAuth2ClientId();
|
||||
BusinessUser businessUser = new BusinessUser(loginName, null, null);
|
||||
businessUser.setClientId(clientId);
|
||||
HttpResult<UserDTO> result = userFeignClient.getUserByName(loginName);
|
||||
LogUtil.njcnDebug(log, "用户认证时,用户名:{}获取用户信息:{}", loginName, result.toString());
|
||||
//成功获取用户信息
|
||||
UserDTO userDTO = result.getData();
|
||||
BeanUtil.copyProperties(userDTO,businessUser,true);
|
||||
businessUser.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", userDTO.getRoleName())));
|
||||
return businessUser;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.njcn.auth.service;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.nimbusds.jose.JWSObject;
|
||||
import com.njcn.common.pojo.constant.SecurityConstants;
|
||||
import com.njcn.common.pojo.dto.UserTokenInfo;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.redis.utils.RedisUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections.CollectionUtils;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author hongawen
|
||||
* @version 1.0.0
|
||||
* @date 2022年03月11日 10:34
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class UserTokenService {
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
|
||||
/**
|
||||
* 记录用户token信息,并经过处理后达到最新登录的使用者,将之前的token信息置为黑名单,过期状态
|
||||
* 1、从在线名单中获取该用户的token信息,key为:TOKEN_ONLINE_PREFIX+userid,value为userTokenInfo的json对象
|
||||
* 1.1 有,则表示有人使用该账户登录过
|
||||
* 1.1.1 将在线名单的用户信息添加到黑名单,并清除黑名单中已经过期的token信息
|
||||
* ,重新赋值黑名单信息,key为:TOKEN_BLACKLIST_PREFIX+userid,value为userTokenInfo的集合
|
||||
* 1.2 没有,该账号当前只有本人在登录,将当前token等信息保存到白名单
|
||||
*
|
||||
* @param oAuth2AccessToken 认证后的最新token信息
|
||||
*/
|
||||
@Async("asyncExecutor")
|
||||
public void recordUserInfo(OAuth2AccessToken oAuth2AccessToken) {
|
||||
UserTokenInfo userTokenInfo = new UserTokenInfo();
|
||||
String accessTokenValue = oAuth2AccessToken.getValue();
|
||||
JWSObject accessJwsObject ;
|
||||
try {
|
||||
accessJwsObject = JWSObject.parse(accessTokenValue);
|
||||
} catch (ParseException e) {
|
||||
throw new BusinessException(CommonResponseEnum.PARSE_TOKEN_ERROR);
|
||||
}
|
||||
JSONObject accessJson = JSONUtil.parseObj(accessJwsObject.getPayload().toString());
|
||||
String userIndex = accessJson.getStr(SecurityConstants.USER_INDEX_KEY);
|
||||
//查询是否有在线的当前用户
|
||||
String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX + userIndex;
|
||||
Object onlineTokenInfoOld = redisUtil.getObjectByKey(onlineUserKey);
|
||||
if (!Objects.isNull(onlineTokenInfoOld)) {
|
||||
//存在在线用户,将在线用户添加到黑名单列表
|
||||
String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
|
||||
List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
|
||||
if (CollectionUtils.isEmpty(blackUsers)) {
|
||||
blackUsers = new ArrayList<>();
|
||||
}
|
||||
blackUsers.add((UserTokenInfo) onlineTokenInfoOld);
|
||||
//筛选黑名单中是否存在过期的token信息
|
||||
blackUsers.removeIf(userTokenInfoTemp -> userTokenInfoTemp.getRefreshTokenExpire().isBefore(LocalDateTime.now()));
|
||||
//将黑名单集合重新缓存,此处根据最新的黑名单计算当前这个key的生命周期,在时间差的基础上增加5分钟的延迟时间
|
||||
LocalDateTime refreshTokenExpire = ((UserTokenInfo) onlineTokenInfoOld).getRefreshTokenExpire();
|
||||
long lifeTime = Math.abs(refreshTokenExpire.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
|
||||
redisUtil.saveByKeyWithExpire(blackUserKey, blackUsers, lifeTime);
|
||||
}
|
||||
String accessJti = accessJson.getStr(SecurityConstants.JWT_JTI);
|
||||
OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
|
||||
JWSObject refreshJwsObject ;
|
||||
try {
|
||||
refreshJwsObject = JWSObject.parse(refreshToken.getValue());
|
||||
} catch (ParseException e) {
|
||||
throw new BusinessException(CommonResponseEnum.PARSE_TOKEN_ERROR);
|
||||
}
|
||||
JSONObject refreshJson = JSONUtil.parseObj(refreshJwsObject.getPayload().toString());
|
||||
String refreshJti = refreshJson.getStr(SecurityConstants.JWT_JTI);
|
||||
Long refreshExpireTime = refreshJson.getLong(SecurityConstants.JWT_EXP);
|
||||
userTokenInfo.setAccessTokenJti(accessJti);
|
||||
userTokenInfo.setRefreshToken(refreshToken.getValue());
|
||||
LocalDateTime refreshLifeTime =LocalDateTime.ofEpochSecond(refreshExpireTime,0,ZoneOffset.of("+8"));
|
||||
userTokenInfo.setRefreshTokenExpire(refreshLifeTime);
|
||||
//生命周期在refreshToken的基础上,延迟5分钟
|
||||
redisUtil.saveByKeyWithExpire(onlineUserKey, userTokenInfo, refreshLifeTime.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验刷新token是否被加入黑名单
|
||||
*
|
||||
* @param refreshToken 刷新token
|
||||
*/
|
||||
public void judgeRefreshToken(String refreshToken) {
|
||||
JWSObject refreshJwsObject;
|
||||
try {
|
||||
refreshJwsObject = JWSObject.parse(refreshToken);
|
||||
} catch (ParseException e) {
|
||||
throw new BusinessException();
|
||||
}
|
||||
JSONObject refreshJson = JSONUtil.parseObj(refreshJwsObject.getPayload().toString());
|
||||
String userIndex = refreshJson.getStr(SecurityConstants.USER_INDEX_KEY);
|
||||
String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
|
||||
List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
|
||||
if (CollectionUtils.isNotEmpty(blackUsers)) {
|
||||
blackUsers.forEach(temp -> {
|
||||
//存在当前的刷新token,则抛出业务异常
|
||||
if(temp.getRefreshToken().equalsIgnoreCase(refreshToken)){
|
||||
throw new BusinessException(CommonResponseEnum.TOKEN_EXPIRE_JWT);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user