1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        Spring Boot 實(shí)現(xiàn)限制賬戶登錄數(shù)

        共 4641字,需瀏覽 10分鐘

         ·

        2020-09-30 06:53

        關(guān)注泥瓦匠”,回復(fù)“1024”,領(lǐng)取精選技術(shù)資料


        作者:殷天文?www.jianshu.com/p/b6f5ec98d790

        通常系統(tǒng)都會限制同一個(gè)賬號的登錄人數(shù),多人登錄要么限制后者登錄,要么踢出前者,Spring Security 提供了這樣的功能,本文講解一下在沒有使用Security的時(shí)候如何手動實(shí)現(xiàn)這個(gè)功能

        如果你是使用 Shiro + Session 的模式,可以閱讀此文

        demo 技術(shù)選型


        • SpringBoot

        • JWT

        • Filter

        • Redis + Redisson


        JWT(token)存儲在Redis中,類似 JSessionId-Session的關(guān)系,用戶登錄后每次請求在Header中攜帶jwt
        如果你是使用session的話,也完全可以借鑒本文的思路,只是代碼上需要加些改動

        兩種實(shí)現(xiàn)思路


        比較時(shí)間戳

        維護(hù)一個(gè) username: jwtToken 這樣的一個(gè) key-value 在Reids中, Filter邏輯如下

        圖片不清可點(diǎn)開放大


        public?class?CompareKickOutFilter?extends?KickOutFilter?{

        ????@Autowired
        ????private?UserService?userService;

        ????@Override
        ????public?boolean?isAccessAllowed(HttpServletRequest?request,?HttpServletResponse?response)?{
        ????????String?token?=?request.getHeader("Authorization");
        ????????String?username?=?JWTUtil.getUsername(token);
        ????????String?userKey?=?PREFIX?+?username;

        ????????RBucket?bucket?=?redissonClient.getBucket(userKey);
        ????????String?redisToken?=?bucket.get();

        ????????if?(token.equals(redisToken))?{
        ????????????return?true;

        ????????}?else?if?(StringUtils.isBlank(redisToken))?{
        ????????????bucket.set(token);

        ????????}?else?{
        ????????????Long?redisTokenUnixTime?=?JWTUtil.getClaim(redisToken,?"createTime").asLong();
        ????????????Long?tokenUnixTime?=?JWTUtil.getClaim(token,?"createTime").asLong();

        ????????????//?token?>?redisToken?則覆蓋
        ????????????if?(tokenUnixTime.compareTo(redisTokenUnixTime)?>?0)?{
        ????????????????bucket.set(token);

        ????????????}?else?{
        ????????????????//?注銷當(dāng)前token
        ????????????????userService.logout(token);
        ????????????????sendJsonResponse(response,?4001,?"您的賬號已在其他設(shè)備登錄");
        ????????????????return?false;

        ????????????}

        ????????}

        ????????return?true;

        ????}
        }

        隊(duì)列踢出

        public?class?QueueKickOutFilter?extends?KickOutFilter?{
        ????/**
        ?????*?踢出之前登錄的/之后登錄的用戶?默認(rèn)踢出之前登錄的用戶
        ?????*/

        ????private?boolean?kickoutAfter?=?false;
        ????/**
        ?????*?同一個(gè)帳號最大會話數(shù)?默認(rèn)1
        ?????*/

        ????private?int?maxSession?=?1;

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

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

        ????@Override
        ????public?boolean?isAccessAllowed(HttpServletRequest?request,?HttpServletResponse?response)?throws?Exception?
        {
        ????????String?token?=?request.getHeader("Authorization");
        ????????UserBO?currentSession?=?CurrentUser.get();
        ????????Assert.notNull(currentSession,?"currentSession?cannot?null");
        ????????String?username?=?currentSession.getUsername();
        ????????String?userKey?=?PREFIX?+?"deque_"?+?username;
        ????????String?lockKey?=?PREFIX_LOCK?+?username;

        ????????RLock?lock?=?redissonClient.getLock(lockKey);

        ????????lock.lock(2,?TimeUnit.SECONDS);

        ????????try?{
        ????????????RDeque?deque?=?redissonClient.getDeque(userKey);

        ????????????//?如果隊(duì)列里沒有此token,且用戶沒有被踢出;放入隊(duì)列
        ????????????if?(!deque.contains(token)?&&?currentSession.isKickout()?==?false)?{
        ????????????????deque.push(token);
        ????????????}

        ????????????//?如果隊(duì)列里的sessionId數(shù)超出最大會話數(shù),開始踢人
        ????????????while?(deque.size()?>?maxSession)?{
        ????????????????String?kickoutSessionId;
        ????????????????if?(kickoutAfter)?{?//?如果踢出后者
        ????????????????????kickoutSessionId?=?deque.removeFirst();
        ????????????????}?else?{?//?否則踢出前者
        ????????????????????kickoutSessionId?=?deque.removeLast();
        ????????????????}

        ????????????????try?{
        ????????????????????RBucket?bucket?=?redissonClient.getBucket(kickoutSessionId);
        ????????????????????UserBO?kickoutSession?=?bucket.get();

        ????????????????????if?(kickoutSession?!=?null)?{
        ????????????????????????//?設(shè)置會話的kickout屬性表示踢出了
        ????????????????????????kickoutSession.setKickout(true);
        ????????????????????????bucket.set(kickoutSession);
        ????????????????????}

        ????????????????}?catch?(Exception?e)?{
        ????????????????}

        ????????????}

        ????????????//?如果被踢出了,直接退出,重定向到踢出后的地址
        ????????????if?(currentSession.isKickout())?{
        ????????????????//?會話被踢出了
        ????????????????try?{
        ????????????????????//?注銷
        ????????????????????userService.logout(token);
        ????????????????????sendJsonResponse(response,?4001,?"您的賬號已在其他設(shè)備登錄");

        ????????????????}?catch?(Exception?e)?{
        ????????????????}

        ????????????????return?false;

        ????????????}

        ????????}?finally?{
        ????????????if?(lock.isHeldByCurrentThread())?{
        ????????????????lock.unlock();
        ????????????????LOGGER.info(Thread.currentThread().getName()?+?"?unlock");

        ????????????}?else?{
        ????????????????LOGGER.info(Thread.currentThread().getName()?+?"?already?automatically?release?lock");
        ????????????}
        ????????}

        ????????return?true;
        ????}

        }


        比較兩種方法


        第一種方法邏輯簡單粗暴, 只維護(hù)一個(gè)key-value 不需要使用鎖,非要說缺點(diǎn)的話沒有第二種方法靈活。

        第二種方法我很喜歡,代碼很優(yōu)雅靈活,但是邏輯相對麻煩一些,而且為了保證線程安全地操作隊(duì)列,要使用分布式鎖。目前我們項(xiàng)目中使用的是第一種方法

        - 墻裂推薦?-

        專注于「開發(fā)者」綜合成長的深度星球
        限時(shí)優(yōu)惠進(jìn)行中
        星球近期系列更新


        下方二維碼關(guān)注我

        互聯(lián)網(wǎng)草根,堅(jiān)持分享技術(shù)、創(chuàng)業(yè)、產(chǎn)品心得和總結(jié)~



        點(diǎn)擊“閱讀原文”,領(lǐng)取 2020 年最新免費(fèi)技術(shù)資料大全

        ↓↓↓?
        瀏覽 84
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日本淫色视频 | 乡村爱情乱肉版h文全文 | 中文字幕在线观看亚洲 | 久久99久久99精品蜜柚传媒 | 亂伦WWWHD一区二区三区 婷婷五月天无码 | 成人在线A片 | 日本一区二区在线视频 | 亚洲欧洲另类 | 美女露出强行男生揉网站 | 好吊一区二区三区 |