抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

SpringBoot整合Shiro(五)

整合redis作为缓存

​ 本来的整合过程是顺着博客的顺序来的,越往下,集成的越多,由于之前是使用ehcache缓存,现在改为redis,限制登录人数 以及 限制登录次数等 都需要改动,本篇为了简单,目前先将这两个功能下线,配置暂时是注销的,原类保存,在下篇博客中改。
​ 还有之前是使用SessionListener监听session创建来统计在线人数,在本篇中也将改为统计redis中的key数目。
如果是单机,使用ehcache是最快的,项目一般都不是单节点,为了方便之后使用sso单点登录,以及多节点部署,所以使用shiro整合redis。

整合Redis

参考整合Redis

删除Ehcache

POM文件修改

去掉shiro-ehcache依赖

删除Ehcache配置

删除ehcache-shiro.xml

创建代理类

因为通过@Bean的方式初始化一些组件会产生业务Bean的污染,需要进行采用代理的方式屏蔽依赖污染。

ApplicationUtils

Application相关工具类,使用getBean的方式来获取实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ApplicationUtils {

private static ApplicationContext appContext = null;


public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
appContext = applicationContext;
}

public static Object getBean(String beanId) {
Object bean = null;
if (null != appContext) {
bean = appContext.getBean(beanId);
}
return bean;
}

public static <T> T getBean(Class clazz) {
Object bean = null;
if (null != appContext) {
bean = appContext.getBean(clazz);
}
return (T) bean;
}


public static UserMapper getUserMapper() {
return ApplicationUtils.getBean(UserMapper.class);
}

public static RoleMapper getRoleMapper() {
return ApplicationUtils.getBean(RoleMapper.class);
}

public static PermissionMapper getPermissionMapper() {
return ApplicationUtils.getBean(PermissionMapper.class);
}

public static RedisManagerImpl getRedisManager() {
return ApplicationUtils.getBean(RedisManagerImpl.class);
}
}
ApplicationContexConfig

实现applicationContext的启动注册

1
2
3
4
5
6
7
@Component
public class ApplicationContexConfig implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationUtils.setApplicationContext(applicationContext);
}
}
UserServiceAgent

用户操作的代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserServiceAgent implements UserService {


@Override
public User findByUserName(String userName) {
return ApplicationUtils.getUserMapper().findByUserName(userName);
}

@Override
public int insert(User user) {
return ApplicationUtils.getUserMapper().insert(user);
}

@Override
public int update(User user) {
return ApplicationUtils.getUserMapper().update(user);
}

@Override
public int del(String username) {
return ApplicationUtils.getUserMapper().del(username);
}
}

RoleServiceAgent

角色代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RoleServiceAgent implements RoleService {


@Override
public Set<Role> findRolesByUserId(Integer uid) {
return ApplicationUtils.getRoleMapper().findRolesByUserId(uid);
}

@Override
public void delPermission(int roleId, int permissionId) {
ApplicationUtils.getRoleMapper().delPermission(roleId, permissionId);
}

@Override
public void addPermission(int roleId, int permissionId) {
ApplicationUtils.getRoleMapper().addPermission(roleId, permissionId);
}
}
PermissionServiceAgent

权限代理类

1
2
3
4
5
6
7
public class PermissionServiceAgent implements PermissionService {

@Override
public Set<Permission> findPermissionsByRoleId(Set<Role> roles) {
return ApplicationUtils.getPermissionMapper().findPermissionsByRoleId(roles);
}
}
RedisManagerAgent

为了解决初始化bean 需要依赖redis 造成的 bean过早被初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class RedisManagerAgent implements RedisManager {

/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public void expire(String key, long time) {
ApplicationUtils.getRedisManager().expire(key, time);
}

/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return ApplicationUtils.getRedisManager().hasKey(key);
}

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
ApplicationUtils.getRedisManager().del(key);
}

/**
* 批量删除key
*
* @param keys
*/
public void del(Collection keys) {
ApplicationUtils.getRedisManager().del(keys);
}

//============================String=============================

/**
* 普通缓存获取
*
* @param key 键
* @return
*/
public Object get(String key) {
return ApplicationUtils.getRedisManager().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
*/
public void set(String key, Object value) {
ApplicationUtils.getRedisManager().set(key, value);
}

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
*/
public void set(String key, Object value, long time) {
ApplicationUtils.getRedisManager().set(key, value, time);
}

/**
* 使用scan命令 查询某些前缀的key
*
* @param key
* @return
*/
public Set<String> scan(String key) {
return ApplicationUtils.getRedisManager().scan(key);
}

/**
* 使用scan命令 查询某些前缀的key 有多少个
* 用来获取当前session数量,也就是在线用户
*
* @param key
* @return
*/
public Long scanSize(String key) {
return ApplicationUtils.getRedisManager().scanSize(key);
}
}

相关依赖类

ShiroSession

