Skip to content

Commit d47f9ec

Browse files
Merge pull request #63 from geekidea/dev
🇨🇳 1.3.0.RELEASE shiro+jwt
2 parents 7032c12 + e358540 commit d47f9ec

File tree

7 files changed

+98
-13
lines changed

7 files changed

+98
-13
lines changed

src/main/java/io/geekidea/springbootplus/common/constant/CommonRedisKey.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ public interface CommonRedisKey {
4141
String LOGIN_SALT= "login:salt:%s";
4242

4343
/**
44-
* 登陆user hash key
44+
* 登陆用户username token
4545
*/
46-
String LOGIN_USER_HASH = "login:user:hash";
46+
String LOGIN_USER_TOKEN = "login:user:token:%s:%s";
4747

4848
}

src/main/java/io/geekidea/springbootplus/shiro/cache/LoginRedisService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,11 @@ public interface LoginRedisService {
6868
* @return
6969
*/
7070
boolean exists(String token);
71+
72+
/**
73+
* 删除用户所有登陆缓存
74+
* @param username
75+
*/
76+
void deleteUserAllCache(String username);
77+
7178
}

src/main/java/io/geekidea/springbootplus/shiro/cache/impl/LoginRedisServiceImpl.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,26 @@
1414
package io.geekidea.springbootplus.shiro.cache.impl;
1515

1616
import io.geekidea.springbootplus.common.constant.CommonRedisKey;
17-
import io.geekidea.springbootplus.shiro.jwt.JwtToken;
1817
import io.geekidea.springbootplus.shiro.cache.LoginRedisService;
1918
import io.geekidea.springbootplus.shiro.convert.ShiroMapstructConvert;
19+
import io.geekidea.springbootplus.shiro.jwt.JwtProperties;
20+
import io.geekidea.springbootplus.shiro.jwt.JwtToken;
2021
import io.geekidea.springbootplus.shiro.vo.ClientInfo;
2122
import io.geekidea.springbootplus.shiro.vo.JwtTokenRedisVo;
2223
import io.geekidea.springbootplus.shiro.vo.LoginSysUserRedisVo;
2324
import io.geekidea.springbootplus.shiro.vo.LoginSysUserVo;
2425
import io.geekidea.springbootplus.util.ClientInfoUtil;
2526
import io.geekidea.springbootplus.util.HttpServletRequestUtil;
2627
import org.apache.commons.codec.digest.DigestUtils;
28+
import org.apache.commons.collections4.CollectionUtils;
2729
import org.apache.commons.lang3.StringUtils;
2830
import org.springframework.beans.factory.annotation.Autowired;
2931
import org.springframework.data.redis.core.RedisTemplate;
3032
import org.springframework.stereotype.Service;
3133

3234
import java.time.Duration;
35+
import java.util.List;
36+
import java.util.Set;
3337

3438
/**
3539
* 登陆信息Redis缓存服务类
@@ -41,6 +45,9 @@
4145
@Service
4246
public class LoginRedisServiceImpl implements LoginRedisService {
4347

48+
@Autowired
49+
private JwtProperties jwtProperties;
50+
4451
@Autowired
4552
private RedisTemplate redisTemplate;
4653

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

94+
// 判断是否启用单个用户登陆,如果是,这每个用户只有一个有效token
95+
boolean singleLogin = jwtProperties.isSingleLogin();
96+
if (singleLogin) {
97+
deleteUserAllCache(username);
98+
}
99+
87100
// 1. tokenMd5:jwtTokenRedisVo
88-
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5), jwtTokenRedisVo, expireDuration);
101+
String loginTokenRedisKey = String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5);
102+
redisTemplate.opsForValue().set(loginTokenRedisKey, jwtTokenRedisVo, expireDuration);
89103
// 2. username:loginSysUserRedisVo
90104
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER, username), loginSysUserRedisVo, expireDuration);
91105
// 3. salt hash,方便获取盐值鉴权
92106
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_SALT, username), salt, expireDuration);
93-
107+
// 4. login user token
108+
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5), loginTokenRedisKey, expireDuration);
94109
}
95110

96111
@Override
@@ -134,6 +149,8 @@ public void deleteLoginInfo(String token, String username) {
134149
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
135150
// 3. delete salt
136151
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
152+
// 4. delete user token
153+
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, tokenMd5));
137154
}
138155

139156
@Override
@@ -145,4 +162,23 @@ public boolean exists(String token) {
145162
Object object = redisTemplate.opsForValue().get(String.format(CommonRedisKey.LOGIN_TOKEN, tokenMd5));
146163
return object != null;
147164
}
165+
166+
@Override
167+
public void deleteUserAllCache(String username) {
168+
Set<String> userTokenMd5Set = redisTemplate.keys(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "*"));
169+
if (CollectionUtils.isEmpty(userTokenMd5Set)) {
170+
return;
171+
}
172+
173+
// 1. 删除登陆用户的所有token信息
174+
List<String> tokenMd5List = redisTemplate.opsForValue().multiGet(userTokenMd5Set);
175+
redisTemplate.delete(tokenMd5List);
176+
// 2. 删除登陆用户的所有user:token信息
177+
redisTemplate.delete(userTokenMd5Set);
178+
// 3. 删除登陆用户信息
179+
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_USER, username));
180+
// 4. 删除登陆用户盐值信息
181+
redisTemplate.delete(String.format(CommonRedisKey.LOGIN_SALT, username));
182+
}
183+
148184
}

src/main/java/io/geekidea/springbootplus/shiro/jwt/JwtFilter.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,17 @@ protected AuthenticationToken createToken(ServletRequest servletRequest, Servlet
7070
throw new AuthenticationException("token不能为空");
7171
}
7272
if (JwtUtil.isExpired(token)) {
73-
throw new AuthenticationException("token已过期,token:" + token);
73+
throw new AuthenticationException("JWT Token已过期,token:" + token);
7474
}
75+
76+
// 如果开启redis二次校验,或者设置为单个用户token登陆,则先在redis中判断token是否存在
77+
if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) {
78+
boolean redisExpired = loginRedisService.exists(token);
79+
if (!redisExpired) {
80+
throw new AuthenticationException("Redis Token不存在,token:" + token);
81+
}
82+
}
83+
7584
String username = JwtUtil.getUsername(token);
7685
String salt = loginRedisService.getSalt(username);
7786
return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond());

src/main/java/io/geekidea/springbootplus/shiro/jwt/JwtProperties.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,14 @@ public class JwtProperties {
6969
*/
7070
private Integer refreshTokenCountdown;
7171

