新增登录Token续命模式

v1.4.1
hiparker 4 years ago
parent e7c9212765
commit dbd9373a4d

@ -100,6 +100,9 @@ public class GlobalProperties {
@EqualsAndHashCode(callSuper = false)
public static class Login {
/** 续命模式 (开启续命模式后 在有效时间内 访问任意接口 则自动续命) */
private Boolean reviveMode;
/** 限制登录数量 -1 为无限大 */
private Integer limitCount;
@ -115,7 +118,6 @@ public class GlobalProperties {
/** 失败锁定时间(秒) */
private Integer slipLockSpeed;
}
}

@ -1,8 +1,10 @@
package org.opsli.core.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
@ -37,7 +39,7 @@ public class JwtUtil {
/**
* 120
*/
public static long EXPIRE;
public static int EXPIRE_MILLISECOND;
/**
* JWT(Base64)
@ -80,23 +82,31 @@ public class JwtUtil {
* @param tokenType token
* @param account
* @param userId ID
* @param isExpire
* @return java.lang.String Token
*/
public static String sign(String tokenType, String account, String userId) {
public static String sign(String tokenType, String account, String userId, boolean isExpire) {
// 时间戳
long currentTimeMillis = System.currentTimeMillis();
// 帐号加JWT私钥加密
String secret = account + Base64.decodeStr(ENCRYPT_JWT_INITIAL_SECRET);
// 此处过期时间是以毫秒为单位所以乘以1000
Date date = new Date(System.currentTimeMillis() + EXPIRE);
Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带account帐号信息
return JWT.create()
JWTCreator.Builder builder = JWT.create()
.withClaim(SignConstants.TYPE, tokenType)
.withClaim(SignConstants.ACCOUNT, account)
.withClaim(SignConstants.USER_ID, userId)
.withClaim(SignConstants.TIMESTAMP, String.valueOf(System.currentTimeMillis()))
// token 过期时间
.withExpiresAt(date)
.sign(algorithm);
.withClaim(SignConstants.TIMESTAMP, String.valueOf(currentTimeMillis));
// 如果为是 则开启失效时间
if(isExpire){
// 时间偏移,取最终失效时间
Date expireDate = DateUtil.offsetMillisecond(DateUtil.date(currentTimeMillis), EXPIRE_MILLISECOND);
// token 过期时间
builder.withExpiresAt(expireDate);
}
// 附带account帐号信息
return builder.sign(algorithm);
}
@ -113,7 +123,7 @@ public class JwtUtil {
&& globalProperties.getAuth().getToken() != null
){
// 获得 Token失效时间
JwtUtil.EXPIRE = globalProperties.getAuth()
JwtUtil.EXPIRE_MILLISECOND = globalProperties.getAuth()
.getToken().getEffectiveTime() * 60 * 1000;
// 获得 Token初始盐值
@ -125,7 +135,7 @@ public class JwtUtil {
// ==================
public static void main(String[] args) {
String token = JwtUtil.sign(TokenTypeConstants.TYPE_SYSTEM, "test","123123");
String token = JwtUtil.sign(TokenTypeConstants.TYPE_SYSTEM, "test","123123", true);
boolean verify = JwtUtil.verify(token);
System.out.println(token);

@ -19,6 +19,7 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -43,6 +44,7 @@ import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
@ -90,7 +92,8 @@ public class UserTokenUtil {
// 如果当前登录开启 数量限制
if(LOGIN_PROPERTIES.getLimitCount() > ACCOUNT_LIMIT_INFINITE){
// 当前用户已存在 Token数量
Long ticketLen = redisPlugin.sSize(CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername());
Long ticketLen = redisPlugin.sSize(CacheUtil.getPrefixName() +
TICKET_PREFIX + user.getUsername());
if(ticketLen !=null && ticketLen >= LOGIN_PROPERTIES.getLimitCount()){
// 如果是拒绝后者 则直接抛出异常
if(LoginLimitRefuse.AFTER == LOGIN_PROPERTIES.getLimitRefuse()){
@ -104,29 +107,28 @@ public class UserTokenUtil {
}
}
// 生效时间
int expire = Integer.parseInt(
String.valueOf(JwtUtil.EXPIRE)
);
// 开启续命模式 如果为续命模式 则不指定Token 的 失效时间
// 生成 Token 包含 username userId timestamp
String signToken = JwtUtil.sign(TokenTypeConstants.TYPE_SYSTEM, user.getUsername(), user.getId());
boolean reviveMode = LOGIN_PROPERTIES.getReviveMode() != null && LOGIN_PROPERTIES.getReviveMode();
String signToken = JwtUtil.sign(
TokenTypeConstants.TYPE_SYSTEM, user.getUsername(), user.getId(), !reviveMode);
// 获得当前时间戳时间
// 获得当前Token时间戳
long timestamp = Convert.toLong(
JwtUtil.getClaim(signToken, SignConstants.TIMESTAMP));
DateTime currDate = DateUtil.date(timestamp);
// 获得失效偏移量时间
DateTime dateTime = DateUtil.offsetMillisecond(currDate, expire);
long endTimestamp = dateTime.getTime();
long endTimestamp = DateUtil.offsetMillisecond(
DateUtil.date(timestamp), JwtUtil.EXPIRE_MILLISECOND).getTime();
// 在redis存一份 token 是为了防止 人为造假
// 保存用户token
Long saveLong = redisPlugin.sPut(CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), signToken);
Long saveLong = redisPlugin.sPut(
CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), signToken);
if(saveLong != null && saveLong > 0){
// 设置该用户全部token失效时间 如果这时又有新设备登录 则续命
redisPlugin.expire(CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), expire);
redisPlugin.expire(
CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(),
JwtUtil.EXPIRE_MILLISECOND, TimeUnit.MILLISECONDS);
TokenRet tokenRet = new TokenRet();
tokenRet.setToken(signToken);
@ -192,10 +194,12 @@ public class UserTokenUtil {
UserModel user = UserUtil.getUser(userId);
if(user != null){
// 删除Token信息
redisPlugin.sRemove(CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), token);
redisPlugin.sRemove(
CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), token);
// 如果缓存中 无该用户任何Token信息 则删除用户缓存
Long size = redisPlugin.sSize(CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername());
Long size = redisPlugin.sSize(
CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername());
if(size == null || size == 0L){
// 删除相关信息
UserUtil.refreshUser(user);
@ -228,11 +232,20 @@ public class UserTokenUtil {
// 生成MD5 16进制码 用于缩减存储
// 删除相关信息
String username = getUserNameByToken(token);
boolean hashKey = redisPlugin.sHashKey(CacheUtil.getPrefixName() + TICKET_PREFIX + username, token);
boolean hashKey = redisPlugin.sHashKey(
CacheUtil.getPrefixName() + TICKET_PREFIX + username, token);
if(!hashKey){
return false;
}
// JWT 自带过期校验 无需多做处理
// 3. 校验通过后 如果开启续命模式 则整体延长登录时效
if(BooleanUtil.isTrue(LOGIN_PROPERTIES.getReviveMode())){
// 设置该用户全部token失效时间 如果这时又有新设备登录 则续命
redisPlugin.expire(
CacheUtil.getPrefixName() + TICKET_PREFIX + username,
JwtUtil.EXPIRE_MILLISECOND, TimeUnit.MILLISECONDS);
}
} catch (Exception e){
return false;
@ -248,9 +261,10 @@ public class UserTokenUtil {
*/
public static void verifyLockAccount(String username){
// 判断账号是否临时锁定
Long loseTimeMillis = (Long) redisPlugin.get(CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username);
Long loseTimeMillis = (Long) redisPlugin.get(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username);
if(loseTimeMillis != null){
Date currDate = new Date();
Date currDate = DateUtil.date();
DateTime loseDate = DateUtil.date(loseTimeMillis);
// 偏移5分钟
DateTime currLoseDate = DateUtil.offsetSecond(loseDate, LOGIN_PROPERTIES.getSlipLockSpeed());
@ -277,20 +291,24 @@ public class UserTokenUtil {
*/
public static TokenMsg lockAccount(String username){
// 如果失败次数 超过阈值 则锁定账号
Long slipNum = redisPlugin.increment(CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username);
Long slipNum = redisPlugin.increment(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username);
if (slipNum != null){
// 设置失效时间为 5分钟
redisPlugin.expire(CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username, LOGIN_PROPERTIES.getSlipLockSpeed());
redisPlugin.expire(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username, LOGIN_PROPERTIES.getSlipLockSpeed());
// 如果确认 都失败 则存入临时缓存
if(slipNum >= LOGIN_PROPERTIES.getSlipCount()){
long currentTimeMillis = System.currentTimeMillis();
// 存入Redis
redisPlugin.put(CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username,
redisPlugin.put(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username,
currentTimeMillis, LOGIN_PROPERTIES.getSlipLockSpeed());
}
}
return TokenMsg.EXCEPTION_LOGIN_ACCOUNT_NO;
}
@ -300,7 +318,8 @@ public class UserTokenUtil {
*/
public static long getSlipCount(String username){
long count = 0L;
Object obj = redisPlugin.get(CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username);
Object obj = redisPlugin.get(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username);
if(obj != null){
try {
count = Convert.convert(Long.class, obj);
@ -316,9 +335,11 @@ public class UserTokenUtil {
*/
public static void clearLockAccount(String username){
// 删除失败次数记录
redisPlugin.del(CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username);
redisPlugin.del(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_COUNT_PREFIX + username);
// 删除失败次数记录
redisPlugin.del(CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username);
redisPlugin.del(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username);
}
@ -373,4 +394,5 @@ public class UserTokenUtil {
private Long endTimestamp;
}
}

@ -122,6 +122,13 @@
"description": "认证类 Token 排除URL."
},
{
"name": "opsli.auth.login.revive-mode",
"sourceType": "org.opsli.core.autoconfigure.properties.GlobalProperties$Auth$Login",
"type": "java.lang.Boolean",
"defaultValue": false,
"description": "续命模式 (开启续命模式后 在有效时间内 访问任意接口 则自动续命)."
},
{
"name": "opsli.auth.login.limit-count",
"sourceType": "org.opsli.core.autoconfigure.properties.GlobalProperties$Auth$Login",

@ -197,6 +197,8 @@ opsli:
# 登录设置
login:
# 续命模式 (开启续命模式后 在有效时间内 访问任意接口 则自动续命)
revive-mode: false
# 限制登录数量 -1 为无限大
limit-count: -1
# 限制登录拒绝策略 after为后者 before为前者

Loading…
Cancel
Save