由于SimpleSession lastAccessTime更改后也会调用SessionDao update方法,更新的字段只有LastAccessTime(最后一次访问时间),由于会话失效是由Redis数据过期实现的,这个字段意义不大,为了减少对Redis的访问,降低网络压力,实现自己的Session,在SimpleSession上套一层,增加一个标识位,如果Session除lastAccessTime意外其它字段修改,就标识一下,只有标识为修改的才可以通过doUpdate访问Redis,否则直接返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public class ShiroSession extends SimpleSession implements Serializable {
// 除lastAccessTime以外其他字段发生改变时为true
private boolean isChanged = false;

public ShiroSession() {
super();
this.setChanged(true);
}

public ShiroSession(String host) {
super(host);
this.setChanged(true);
}


@Override
public void setId(Serializable id) {
super.setId(id);
this.setChanged(true);
}

@Override
public void setStopTimestamp(Date stopTimestamp) {
super.setStopTimestamp(stopTimestamp);
this.setChanged(true);
}

@Override
public void setExpired(boolean expired) {
super.setExpired(expired);
this.setChanged(true);
}

@Override
public void setTimeout(long timeout) {
super.setTimeout(timeout);
this.setChanged(true);
}

@Override
public void setHost(String host) {
super.setHost(host);
this.setChanged(true);
}

@Override
public void setAttributes(Map<Object, Object> attributes) {
super.setAttributes(attributes);
this.setChanged(true);
}

@Override
public void setAttribute(Object key, Object value) {
super.setAttribute(key, value);
this.setChanged(true);
}

@Override
public Object removeAttribute(Object key) {
this.setChanged(true);
return super.removeAttribute(key);
}

/**
* 停止
*/
@Override
public void stop() {
super.stop();
this.setChanged(true);
}

/**
* 设置过期
*/
@Override
protected void expire() {
this.stop();
this.setExpired(true);
}

public boolean isChanged() {
return isChanged;
}

public void setChanged(boolean isChanged) {
this.isChanged = isChanged;
}

@Override
public boolean equals(Object obj) {
return super.equals(obj);
}

@Override
protected boolean onEquals(SimpleSession ss) {
return super.onEquals(ss);
}

@Override
public int hashCode() {
return super.hashCode();
}

@Override
public String toString() {
return super.toString();
}
}
SessionInMemory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SessionInMemory {
private Session session;
private Date createTime;

public Session getSession() {
return session;
}

public void setSession(Session session) {
this.session = session;
}

public Date getCreateTime() {
return createTime;
}

public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}

ShiroSessionFactory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ShiroSessionFactory implements SessionFactory {
private static final Logger logger = LoggerFactory.getLogger(ShiroSessionFactory.class);

@Override
public Session createSession(SessionContext initData) {
ShiroSession session = new ShiroSession();
HttpServletRequest request = (HttpServletRequest)initData.get(DefaultWebSessionContext.class.getName() + ".SERVLET_REQUEST");
session.setHost(getIpAddress(request));
return session;
}

public static String getIpAddress(HttpServletRequest request) {
String localIP = "127.0.0.1";
String ip = request.getHeader("x-forwarded-for");
if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(ip) || (ip.equalsIgnoreCase(localIP)) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
ShiroSessionListener

创建Session监听器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class ShiroSessionListener implements SessionListener {

/*
* 统计在线人数
* juc包下线程安全自增
*/

private final AtomicInteger sessionCount = new AtomicInteger(0);

/*
* 会话创建时触发
*
* @param session
*/
@Override
public void onStart(Session session) {
//会话创建,在线人数加一
sessionCount.incrementAndGet();
}

/**
* 退出会话时触发
*
* @param session
*/

@Override

public void onStop(Session session) {
//会话退出,在线人数减一
sessionCount.decrementAndGet();
}

/**
* 会话过期时触发
*
* @param session
*/

@Override

public void onExpiration(Session session) {
//会话过期,在线人数减一
sessionCount.decrementAndGet();
}

/**
* 获取在线人数使用
*
* @return
*/

public int getSessionCount() {
return sessionCount.get();
}
}
重写密码比较器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class RetryLimitHashedCredentialsMatcher extends SimpleCredentialsMatcher {

private static final Logger logger = LoggerFactory.getLogger(RetryLimitHashedCredentialsMatcher.class);

public static final String DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX = "shiro:cache:retrylimit:";
private String keyPrefix = DEFAULT_RETRYLIMIT_CACHE_KEY_PREFIX;


private UserServiceAgent userServiceAgent = new UserServiceAgent();
private RedisManager redisManager;

public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}

private String getRedisKickoutKey(String username) {
return this.keyPrefix + username;
}

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

//获取用户名
String username = (String) token.getPrincipal();
//获取用户登录次数
AtomicInteger retryCount = (AtomicInteger) redisManager.get(getRedisKickoutKey(username));
if (retryCount == null) {
//如果用户没有登陆过,登陆次数加1 并放入缓存
retryCount = new AtomicInteger(0);
}
if (retryCount.incrementAndGet() > 5) {
//如果用户登陆失败次数大于5次 抛出锁定用户异常 并修改数据库字段
User user = userServiceAgent.findByUserName(username);
if (user != null && "0".equals(user.getState())) {
//数据库字段 默认为 0 就是正常状态 所以 要改为1
//修改数据库的状态字段为锁定
user.setState("1");
userServiceAgent.update(user);
}
logger.info("锁定用户" + user.getUsername());
//抛出用户锁定异常
throw new LockedAccountException();
}
//判断用户账号和密码是否正确
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
//如果正确,从缓存中将用户登录计数 清除
redisManager.del(getRedisKickoutKey(username));
}
{
redisManager.set(getRedisKickoutKey(username), retryCount);
}
return matches;
}

