新增登录Token续命模式

v1.4.1
hiparker 4 years ago
parent e7c9212765
commit dbd9373a4d

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

@ -1,8 +1,10 @@
package org.opsli.core.utils; package org.opsli.core.utils;
import cn.hutool.core.codec.Base64; import cn.hutool.core.codec.Base64;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT; import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTDecodeException;
@ -37,7 +39,7 @@ public class JwtUtil {
/** /**
* 120 * 120
*/ */
public static long EXPIRE; public static int EXPIRE_MILLISECOND;
/** /**
* JWT(Base64) * JWT(Base64)
@ -80,23 +82,31 @@ public class JwtUtil {
* @param tokenType token * @param tokenType token
* @param account * @param account
* @param userId ID * @param userId ID
* @param isExpire
* @return java.lang.String Token * @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私钥加密 // 帐号加JWT私钥加密
String secret = account + Base64.decodeStr(ENCRYPT_JWT_INITIAL_SECRET); String secret = account + Base64.decodeStr(ENCRYPT_JWT_INITIAL_SECRET);
// 此处过期时间是以毫秒为单位所以乘以1000
Date date = new Date(System.currentTimeMillis() + EXPIRE);
Algorithm algorithm = Algorithm.HMAC256(secret); Algorithm algorithm = Algorithm.HMAC256(secret);
// 附带account帐号信息 JWTCreator.Builder builder = JWT.create()
return JWT.create()
.withClaim(SignConstants.TYPE, tokenType) .withClaim(SignConstants.TYPE, tokenType)
.withClaim(SignConstants.ACCOUNT, account) .withClaim(SignConstants.ACCOUNT, account)
.withClaim(SignConstants.USER_ID, userId) .withClaim(SignConstants.USER_ID, userId)
.withClaim(SignConstants.TIMESTAMP, String.valueOf(System.currentTimeMillis())) .withClaim(SignConstants.TIMESTAMP, String.valueOf(currentTimeMillis));
// token 过期时间
.withExpiresAt(date) // 如果为是 则开启失效时间
.sign(algorithm); 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 && globalProperties.getAuth().getToken() != null
){ ){
// 获得 Token失效时间 // 获得 Token失效时间
JwtUtil.EXPIRE = globalProperties.getAuth() JwtUtil.EXPIRE_MILLISECOND = globalProperties.getAuth()
.getToken().getEffectiveTime() * 60 * 1000; .getToken().getEffectiveTime() * 60 * 1000;
// 获得 Token初始盐值 // 获得 Token初始盐值
@ -125,7 +135,7 @@ public class JwtUtil {
// ================== // ==================
public static void main(String[] args) { 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); boolean verify = JwtUtil.verify(token);
System.out.println(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.DateTime;
import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ -43,6 +44,7 @@ import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit;
import static org.opsli.common.constants.OrderConstants.UTIL_ORDER; import static org.opsli.common.constants.OrderConstants.UTIL_ORDER;
@ -90,7 +92,8 @@ public class UserTokenUtil {
// 如果当前登录开启 数量限制 // 如果当前登录开启 数量限制
if(LOGIN_PROPERTIES.getLimitCount() > ACCOUNT_LIMIT_INFINITE){ if(LOGIN_PROPERTIES.getLimitCount() > ACCOUNT_LIMIT_INFINITE){
// 当前用户已存在 Token数量 // 当前用户已存在 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(ticketLen !=null && ticketLen >= LOGIN_PROPERTIES.getLimitCount()){
// 如果是拒绝后者 则直接抛出异常 // 如果是拒绝后者 则直接抛出异常
if(LoginLimitRefuse.AFTER == LOGIN_PROPERTIES.getLimitRefuse()){ if(LoginLimitRefuse.AFTER == LOGIN_PROPERTIES.getLimitRefuse()){
@ -104,29 +107,28 @@ public class UserTokenUtil {
} }
} }
// 生效时间 // 开启续命模式 如果为续命模式 则不指定Token 的 失效时间
int expire = Integer.parseInt(
String.valueOf(JwtUtil.EXPIRE)
);
// 生成 Token 包含 username userId timestamp // 生成 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( long timestamp = Convert.toLong(
JwtUtil.getClaim(signToken, SignConstants.TIMESTAMP)); JwtUtil.getClaim(signToken, SignConstants.TIMESTAMP));
DateTime currDate = DateUtil.date(timestamp);
// 获得失效偏移量时间 // 获得失效偏移量时间
DateTime dateTime = DateUtil.offsetMillisecond(currDate, expire); long endTimestamp = DateUtil.offsetMillisecond(
long endTimestamp = dateTime.getTime(); DateUtil.date(timestamp), JwtUtil.EXPIRE_MILLISECOND).getTime();
// 在redis存一份 token 是为了防止 人为造假 // 在redis存一份 token 是为了防止 人为造假
// 保存用户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){ if(saveLong != null && saveLong > 0){
// 设置该用户全部token失效时间 如果这时又有新设备登录 则续命 // 设置该用户全部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 tokenRet = new TokenRet();
tokenRet.setToken(signToken); tokenRet.setToken(signToken);
@ -192,10 +194,12 @@ public class UserTokenUtil {
UserModel user = UserUtil.getUser(userId); UserModel user = UserUtil.getUser(userId);
if(user != null){ if(user != null){
// 删除Token信息 // 删除Token信息
redisPlugin.sRemove(CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), token); redisPlugin.sRemove(
CacheUtil.getPrefixName() + TICKET_PREFIX + user.getUsername(), token);
// 如果缓存中 无该用户任何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){ if(size == null || size == 0L){
// 删除相关信息 // 删除相关信息
UserUtil.refreshUser(user); UserUtil.refreshUser(user);
@ -228,11 +232,20 @@ public class UserTokenUtil {
// 生成MD5 16进制码 用于缩减存储 // 生成MD5 16进制码 用于缩减存储
// 删除相关信息 // 删除相关信息
String username = getUserNameByToken(token); 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){ if(!hashKey){
return false; 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){ } catch (Exception e){
return false; return false;
@ -248,9 +261,10 @@ public class UserTokenUtil {
*/ */
public static void verifyLockAccount(String username){ 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){ if(loseTimeMillis != null){
Date currDate = new Date(); Date currDate = DateUtil.date();
DateTime loseDate = DateUtil.date(loseTimeMillis); DateTime loseDate = DateUtil.date(loseTimeMillis);
// 偏移5分钟 // 偏移5分钟
DateTime currLoseDate = DateUtil.offsetSecond(loseDate, LOGIN_PROPERTIES.getSlipLockSpeed()); DateTime currLoseDate = DateUtil.offsetSecond(loseDate, LOGIN_PROPERTIES.getSlipLockSpeed());
@ -277,20 +291,24 @@ public class UserTokenUtil {
*/ */
public static TokenMsg lockAccount(String username){ 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){ if (slipNum != null){
// 设置失效时间为 5分钟 // 设置失效时间为 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()){ if(slipNum >= LOGIN_PROPERTIES.getSlipCount()){
long currentTimeMillis = System.currentTimeMillis(); long currentTimeMillis = System.currentTimeMillis();
// 存入Redis // 存入Redis
redisPlugin.put(CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username, redisPlugin.put(
CacheUtil.getPrefixName() + ACCOUNT_SLIP_LOCK_PREFIX + username,
currentTimeMillis, LOGIN_PROPERTIES.getSlipLockSpeed()); currentTimeMillis, LOGIN_PROPERTIES.getSlipLockSpeed());
} }
} }
return TokenMsg.EXCEPTION_LOGIN_ACCOUNT_NO; return TokenMsg.EXCEPTION_LOGIN_ACCOUNT_NO;
} }
@ -300,7 +318,8 @@ public class UserTokenUtil {
*/ */
public static long getSlipCount(String username){ public static long getSlipCount(String username){
long count = 0L; 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){ if(obj != null){
try { try {
count = Convert.convert(Long.class, obj); count = Convert.convert(Long.class, obj);
@ -316,9 +335,11 @@ public class UserTokenUtil {
*/ */
public static void clearLockAccount(String username){ 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; private Long endTimestamp;
} }
} }

@ -122,6 +122,13 @@
"description": "认证类 Token 排除URL." "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", "name": "opsli.auth.login.limit-count",
"sourceType": "org.opsli.core.autoconfigure.properties.GlobalProperties$Auth$Login", "sourceType": "org.opsli.core.autoconfigure.properties.GlobalProperties$Auth$Login",

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

Loading…
Cancel
Save