72+
/**
73+
* redis校验jwt token是否存在
74+
*/
75+
private boolean redisCheck;
76+
77+
/**
78+
* 单用户登陆,一个用户只能又一个有效的token
79+
*/
80+
private boolean singleLogin;
81+
7282
}

src/main/resources/config/application.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,13 @@ spring-boot-plus:
120120
# 默认过期时间1小时,单位:秒
121121
expire-second: 3600
122122
# 是否刷新token
123-
refresh_token: true
123+
refresh-token: true
124124
# 刷新token的时间间隔,默认10分钟,单位:秒
125-
refresh_token_countdown: 600
125+
refresh-token-countdown: 600
126+
# redis校验jwt token是否存在,可选
127+
redis-check: true
128+
# true: 同一个账号只能是最后一次登陆token有效,false:同一个账号可多次登陆
129+
single-login: false
126130
############################ JWT end ###############################
127131

128132
############################### spring-boot-plus end ###############################
Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package io.geekidea.springbootplus.test;
22

3+
import io.geekidea.springbootplus.common.constant.CommonRedisKey;
34
import org.junit.Test;
45
import org.junit.runner.RunWith;
56
import org.springframework.beans.factory.annotation.Autowired;
67
import org.springframework.boot.test.context.SpringBootTest;
78
import org.springframework.data.redis.core.RedisTemplate;
89
import org.springframework.test.context.junit4.SpringRunner;
910

11+
import java.util.List;
12+
import java.util.Set;
13+
1014
@RunWith(SpringRunner.class)
1115
@SpringBootTest
1216
public class RedisTemplateTest {
@@ -15,10 +19,25 @@ public class RedisTemplateTest {
1519
private RedisTemplate redisTemplate;
1620

1721
@Test
18-
public void put(){
19-
redisTemplate.opsForHash().increment("hello","world",1);
20-
System.out.println(redisTemplate.opsForHash().get("hello","world"));
21-
redisTemplate.opsForHash().increment("hello","world",-1);
22-
System.out.println(redisTemplate.opsForHash().get("hello","world"));
22+
public void put() {
23+
24+
String username = "junit-test-admin";
25+
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "111"), "111");
26+
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "222"), "222");
27+
redisTemplate.opsForValue().set(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "333"), "333");
28+
29+
Set<String> set = redisTemplate.keys(String.format(CommonRedisKey.LOGIN_USER_TOKEN, username, "*"));
30+
System.out.println("set = " + set);
31+
32+
List<String> list = redisTemplate.opsForValue().multiGet(set);
33+
System.out.println("list = " + list);
34+
35+
Long listResult = redisTemplate.delete(list);
36+
Long setResult = redisTemplate.delete(set);
37+
System.out.println("listResult = " + listResult);
38+
System.out.println("setResult = " + setResult);
39+
40+
Long count = redisTemplate.countExistingKeys(set);
41+
System.out.println("count = " + count);
2342
}
2443
}

0 commit comments

Comments
 (0)