/**
* 根据用户名 解锁用户
*
* @param username
* @return
*/
public void unlockAccount(String username) {
User user = userServiceAgent.findByUserName(username);
if (user != null) {
//修改数据库的状态字段为锁定
user.setState("0");
userServiceAgent.update(user);
redisManager.del(getRedisKickoutKey(username));
}
}
}

SerializeUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class SerializeUtils implements RedisSerializer {

private static Logger logger = LoggerFactory.getLogger(SerializeUtils.class);

public static boolean isEmpty(byte[] data) {
return (data == null || data.length == 0);
}

/**
* 序列化
*
* @param object
* @return
* @throws SerializationException
*/
@Override
public byte[] serialize(Object object) throws SerializationException {
byte[] result = null;

if (object == null) {
return new byte[0];
}
try (
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(128);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteStream)
) {

if (!(object instanceof Serializable)) {
throw new IllegalArgumentException(SerializeUtils.class.getSimpleName() + " requires a Serializable payload " +
"but received an object of type [" + object.getClass().getName() + "]");
}

objectOutputStream.writeObject(object);
objectOutputStream.flush();
result = byteStream.toByteArray();
} catch (Exception ex) {
logger.error("Failed to serialize", ex);
}
return result;
}

/**
* 反序列化
*
* @param bytes
* @return
* @throws SerializationException
*/
@Override
public Object deserialize(byte[] bytes) throws SerializationException {

Object result = null;

if (isEmpty(bytes)) {
return null;
}

try (
ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteStream)
) {
result = objectInputStream.readObject();
} catch (Exception e) {
logger.error("Failed to deserialize", e);
}
return result;
}

}

重写并发登录控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/**
* shiro 自定义filter 实现 并发登录控制
* <p>
* 踢出后到的地址
* <p>
* 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
* <p>
* 同一个帐号最大会话数 默认1
* <p>
* 是否允许访问,返回true表示允许
* <p>
* 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。
*/

public class KickoutSessionControlFilter extends AccessControlFilter {


/**
* 踢出后到的地址
*/
private String kickoutUrl;

/**
* 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
*/
private boolean kickoutAfter = false;

/**
* 同一个帐号最大会话数 默认1
*/
private int maxSession = 1;
private SessionManager sessionManager;

private RedisManager redisManager;

public static final String DEFAULT_KICKOUT_CACHE_KEY_PREFIX = "shiro:cache:kickout:";
private String keyPrefix = DEFAULT_KICKOUT_CACHE_KEY_PREFIX;

public void setKickoutUrl(String kickoutUrl) {
this.kickoutUrl = kickoutUrl;
}

public void setKickoutAfter(boolean kickoutAfter) {
this.kickoutAfter = kickoutAfter;
}

public void setMaxSession(int maxSession) {
this.maxSession = maxSession;
}

public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}

public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

private String getRedisKickoutKey(String username) {
return this.keyPrefix + username;
}

/**
* 是否允许访问,返回true表示允许
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}

/**
* 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if (!subject.isAuthenticated() && !subject.isRemembered()) {
//如果没有登录,直接进行之后的流程
return true;
}

//如果有登录,判断是否访问的为静态资源,如果是游客允许访问的静态资源,直接返回true
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String path = httpServletRequest.getServletPath();
// 如果是静态文件,则返回true
if (isStaticFile(path)) {
return true;
}


Session session = subject.getSession();
//这里获取的User是实体 因为我在 自定义ShiroRealm中的doGetAuthenticationInfo方法中
//new SimpleAuthenticationInfo(user, password, getName()); 传的是 User实体 所以这里拿到的也是实体,如果传的是userName 这里拿到的就是userName
String username = ((User) subject.getPrincipal()).getUsername();
Serializable sessionId = session.getId();

// 初始化用户的队列放到缓存里
Deque<Serializable> deque = (Deque<Serializable>) redisManager.get(getRedisKickoutKey(username));
if (deque == null || deque.size() == 0) {
deque = new LinkedList<Serializable>();
}

//如果队列里没有此sessionId,且用户没有被踢出;放入队列
if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
deque.push(sessionId);
}

//如果队列里的sessionId数超出最大会话数,开始踢人
while (deque.size() > maxSession) {
Serializable kickoutSessionId = null;
if (kickoutAfter) { //如果踢出后者
kickoutSessionId = deque.getFirst();
kickoutSessionId = deque.removeFirst();
} else { //否则踢出前者
kickoutSessionId = deque.removeLast();
}
try {
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
if (kickoutSession != null) {
//设置会话的kickout属性表示踢出了
kickoutSession.setAttribute("kickout", true);
}
} catch (Exception e) {//ignore exception
e.printStackTrace();
}
}

redisManager.set(getRedisKickoutKey(username), deque);

//如果被踢出了,直接退出,重定向到踢出后的地址
if (session.getAttribute("kickout") != null) {
//会话被踢出了
try {
subject.logout();
} catch (Exception e) {
}
WebUtils.issueRedirect(request, response, kickoutUrl);
return false;
}
return true;
}

private boolean isStaticFile(String path) {
return true;
}
}

配置Redis管理器

Redis 配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Redis 配置类
*/
@Configuration
public class RedisConfiguration extends CachingConfigurerSupport {


@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
SerializeUtils serializeUtils = new SerializeUtils();
// value值的序列化采用JDK的序列化
template.setValueSerializer(serializeUtils);
template.setHashValueSerializer(serializeUtils);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setConnectionFactory(factory);
return template;
}
}

