单设备登录

之前写过一篇单设备登录的小文章,写的不详细,所以在原来的基础上进行了修改。另外也加入了在修复此次bug的感悟与总结。希望自己能不断反思与进步,也希望能和大家互相讨论学习。

一、什么是单设备登录?

1.单设备登录概念   

    以QQ登录为例,当使用不同的手机(目前QQ支持pad与电脑、手机同时在线,但是相同类型的终端是不可以的)登录同一个QQ账号的时候,后登录的终端会把之前登录的终端给踢下线。也就是说同一账号不能在两台设备上同时登录使用。

2.单设备登录与单点登录的区别

    单点登录(SSO),英文解释为Single  Sign  On。单点登录是在一家公司的服务不断增加的环境下,用户面临输入多次账户密码的窘境。例如,要登录豆瓣FM、豆瓣读书、豆瓣电影、豆瓣日记......多种豆瓣相关的服务,每次如果都需要输入一次账号密码,岂不是用户要疯掉?并且也存在安全问题。此时就引入了单点登录,也就是说多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。与单设备登录其实并没有多大关系。

二、单设备登录实现原理

1.Token标识

数据库中保存有Token与User相关联的信息:

关键信息:user_code ---> token_id --->old_token_id,注:oldTokenId存三个tokenId。

2.tokenCache缓存

缓存userCode ---> token_id 与old_token_id ---> token_id;缓存时效可配置,目前为5分钟。

3.机制

前提条件:

    用户登录完成后,每次发送的请求需要携带登录成功后所返回的tokenId与当前设备号,并且同一台设备的设备号不能发生更改。

具体实现的两个关键点:

 

A.登录操作:

    用户登录会更新userToken信息,主要是更新tokenId与oldTokenId字段。另外也需要更新user的设备号信息,并把此次的设备号信息以userCode ---> DSN的形式存入deviceCache缓存中。

    在更新数tokenId时,会把此次登录的tokenId以userCode ---> tokenId存入tokenCache缓存中。在缓存有效期内,多次登录操作不再更新数据库tokenId字段。

B.UserLoginFilter过滤操作

    根据过滤到的请求的session中是否存在user信息分为两部分操作:

    1) user信息为空,需要判断用户是否满足长登录要求。

    根据此次请求所携带的tokenId从tokenCache中获取最新的newTokenId,若为空则生成一个并存入该缓存中,使得并发的请求都能得到同一个newTokenId。当然这部分代码放在了同步块当中。

    然后通过请求中所携带的tokenId查询对应的用户,若对应用户不存在,则用户需要重新登录。反之则直接登录此用户,并把之前生成的newTokenId更新到数据库UserToken表中。以备后续请求(携带newTokenId)能够在通过tokenId在数据库中获取user对象。

    2) user信息存在,判断是否存在多设备登录的情况,需要实现单设备登录的需求。根据此次请求所携带的设备号与缓存中的设备号(每次登录后会把当前设备号存入缓存,时效为30分钟[ 目前认为30分钟时间太短 ])进行对比,不一致则通知当前请求的设备被其他设备踢下线。若设备号的缓存时效,则从数据库中查询用户的设备号进行对比。

    至此,功能基本完成。

三、单设备登录遇到的问题

1.并发问题。

    若A用户同时发送多个请求,req1,req2,req3...他们做携带的tokenId一致。并且都执行到过滤器,由于并发的不确定性,无法保证数据库数据与缓存中的数据的一致性。即:req1判断当前请求中user为空,进行长登录操作,需要根据tokenId查询对应的user,完毕后会进行登录操作,登录又会更新缓存与数据库中的tokenId。若req1已经更新了数据库与缓存,此时req2也判断user为空需要进行长登录操作,也需要通过tokenId查询对应的用户,而此时req1已经更新了数据库,req2再通过该tokenId就会查询不到user,则该请求就会通知客户端需要重新登录的信息。就无法完成长登录的功能。

2.集群问题

    在修复长登录与单设备登录问题的测试过程中,总是出现在本地测试一切正常,一旦把代码打包到178环境就会有各种各样的问题出现。原因就是178环境是集群部署,另一台服务器是165,打包时并没有考虑到165环境,并且ehCache也没有实现集群配置。导致代码在本地正常,放到178就有问题。

    这一度迫使我们加班到很晚也依然没有找到问题所在,因为静态代码分析各种情况都已经考虑的十分周全,理论上是不可能出现那些情况的。这也是当时最最最头疼的问题(虽然是低级错误)。

四、另一种实现思路

1.长登录

    针对并发,每当App启动的时候,发送请求checkToken.app,去校验该用户的token是否还是有效的。需要与缓存中的token进行比对,若有效就更新数据库token,并返回更新后的token,同时在缓存中存入该token值。若无效,则根据tokenId查询token对象。如果可以查询到,则更新token并返回新token,同时更新缓存数据。若查询为空,则当前用户的发送请求的token已经失效,需要其重新登录。

    登录操作,需要更新用户token并返回最新token给客户端,并同时刷新缓存记录。

    假如该用户此次校验token通过,则后续发送的请求都要携带最新的token值来进行访问。Filter中需要校验该token是否有效即可(与缓存中比对)。若失效,则需要用户重新登录。

    这种做法比较关键的问题是缓存有效时长。如果只设置5分钟,用户也一直用app操作的情况下,当缓存失效,就会被迫重新登录。所以把缓存失效设置为24h。在这24h内,用户的app关闭或切换到后台较长时间,就会触发checkToken机制,重新获取新的token值。

2.单设备登录

    在以上实现机制的前提下,单设备登录自然就实现了。A账户在A设备上登录,同时B设备也登陆A账户。此时会更新用户的token并刷新缓存,A用户在访问时,Filter会校验token值,此时A设备中的token已经是失效的、不是最新的token值,就会被强制重新登录。反之亦然。

3.存在问题

    客户端无法控制每次重新加载App时都要去发送checkToken.app的接口,因此无法保证token的正确性。但我认为这种方式最合理有效。

五、反思与收获

1.反思

     我认为导致此次问题的最大原因就是考虑问题不周全个人技能不足

    人非圣贤孰能无过,对于第一个问题是无法完全解决的,但是可以尽力避免。过不仅仅是过失,还有看待问题的个人局限性,无法把问题的方方面面都考虑其中。对于自己来说,就是对并发与框架的熟悉度不够。没有把并发问题纳入代码设计中来,也就直接导致了今天这个问题。痛定思痛,应该在今后的工作与学习中,更加努力与更加勤奋!

2.收获

    此次收获还是十分丰富的。

    首先对并发有了新的认识。考虑并发就跟排列组合一样,各种各样的问题与可能性都要考虑到。特别是并发数增多,可能性更是指数级别的增长。以及对synchronized与violate的使用也有了自己的看法。对于接触并发很少的我是一个提高自我恰好的时机。再有就是对静态代码的逻辑分析能力,由于并发情况多种多样,需要自己设想各种情况,然后按照代码逻辑走下去,并考虑各种变量的值与当时服务器缓存情况。

    学如逆水行舟,不进则退。Learn playing, Play learning !

猜你喜欢

转载自blog.csdn.net/ls0111/article/details/79062857