Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ public interface CommonRedisKey {
String LOGIN_SALT= "login:salt:%s";

/**
* 登陆user hash key
* 登陆用户username token
*/
String LOGIN_USER_HASH = "login:user:hash";
String LOGIN_USER_TOKEN = "login:user:token:%s:%s";

}
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,11 @@ public interface LoginRedisService {
* @return
*/
boolean exists(String token);

/**
* 删除用户所有登陆缓存
* @param username
*/
void deleteUserAllCache(String username);

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,26 @@
package io.geekidea.springbootplus.shiro.cache.impl;

import io.geekidea.springbootplus.common.constant.CommonRedisKey;
import io.geekidea.springbootplus.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.shiro.cache.LoginRedisService;
import io.geekidea.springbootplus.shiro.convert.ShiroMapstructConvert;
import io.geekidea.springbootplus.shiro.jwt.JwtProperties;
import io.geekidea.springbootplus.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.shiro.vo.ClientInfo;
import io.geekidea.springbootplus.shiro.vo.JwtTokenRedisVo;
import io.geekidea.springbootplus.shiro.vo.LoginSysUserRedisVo;
import io.geekidea.springbootplus.shiro.vo.LoginSysUserVo;
import io.geekidea.springbootplus.util.ClientInfoUtil;
import io.geekidea.springbootplus.util.HttpServletRequestUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.List;
import java.util.Set;

/**
* 登陆信息Redis缓存服务类
Expand All @@ -41,6 +45,9 @@
@Service
public class LoginRedisServiceImpl implements LoginRedisService {

@Autowired
private JwtProperties jwtProperties;

@Autowired
private RedisTemplate redisTemplate;

Expand Down Expand Up @@ -84,13 +91,21 @@ public void cacheLoginInfo(JwtToken jwtToken, LoginSysUserVo loginSysUserVo, boo
// Redis过期时间与JwtToken过期时间一致
Duration expireDuration = Duration.ofSeconds(jwtToken.getExpireSecond());

// 判断是否启用单个用户登陆,如果是,这每个用户只有一个有效token
boolean singleLogin = jwtProperties.isSingleLogin();
if (singleLogin) {
deleteUserAllCache(username);
}

// 1. tokenMd5:jwtTokenRedisVo
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5), jwtTokenRedisVo, expireDuration);
String loginTokenRedisKey = String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5);
redisTemplate.opsForValue().set(loginTokenRedisKey, jwtTokenRedisVo, expireDuration);
// 2. username:loginSysUserRedisVo
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER, username), loginSysUserRedisVo, expireDuration);
// 3. salt hash,方便获取盐值鉴权
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_SALT, username), salt, expireDuration);

// 4. login user token
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5), loginTokenRedisKey, expireDuration);
}

@Override
Expand Down Expand Up @@ -134,6 +149,8 @@ public void deleteLoginInfo(String token, String username) {
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
// 3. delete salt
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
// 4. delete user token
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5));
}

@Override
Expand All @@ -145,4 +162,23 @@ public boolean exists(String token) {
Object object = redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5));
return object != null;
}

@Override
public void deleteUserAllCache(String username) {
Set<String> userTokenMd5Set = redisTemplate.keys(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "*"));
if (CollectionUtils.isEmpty(userTokenMd5Set)) {
return;
}

// 1. 删除登陆用户的所有token信息
List<String> tokenMd5List = redisTemplate.opsForValue().multiGet(userTokenMd5Set);
redisTemplate.delete(tokenMd5List);
// 2. 删除登陆用户的所有user:token信息
redisTemplate.delete(userTokenMd5Set);
// 3. 删除登陆用户信息
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
// 4. 删除登陆用户盐值信息
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,17 @@ protected AuthenticationToken createToken(ServletRequest servletRequest, Servlet
throw new AuthenticationException("token不能为空");
}
if (JwtUtil.isExpired(token)) {
throw new AuthenticationException("token已过期,token:" + token);
throw new AuthenticationException("JWT Token已过期,token:" + token);
}

// 如果开启redis二次校验,或者设置为单个用户token登陆,则先在redis中判断token是否存在
if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) {
boolean redisExpired = loginRedisService.exists(token);
if (!redisExpired) {
throw new AuthenticationException("Redis Token不存在,token:" + token);
}
}

String username = JwtUtil.getUsername(token);
String salt = loginRedisService.getSalt(username);
return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,14 @@ public class JwtProperties {
*/
private Integer refreshTokenCountdown;

/**
* redis校验jwt token是否存在
*/
private boolean redisCheck;

/**
* 单用户登陆,一个用户只能又一个有效的token
*/
private boolean singleLogin;

}
8 changes: 6 additions & 2 deletions src/main/resources/config/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,13 @@ spring-boot-plus:
# 默认过期时间1小时,单位:秒
expire-second: 3600
# 是否刷新token
refresh_token: true
refresh-token: true
# 刷新token的时间间隔,默认10分钟,单位:秒
refresh_token_countdown: 600
refresh-token-countdown: 600
# redis校验jwt token是否存在,可选
redis-check: true
# true: 同一个账号只能是最后一次登陆token有效,false:同一个账号可多次登陆
single-login: false
############################ JWT end ###############################

############################### spring-boot-plus end ###############################
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.geekidea.springbootplus.test;

import io.geekidea.springbootplus.common.constant.CommonRedisKey;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.Set;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTemplateTest {
Expand All @@ -15,10 +19,25 @@ public class RedisTemplateTest {
private RedisTemplate redisTemplate;

@Test
public void put(){
redisTemplate.opsForHash().increment("hello","world",1);
System.out.println(redisTemplate.opsForHash().get("hello","world"));
redisTemplate.opsForHash().increment("hello","world",-1);
System.out.println(redisTemplate.opsForHash().get("hello","world"));
public void put() {

String username = "junit-test-admin";
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "111"), "111");
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "222"), "222");
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "333"), "333");

Set<String> set = redisTemplate.keys(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "*"));
System.out.println("set = " + set);

List<String> list = redisTemplate.opsForValue().multiGet(set);
System.out.println("list = " + list);

Long listResult = redisTemplate.delete(list);
Long setResult = redisTemplate.delete(set);
System.out.println("listResult = " + listResult);
System.out.println("setResult = " + setResult);

Long count = redisTemplate.countExistingKeys(set);
System.out.println("count = " + count);
}
}