工作趣事之ThreadLocal

问题简述

最近一直搞前端,于是对后台代码不太重视了,突然想起那段话,“现在的程序员啊,压力没那么大,复制粘贴,代码能跑就行”,哈哈,确实做到了能跑就行,没有关注很多前端后台的代码问题,没成想这次遇到了,这完全是一次意外的发现。事情的起因是前端会对静态资源的请求放行,于是将接口请求伪装成静态资源的请求时,可以轻松绕过第一道校验,其实绕过校验也没关系,因为我们后面还是会对登录用户的信息进行校验,然而让人没想到的是,此次发送的模拟请求并没有带用户信息,却请求成功了,并且具有了一切能力,增删改查样样精通,这就让人很是费解了,于是当这个问题爆出来的时候,我们定位的时候觉得一脸懵逼,就觉得这个是怎么伪造出来的请求,竟然能够绕过我们的用户验证。可是当问题一遍遍复现的时候,我们无话可说,只能硬着头皮定位问题。

问题根因

我们把代码看了一个遍,一遍遍的怀疑又一遍遍的推翻怀疑,曾经一度认为定位出了问题,那就是单例问题导致的,形如以下代码:

@Service
public class UserService {
	private static ThreadLocal<User> userThread = new ThreadLocal<>

	public void set(User user){
		// 将用户塞入当前线程中
	}
	
	public User getUser(){
		// 将用户从当前线程中取出
	}
}

我们一开始的想法是此类只初始化了一次,ThreadLocal是成员变量,那么当第一个用户登录后,线程A就记住了此用户的信息,在没有下一个用户登录前来覆盖线程现存用户的情况下,那么此用户的信息会一直存储在当前线程中,那么这时候模拟请求发送的时候就会出现虽然请求中没有携带用户信息,但是依然会用到之前线程中已经存储好的用户信息。这么一想,确实有几分道理,并且我们大部分人也认同这种想法。但如果真是这样的话,那么还会有一个严重的问题,那就是如果用户A正在登陆中,那么当用户B登录上之后,这个时候线程中的用户信息就会刷成用户B的,那么用户A会看见用户B的信息而不是A自己的信息,这个就是太严重的问题了,但至今我们也没有收到过投诉,这极大说明了我们的推理可能是不成立的。基于此的想法,我们点进了ThreadLocal的源码中进行查看,如下是它的set方法:

public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
	    map.set(this, value);
	else
	    createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

从上面我们可以看出,ThreadLocal每次设值都是放进一个map中,key值是线程自身,这就实现了线程隔离,保证了不同线程之间是不共享变量的,那这也便推翻了我们关于单例的假设。那么原因为何,我们继续推进,发现了关于ThreadLocal的一些理念描述,并且其中有强调ThreadLocal在设置完变量并且用完后要及时将其清除,以避免后续使用中出现线程中变量被混乱使用的情况。我们如醍醐灌顶般被点醒,我们遇到的情况不就是这样么,比如Thread A线程在一开始请求中拿到了UserInfo,之后它在后续的请求中虽然没有设置用户信息,但其本身已经包含了上一次的用户信息,轻松绕过了登录用户验证,产生个人隐私风险。

解决方案

知道了原因,解决方案自然清晰明了了,那就是调用线程的remove的方法,对线程中的信息进行remove,这样就保证了线程中不包含上次请求中塞入的信息。那么,这个线程的remove方法放在何处最合适呢?这是一个开放性问题,What do you think~
另附,多读书,读好书还是很有用处的:
ThreadLocal_1
ThreadLocal_2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值