1. 程式人生 > >Springboot + redis+shiro 限制 同一賬號 同時 多處登入

Springboot + redis+shiro 限制 同一賬號 同時 多處登入

從網上看了很多解決方案,用的最多的 應當是SessionId 了。方案雖多,適合自己的才是最好的。

之前做了一個 線上使用者的統計 和 管理員 踢出啟用線上使用者的功能,因此我得到了一個啟發。程式是死的,人是活得,我可不可以定一些規則,讓程式 根據我的規定 來 執行。

思路:

1.定規則。

將 踢出的使用者 畫一個標識,也就是 訪問的Sess ionId。踢出了 我將它標記為false

如果 SeeioId 標識為false 就強制退出使用者

2.監聽

定了規則之後我要監視它,如果它滿足規則,要執行相應的操作

 流程:

1.在登入時 儲存 登入的使用者Id 和訪問的SessionId 到redis 使用Hash表的方式

2.在登入時,取出 儲存的使用者資訊Map

3.判斷 Map 的Size 

4.大於1,說明是第二次登入,取出 Map中的資訊

5.判斷 如果 該次請求的SessionId 不等於Map中的SessionId 並且 該次請求的使用者Id 等於Map 中的使用者Id

6.滿足上述條件,則是 同一賬號多處登入。將Map 中的SessionId 標記為false (這裡是標記前一位登入的使用者) 存入redis 

7.將標記的SessionId儲存到redis中 用鍵值對的方式,Key 隨便取,比如("ks",sessionId)


8.自定義Filter 這裡使用  HandlerInterceptorAdapter 因為 該 顆粒度更高,可以使用 注入。

9.獲取 被踢出的SessionId 的值 ,利用 get("ks") 在redis中取出

10.判斷 值為 false 並且 該次請求的SessionId 等於 踢出的SessionId

11.退出,跳轉

//原始碼

定規則:

 //此方法在 登入時呼叫

 public void kickOutLogin(String sessionId, String userId) {

        Jedis jedis = null;

        try {

            jedis = jedisPool.getResource();

             //儲存使用者Id,訪問的SessionID 
            jedis.hset("kickoutlogin", sessionId, userId);

            //儲存請求的SessionId,標識為true
            jedis.hset("kickoutSessionId", sessionId, "true");

            //設定過期時間為30分鐘
            jedis.expire("kickoutSessionId", 30 * 60);
            jedis.expire("kickoutlogin", 30 * 60);

            //取出儲存在redis中的域和值
            Map<String, String> map = jedis.hgetAll("kickoutlogin");
            if (map.size() > MaxSize) {
                for (Map.Entry entry : map.entrySet()) {
                    //域 = sessionId
                    String jessionId = (String) entry.getKey();
                   //值 = 使用者Id
                    String uid = (String) entry.getValue();
                   //獲取請求的標識
                    String kickoutSessionId = jedis.hget("kickoutSessionId", jessionId);
                    if (!kickoutSessionId.equals("false")) {

                        // sessionId 不同,userId相同, 表示同一賬號 多處登入
                        if (!sessionId.equals(jessionId) && userId.equals(uid)) {
                            //false 表示踢出
                            jedis.hset("kickoutSessionId", jessionId, "false");

                            //被踢出的使用者SesionId儲存到redis
                            jedis.set("Ks", jessionId);

                        }
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }

    }

監聽:

package com.example.springboot.shiro.core.shiro.filter;


import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

//使用 HandlerInterceptorAdapter  可以注入資源 

public class SessionControlInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    private JedisPool jedisPool;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        //如果沒有登入
        if (!subject.isAuthenticated()) {
            return true;
        }
        Jedis jedis = null;
        String kickoutlogin = null;
        try {
              //獲取請求的SessionId
            String sessionId = request.getSession().getId();
            jedis = jedisPool.getResource();
            //獲取被踢出的SessionId
            String jessionId=jedis.get("Ks");
            if (sessionId != null) {
               //做判斷 容易空指標
                if (null != jessionId) {
                    //獲取這次請求SessionId 的標識
                    kickoutlogin = jedis.hget("kickoutSessionId", jessionId);
                }
            }

            if (kickoutlogin != null) {
                //標記為 false 並且 該 次請求的sessionId 和踢出的SessionId 相同
                if (kickoutlogin.equals("false") && sessionId.equals(jessionId)) {
                   //退出
                    subject.logout();
                  //重定向
                    WebUtils.issueRedirect(request, response, "kickoutLogin");
                }
            }

          
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return Boolean.TRUE;
    }

}

關於  HandlerInterceptorAdapter 的配置 可以去看一下 Springboot+Shiro+redis 踢出線上使用者