创建RedisManager接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

public interface RedisManager {


/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public void expire(String key, long time);

/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key);

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/

public void del(String... key);

/**
* 批量删除key
*
* @param keys
*/
public void del(Collection keys);

//============================String=============================

/**
* 普通缓存获取
*
* @param key 键
* @return
*/
public Object get(String key);

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
*/
public void set(String key, Object value);

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
*/
public void set(String key, Object value, long time);

/**
* 使用scan命令 查询某些前缀的key
*
* @param key
* @return
*/
public Set<String> scan(String key);

/**
* 使用scan命令 查询某些前缀的key 有多少个
* 用来获取当前session数量,也就是在线用户
*
* @param key
* @return
*/
public Long scanSize(String key);
}

创建RedisManagerImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* @author wangsaichao
* 基于spring和redis的redisTemplate工具类
*/
@Component("redisManager")
public class RedisManagerImpl implements RedisManager {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

//=============================common============================

/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public void expire(String key, long time) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}

/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}

/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}

/**
* 批量删除key
*
* @param keys
*/
public void del(Collection keys) {
redisTemplate.delete(keys);
}

//============================String=============================

/**
* 普通缓存获取
*
* @param key 键
* @return
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}

/**
* 普通缓存放入
*
* @param key 键
* @param value 值
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
*/
public void set(String key, Object value, long time) {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
}

/**
* 使用scan命令 查询某些前缀的key
*
* @param key
* @return
*/
public Set<String> scan(String key) {
Set<String> execute = this.redisTemplate.execute(new RedisCallback<Set<String>>() {

@Override
public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {

Set<String> binaryKeys = new HashSet<>();

Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key).count(1000).build());
while (cursor.hasNext()) {
binaryKeys.add(new String(cursor.next()));
}
return binaryKeys;
}
});
return execute;
}

/**
* 使用scan命令 查询某些前缀的key 有多少个
* 用来获取当前session数量,也就是在线用户
*
* @param key
* @return
*/
public Long scanSize(String key) {
long dbSize = this.redisTemplate.execute(new RedisCallback<Long>() {

@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
long count = 0L;
Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(1000).build());
while (cursor.hasNext()) {
cursor.next();
count++;
}
return count;
}
});
return dbSize;
}
}
创建创建Redis缓存管理器
创建RedisCache
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
public class RedisCache<K, V> implements Cache<K, V> {

private static Logger logger = LoggerFactory.getLogger(RedisCache.class);

private RedisManager redisManager;
private String keyPrefix = "";
private int expire = 0;
private String principalIdFieldName = RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME;

/**
* Construction
*
* @param redisManager
*/
public RedisCache(RedisManager redisManager, String prefix, int expire, String principalIdFieldName) {
if (redisManager == null) {
throw new IllegalArgumentException("redisManager cannot be null.");
}
this.redisManager = redisManager;
if (prefix != null && !"".equals(prefix)) {
this.keyPrefix = prefix;
}
if (expire != -1) {
this.expire = expire;
}
if (principalIdFieldName != null && !"".equals(principalIdFieldName)) {
this.principalIdFieldName = principalIdFieldName;
}
}

@Override
public V get(K key) throws CacheException {
logger.debug("get key [{}]", key);

if (key == null) {
return null;
}

try {
String redisCacheKey = getRedisCacheKey(key);
Object rawValue = redisManager.get(redisCacheKey);
if (rawValue == null) {
return null;
}
V value = (V) rawValue;
return value;
} catch (Exception e) {
throw new CacheException(e);
}
}

@Override
public V put(K key, V value) throws CacheException {
logger.debug("put key [{}]", key);
if (key == null) {
logger.warn("Saving a null key is meaningless, return value directly without call Redis.");
return value;
}
try {
String redisCacheKey = getRedisCacheKey(key);
redisManager.set(redisCacheKey, value != null ? value : null, expire);
return value;
} catch (Exception e) {
throw new CacheException(e);
}
}

@Override
public V remove(K key) throws CacheException {
logger.debug("remove key [{}]", key);
if (key == null) {
return null;
}
try {
String redisCacheKey = getRedisCacheKey(key);
Object rawValue = redisManager.get(redisCacheKey);
V previous = (V) rawValue;
redisManager.del(redisCacheKey);
return previous;
} catch (Exception e) {
throw new CacheException(e);
}
}

private String getRedisCacheKey(K key) {
if (key == null) {
return null;
}
return this.keyPrefix + getStringRedisKey(key);
}

private String getStringRedisKey(K key) {
String redisKey;
if (key instanceof PrincipalCollection) {
redisKey = getRedisKeyFromPrincipalIdField((PrincipalCollection) key);
} else {
redisKey = key.toString();
}
return redisKey;
}

private String getRedisKeyFromPrincipalIdField(PrincipalCollection key) {
String redisKey;
Object principalObject = key.getPrimaryPrincipal();
Method pincipalIdGetter = null;
Method[] methods = principalObject.getClass().getDeclaredMethods();
for (Method m : methods) {
if (RedisCacheManager.DEFAULT_PRINCIPAL_ID_FIELD_NAME.equals(this.principalIdFieldName)
&& ("getAuthCacheKey".equals(m.getName()) || "getId".equals(m.getName()))) {
pincipalIdGetter = m;
break;
}
if (m.getName().equals("get" + this.principalIdFieldName.substring(0, 1).toUpperCase() + this.principalIdFieldName.substring(1))) {
pincipalIdGetter = m;
break;
}
}
if (pincipalIdGetter == null) {
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName);
}

try {
Object idObj = pincipalIdGetter.invoke(principalObject);
if (idObj == null) {
throw new PrincipalIdNullException(principalObject.getClass(), this.principalIdFieldName);
}
redisKey = idObj.toString();
} catch (IllegalAccessException e) {
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e);
} catch (InvocationTargetException e) {
throw new PrincipalInstanceException(principalObject.getClass(), this.principalIdFieldName, e);
}

