1、背景
批量操作,用户A登录执行完批量操作,用户B登录在执行批量操作的时候报错
2、排查过程
2.1 网上搜的大部分都是:Shiro的Cookie名称默认是JSESSIONID,与servlet容器冲突。修改Shiro的SessionID即可。但是如果是这个问题,用户B应该是无法登录的!所以还是shiro代码出的问题!
2.2 批量操作代码只有子线程中获取了当前登录人信息的代码与shiro有关,所以问题还是在这!
vo.setOperatorId(ShiroUtils.getUserId());
vo.setOperatorName(ShiroUtils.getUserEntity().getUsername());
进入getUserEntity()
public static SysUserEntity getUserEntity() {
if (isLogin()) {
return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
}
return null;
}
进入SecurityUtils.getSubject()
public static Subject getSubject() {
Subject subject = ThreadContext.getSubject();
if (subject == null) {
subject = (new Builder()).buildSubject();
ThreadContext.bind(subject);
}
return subject;
}
进入ThreadContext.getSubject()
public static Subject getSubject() {
return (Subject)get(SUBJECT_KEY);
}
public static Object get(Object key) {
if (log.isTraceEnabled()) {
String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
Object value = getValue(key);
if (value != null && log.isTraceEnabled()) {
String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" + key + "] bound to thread [" + Thread.currentThread().getName() + "]";
log.trace(msg);
}
return value;
}
进入getValue(key)
private static Object getValue(Object key) {
Map<Object, Object> perThreadResources = (Map)resources.get();
return perThreadResources != null ? perThreadResources.get(key) : null;
}
进入(Map)resources.get();
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
一路走来发现:Subject是与ThreadLocal也就是线程绑定的,这就是造成获取用户错误的原因。我们的线程在执行完毕的时候并没有清除ThreadLocal中的值,导致后面的任务重用现在的LocalMap。
3、解决方法:在线程池中使用Shiro的情况下,子线程无法获取上层线程的shiro相关变量,当前用户需要在外层设置进去,eg:将子线程中调用Shiro获取当前登录人信息的代码,提到上层线程中,在上层线程以参数的形式传到子线程!