return redisKey;
}


@Override
public void clear() throws CacheException {
logger.debug("clear cache");
Set<String> keys = null;
try {
keys = redisManager.scan(this.keyPrefix + "*");
} catch (Exception e) {
logger.error("get keys error", e);
}
if (keys == null || keys.size() == 0) {
return;
}
for (String key : keys) {
redisManager.del(key);
}
}

@Override
public int size() {
Long longSize = 0L;
try {
longSize = new Long(redisManager.scanSize(this.keyPrefix + "*"));
} catch (Exception e) {
logger.error("get keys error", e);
}
return longSize.intValue();
}

@SuppressWarnings("unchecked")
@Override
public Set<K> keys() {
Set<String> keys = null;
try {
keys = redisManager.scan(this.keyPrefix + "*");
} catch (Exception e) {
logger.error("get keys error", e);
return Collections.emptySet();
}

if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}

Set<K> convertedKeys = new HashSet<K>();
for (String key : keys) {
try {
convertedKeys.add((K) key);
} catch (Exception e) {
logger.error("deserialize keys error", e);
}
}
return convertedKeys;
}

@Override
public Collection<V> values() {
Set<String> keys = null;
try {
keys = redisManager.scan(this.keyPrefix + "*");
} catch (Exception e) {
logger.error("get values error", e);
return Collections.emptySet();
}

if (CollectionUtils.isEmpty(keys)) {
return Collections.emptySet();
}

List<V> values = new ArrayList<V>(keys.size());
for (String key : keys) {
V value = null;
try {
value = (V) redisManager.get(key);
} catch (Exception e) {
logger.error("deserialize values= error", e);
}
if (value != null) {
values.add(value);
}
}
return Collections.unmodifiableList(values);
}

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

public String getPrincipalIdFieldName() {
return principalIdFieldName;
}

public void setPrincipalIdFieldName(String principalIdFieldName) {
this.principalIdFieldName = principalIdFieldName;
}
}

​ getRedisKeyFromPrincipalIdField()是获取缓存的用户身份信息 和用户权限信息。 里面有一个属性principalIdFieldName 在RedisCacheManager也有这个属性,设置其中一个就可以.是为了给缓存用户身份和权限信息在Redis中的key唯一,登录用户名可能是username 或者 phoneNum 或者是Email中的一个,如 我的User实体类中 有一个 usernane字段,也是登录时候使用的用户名,在redis中缓存的权限信息key 如下, 这个admin 就是 通过getUsername获得的。

创建RedisCacheManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class RedisCacheManager implements CacheManager {

private final Logger logger = LoggerFactory.getLogger(RedisCacheManager.class);

/**
* fast lookup by name map
*/
private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap<String, Cache>();

private RedisManager redisManager;

/**
* expire time in seconds
*/
private static final int DEFAULT_EXPIRE = 1800;
private int expire = DEFAULT_EXPIRE;

/**
* The Redis key prefix for caches
*/
public static final String DEFAULT_CACHE_KEY_PREFIX = "shiro:cache:";
private String keyPrefix = DEFAULT_CACHE_KEY_PREFIX;

public static final String DEFAULT_PRINCIPAL_ID_FIELD_NAME = "authCacheKey or id";
private String principalIdFieldName = DEFAULT_PRINCIPAL_ID_FIELD_NAME;

@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
logger.debug("get cache, name={}", name);

Cache cache = caches.get(name);

if (cache == null) {
cache = new RedisCache<K, V>(redisManager, keyPrefix + name + ":", expire, principalIdFieldName);
caches.put(name, cache);
}
return cache;
}

public RedisManager getRedisManager() {
return redisManager;
}

public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

public int getExpire() {
return expire;
}

public void setExpire(int expire) {
this.expire = expire;
}

public String getPrincipalIdFieldName() {
return principalIdFieldName;
}

public void setPrincipalIdFieldName(String principalIdFieldName) {
this.principalIdFieldName = principalIdFieldName;
}
}
创建RedisSessionDAO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
public class RedisSessionDAO extends AbstractSessionDAO {

private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);

private static final String DEFAULT_SESSION_KEY_PREFIX = "shiro:session:";
private String keyPrefix = DEFAULT_SESSION_KEY_PREFIX;

private static final long DEFAULT_SESSION_IN_MEMORY_TIMEOUT = 1000L;
/**
* doReadSession be called about 10 times when login.
* Save Session in ThreadLocal to resolve this problem. sessionInMemoryTimeout is expiration of Session in ThreadLocal.
* The default value is 1000 milliseconds (1s).
* Most of time, you don't need to change it.
*/
private long sessionInMemoryTimeout = DEFAULT_SESSION_IN_MEMORY_TIMEOUT;

/**
* expire time in seconds
*/
private static final int DEFAULT_EXPIRE = -2;
private static final int NO_EXPIRE = -1;

/**
* Please make sure expire is longer than sesion.getTimeout()
*/
private int expire = DEFAULT_EXPIRE;

private static final int MILLISECONDS_IN_A_SECOND = 1000;

private RedisManager redisManager;
private static ThreadLocal sessionsInThread = new ThreadLocal();

@Override
public void update(Session session) throws UnknownSessionException {
//如果会话过期/停止 没必要再更新了
try {
if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
return;
}

if (session instanceof ShiroSession) {
// 如果没有主要字段(除lastAccessTime以外其他字段)发生改变
ShiroSession ss = (ShiroSession) session;
if (!ss.isChanged()) {
return;
}
//如果没有返回 证明有调用 setAttribute往redis 放的时候永远设置为false
ss.setChanged(false);
}

this.saveSession(session);
} catch (Exception e) {
logger.warn("update Session is failed", e);
}
}

/**
* save session
* @param session
* @throws UnknownSessionException
*/
private void saveSession(Session session) throws UnknownSessionException {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
throw new UnknownSessionException("session or session id is null");
}
String key = getRedisSessionKey(session.getId());
if (expire == DEFAULT_EXPIRE) {
this.redisManager.set(key, session, (int) (session.getTimeout() / MILLISECONDS_IN_A_SECOND));
return;
}
if (expire != NO_EXPIRE && expire * MILLISECONDS_IN_A_SECOND < session.getTimeout()) {
logger.warn("Redis session expire time: "
+ (expire * MILLISECONDS_IN_A_SECOND)
+ " is less than Session timeout: "
+ session.getTimeout()
+ " . It may cause some problems.");
}
this.redisManager.set(key, session, expire);
}

@Override
public void delete(Session session) {
if (session == null || session.getId() == null) {
logger.error("session or session id is null");
return;
}
try {
redisManager.del(getRedisSessionKey(session.getId()));
} catch (Exception e) {
logger.error("delete session error. session id= {}",session.getId());
}
}

@Override
public Collection<Session> getActiveSessions() {
Set<Session> sessions = new HashSet<Session>();
try {
Set<String> keys = redisManager.scan(this.keyPrefix + "*");
if (keys != null && keys.size() > 0) {
for (String key:keys) {
Session s = (Session) redisManager.get(key);
sessions.add(s);
}
}
} catch (Exception e) {
logger.error("get active sessions error.");
}
return sessions;
}

public Long getActiveSessionsSize() {
Long size = 0L;
try {
size = redisManager.scanSize(this.keyPrefix + "*");
} catch (Exception e) {
logger.error("get active sessions error.");
}
return size;
}

@Override
protected Serializable doCreate(Session session) {
if (session == null) {
logger.error("session is null");
throw new UnknownSessionException("session is null");
}
Serializable sessionId = this.generateSessionId(session);
this.assignSessionId(session, sessionId);
this.saveSession(session);
return sessionId;
}

@Override
protected Session doReadSession(Serializable sessionId) {
if (sessionId == null) {
logger.warn("session id is null");
return null;
}
Session s = getSessionFromThreadLocal(sessionId);

if (s != null) {
return s;
}

logger.debug("read session from redis");
try {
s = (Session) redisManager.get(getRedisSessionKey(sessionId));
setSessionToThreadLocal(sessionId, s);
} catch (Exception e) {
logger.error("read session error. settionId= {}",sessionId);
}
return s;
}

private void setSessionToThreadLocal(Serializable sessionId, Session s) {
Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
if (sessionMap == null) {
sessionMap = new HashMap<Serializable, SessionInMemory>();
sessionsInThread.set(sessionMap);
}
SessionInMemory sessionInMemory = new SessionInMemory();
sessionInMemory.setCreateTime(new Date());
sessionInMemory.setSession(s);
sessionMap.put(sessionId, sessionInMemory);
}

private Session getSessionFromThreadLocal(Serializable sessionId) {
Session s = null;

if (sessionsInThread.get() == null) {
return null;
}

Map<Serializable, SessionInMemory> sessionMap = (Map<Serializable, SessionInMemory>) sessionsInThread.get();
SessionInMemory sessionInMemory = sessionMap.get(sessionId);
if (sessionInMemory == null) {
return null;
}
Date now = new Date();
long duration = now.getTime() - sessionInMemory.getCreateTime().getTime();
if (duration < sessionInMemoryTimeout) {
s = sessionInMemory.getSession();
logger.debug("read session from memory");
} else {
sessionMap.remove(sessionId);
}

return s;
}

private String getRedisSessionKey(Serializable sessionId) {
return this.keyPrefix + sessionId;
}

public RedisManager getRedisManager() {
return redisManager;
}

public void setRedisManager(RedisManager redisManager) {
this.redisManager = redisManager;
}

public String getKeyPrefix() {
return keyPrefix;
}

public void setKeyPrefix(String keyPrefix) {
this.keyPrefix = keyPrefix;
}

public long getSessionInMemoryTimeout() {
return sessionInMemoryTimeout;
}

public void setSessionInMemoryTimeout(long sessionInMemoryTimeout) {
this.sessionInMemoryTimeout = sessionInMemoryTimeout;
}

public int getExpire() {
return expire;
}

public void setExpire(int expire) {
this.expire = expire;
}
}

自定义异常类

PrincipalInstanceException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class PrincipalInstanceException extends RuntimeException  {

private static final String MESSAGE = "We need a field to identify this Cache Object in Redis. "
+ "So you need to defined an id field which you can get unique id to identify this principal. "
+ "For example, if you use UserInfo as Principal class, the id field maybe userId, userName, email, etc. "
+ "For example, getUserId(), getUserName(), getEmail(), etc.\n"
+ "Default value is authCacheKey or id, that means your principal object has a method called \"getAuthCacheKey()\" or \"getId()\"";

public PrincipalInstanceException(Class clazz, String idMethodName) {
super(clazz + " must has getter for field: " + idMethodName + "\n" + MESSAGE);
}

public PrincipalInstanceException(Class clazz, String idMethodName, Exception e) {
super(clazz + " must has getter for field: " + idMethodName + "\n" + MESSAGE, e);
}
}
PrincipalIdNullException
1
2
3
4
5
6
7
8
public class PrincipalIdNullException extends RuntimeException  {

private static final String MESSAGE = "Principal Id shouldn't be null!";

public PrincipalIdNullException(Class clazz, String idMethodName) {
super(clazz + " id field: " + idMethodName + ", value is null\n" + MESSAGE);
}
}

重写ShiroRealm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
public class ShiroRealm extends AuthorizingRealm {


private UserServiceAgent userServiceAgent = new UserServiceAgent();

private RoleServiceAgent roleServiceAgent = new RoleServiceAgent();

private PermissionServiceAgent permissionServiceAgent = new PermissionServiceAgent();

/**
* 验证用户身份
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

//获取用户名密码 第一种方式
//String username = (String) authenticationToken.getPrincipal();
//String password = new String((char[]) authenticationToken.getCredentials());

//获取用户名 密码 第二种方式
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password = new String(usernamePasswordToken.getPassword());

//从数据库查询用户信息
User user = userServiceAgent.findByUserName(username);

//可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}

if ("1".equals(user.getState())) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}

//调用 CredentialsMatcher 校验 还需要创建一个类 继承CredentialsMatcher 如果在上面校验了,这个就不需要了
//配置自定义权限登录器 参考博客:

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return info;
}

/*
* 授权用户权限
* 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
* 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
* 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
* <p>
* shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
* 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
* 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
* <p>
* 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。
* authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
* <p>
* 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
* authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
* <p>
* 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
* 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
* <p>
* 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002],perms[权限添加]");
* 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
*
* @param principalCollection
* @return
*/

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

//获取用户
User user = (User) SecurityUtils.getSubject().getPrincipal();

//获取用户角色
Set<Role> roles = roleServiceAgent.findRolesByUserId(user.getUid());
//添加角色
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (Role role : roles) {
authorizationInfo.addRole(role.getRole());
}

//获取用户权限
Set<Permission> permissions = permissionServiceAgent.findPermissionsByRoleId(roles);
//添加权限
for (Permission permission : permissions) {
authorizationInfo.addStringPermission(permission.getPermission());
}

return authorizationInfo;
}

/**
* 写方法,
* 清除当前用户的的 授权缓存
*
* @param principals
*/

@Override

public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}

/**
* 重写方法,
* 清除当前用户的 认证缓存
*
* @param principals
*/

@Override

public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}

@Override
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}

/**
* 自定义方法:
* 清除所有 授权缓存
*/

public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}

/**
* 自定义方法:
* 清除所有 认证缓存
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}

/**
* 自定义方法:
* 清除所有的 认证缓存
* 和 授权缓存
*/

public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}

}

ShiroConfig 配置

配置redisManagerAgent
1
2
3
4
@Bean("redisManagerAgent")
public RedisManager redisManager() {
return new RedisManagerAgent();
}
配置缓存管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* shiro缓存管理器;
* 需要添加到securityManager中
*
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
//redis中针对不同用户缓存
redisCacheManager.setPrincipalIdFieldName("username");
//用户权限信息缓存时间
redisCacheManager.setExpire(200000);
return redisCacheManager;
}
配置密码比较器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 配置密码比较器
*
* @return
*/

@Bean("credentialsMatcher")
public RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher() {
RetryLimitHashedCredentialsMatcher retryLimitHashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher();
retryLimitHashedCredentialsMatcher.setRedisManager(redisManager());

//如果密码加密,可以打开下面配置
//加密算法的名称
//retryLimitHashedCredentialsMatcher.setHashAlgorithmName("MD5");
//配置加密的次数
//retryLimitHashedCredentialsMatcher.setHashIterations(1024);
//是否存储为16进制
//retryLimitHashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);

return retryLimitHashedCredentialsMatcher;
}

并发登录控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 并发登录控制
*
* @return
*/

@Bean
public KickoutSessionControlFilter kickoutSessionControlFilter() {
KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter(); //用于根据会话ID,获取会话进行踢出操作的;
kickoutSessionControlFilter.setSessionManager(sessionManager());
//使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;
kickoutSessionControlFilter.setRedisManager(redisManager());
//是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;
kickoutSessionControlFilter.setKickoutAfter(false);
//同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
kickoutSessionControlFilter.setMaxSession(1);
//被踢出后重定向到的地址;
kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1");
return kickoutSessionControlFilter;
}
配置sessionDAO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* SessionDAO的作用是为Session提供CRUD并进行持久化的一个shiro组件
* MemorySessionDAO 直接在内存中进行会话维护
* EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
*
* @return
*/

@Bean
public SessionDAO sessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
//session在redis中的保存时间,最好大于session会话超时时间
redisSessionDAO.setExpire(12000);
return redisSessionDAO;
}
配置sessionFactory
1
2
3
4
5
@Bean("sessionFactory")
public ShiroSessionFactory sessionFactory() {
ShiroSessionFactory sessionFactory = new ShiroSessionFactory();
return sessionFactory;
}
配置cacheManager
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* shiro缓存管理器;
* 需要添加到securityManager中
*
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
//redis中针对不同用户缓存
redisCacheManager.setPrincipalIdFieldName("username");
//用户权限信息缓存时间
redisCacheManager.setExpire(200000);
return redisCacheManager;
}
配置Session监听器
1
2
3
4
5
6
7
8
9
10
11
/**
* 配置session监听
*
* @return
*/

@Bean("sessionListener")
public ShiroSessionListener sessionListener() {
ShiroSessionListener sessionListener = new ShiroSessionListener();
return sessionListener;
}
配置会话管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 配置会话管理器,设定会话超时及保存
*
* @return
*/
@Bean("sessionManager")
public SessionManager sessionManager() {
ShiroSessionManager sessionManager = new ShiroSessionManager();
Collection<SessionListener> listeners = new ArrayList<SessionListener>();
//配置监听
listeners.add(sessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionIdCookie(sessionIdCookie());
sessionManager.setSessionDAO(sessionDAO());
sessionManager.setCacheManager(cacheManager());
sessionManager.setSessionFactory(sessionFactory());

//全局会话超时时间(单位毫秒),默认30分钟 暂时设置为10秒钟 用来测试
sessionManager.setGlobalSessionTimeout(1800000);
//是否开启删除无效的session对象 默认为true
sessionManager.setDeleteInvalidSessions(true);
//是否开启定时调度器进行检测过期session 默认为true
sessionManager.setSessionValidationSchedulerEnabled(true);
//设置session失效的扫描时间, 清理用户直接关闭浏览器造成的孤立会话 默认为 1个小时
//设置该属性 就不需要设置 ExecutorServiceSessionValidationScheduler 底层也是默认自动调用ExecutorServiceSessionValidationScheduler
//暂时设置为 5秒 用来测试
sessionManager.setSessionValidationInterval(3600000);
//取消url 后面的 JSESSIONID
sessionManager.setSessionIdUrlRewritingEnabled(false);
return sessionManager;

}

安全事务管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 配置核心安全事务管理器
*
* @return
*/

@Bean(name = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置自定义realm.
securityManager.setRealm(shiroRealm());
//配置记住我
securityManager.setRememberMeManager(rememberMeManager());
//配置redis缓存
securityManager.setCacheManager(cacheManager());
//配置自定义session管理,使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}

评论