1
hao
2025-05-20 8e24c6fea30d9b179375ee2893710cdec2443b13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
package com.filter;
 
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;
 
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
 
/*import com.wyait.manage.entity.ResponseResult;
import com.wyait.manage.pojo.User;
import com.wyait.manage.utils.IStatusMessage;
import com.wyait.manage.utils.ShiroFilterUtils;*/
import com.system.user.entity.SysUser;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import com.entity.ResponseResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pojo.User;
import com.utils.IStatusMessage;
import com.utils.ShiroFilterUtils;
 
/**
 * 
 * @项目名称:wyait-manage
 * @类名称:KickoutSessionFilter
 * @类描述:自定义过滤器,进行用户访问控制
 * @创建人:wyait
 * @创建时间:2018年4月24日 下午5:18:29
 * @version:
 */
public class KickoutSessionFilter extends AccessControlFilter {
 
    private static final Logger logger = LoggerFactory
            .getLogger(KickoutSessionFilter.class);
 
    private final static ObjectMapper objectMapper = new ObjectMapper();
 
    private String kickoutUrl; // 踢出后到的地址
    private boolean kickoutAfter = false; // 踢出之前登录的/之后登录的用户 默认false踢出之前登录的用户
    private int maxSession = 1; // 同一个帐号最大会话数 默认1
    private SessionManager sessionManager;
    private Cache<String, Deque<Serializable>> cache;
 
    public void setKickoutUrl(String kickoutUrl) {
        this.kickoutUrl = kickoutUrl;
    }
 
    public void setKickoutAfter(boolean kickoutAfter) {
        this.kickoutAfter = kickoutAfter;
    }
 
    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }
 
    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }
 
    // 设置Cache的key的前缀
    public void setCacheManager(CacheManager cacheManager) {
        //必须和ehcache缓存配置中的缓存name一致
        this.cache = cacheManager.getCache("shiro-activeSessionCache");
    }
 
    @Override
    protected boolean isAccessAllowed(ServletRequest request,
            ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }
 
    @Override
    protected boolean onAccessDenied(ServletRequest request,
            ServletResponse response) throws Exception {
        Subject subject = getSubject(request, response);
        // 没有登录授权 且没有记住我
        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            // 如果没有登录,直接进行之后的流程
            //判断是不是Ajax请求,异步请求,直接响应返回未登录
            /*ResponseResult responseResult = new ResponseResult();
            if (ShiroFilterUtils.isAjax(request) ) {
                logger.debug(getClass().getName()+ "当前用户已经在其他地方登录,并且是Ajax请求!");
                responseResult.setCode(IStatusMessage.SystemStatus.MANY_LOGINS.getCode());
                responseResult.setMessage("您已在别处登录,请您修改密码或重新登录");
                out(response, responseResult);
                return false;
            }else{
                return true;
            }*/
            return true;
        }
        // 获得用户请求的URI
        HttpServletRequest req=(HttpServletRequest) request;
        String path = req.getRequestURI();
        logger.debug("===当前请求的uri:==" + path);
        //String contextPath = req.getContextPath();
        //logger.debug("===当前请求的域名或ip+端口:==" + contextPath);
        //放行登录
        if(path.equals("/toLogin")){
            return true;
        }
        
        
        if(path.equals("/queryPurview")){
            return true;
        }
        
        Session session = subject.getSession();
        logger.debug("==session时间设置:" + String.valueOf(session.getTimeout())
                + "===========");
        try {
            // 当前用户
            //User user = (User) subject.getPrincipal();
            SysUser user = (SysUser) subject.getPrincipal();
            String username = user.getFcode();
//            String username = user.getUserCode();
            logger.debug("===当前用户username:==" + username);
            Serializable sessionId = session.getId();
            logger.debug("===当前用户sessionId:==" + sessionId);
            // 读取缓存用户 没有就存入
            Deque<Serializable> deque = cache.get(username);
            logger.debug("===当前deque:==" + deque);
            if (deque == null) {
                // 初始化队列
                deque = new ArrayDeque<Serializable>();
            }
            // 如果队列里没有此sessionId,且用户没有被踢出;放入队列
            if (!deque.contains(sessionId)
                    && session.getAttribute("kickout") == null) {
                // 将sessionId存入队列
                deque.push(sessionId);
                // 将用户的sessionId队列缓存
                cache.put(username, deque);
            }
            // 如果队列里的sessionId数超出最大会话数,开始踢人
            while (deque.size() > maxSession) {
                logger.debug("===deque队列长度:==" + deque.size());
                Serializable kickoutSessionId = null;
                // 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;
                if (kickoutAfter) { // 如果踢出后者
                    kickoutSessionId = deque.removeFirst();
                } else { // 否则踢出前者
                    kickoutSessionId = deque.removeLast();
                }
                // 踢出后再更新下缓存队列
                cache.put(username, deque);
                try {
                    // 获取被踢出的sessionId的session对象
                    Session kickoutSession = sessionManager
                            .getSession(new DefaultSessionKey(kickoutSessionId));
                    if (kickoutSession != null) {
                        // 设置会话的kickout属性表示踢出了
                        kickoutSession.setAttribute("kickout", true);
                    }
                } catch (Exception e) {// ignore exception
                }
            }
 
            // 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址
            if ((Boolean) session.getAttribute("kickout") != null
                    && (Boolean) session.getAttribute("kickout") == true) {
                // 会话被踢出了
                try {
                    // 退出登录
                    subject.logout();
                } catch (Exception e) { // ignore
                }
                saveRequest(request);
                logger.debug("==踢出后用户重定向的路径kickoutUrl:" + kickoutUrl);
                // ajax请求
                // 重定向
                //WebUtils.issueRedirect(request, response, kickoutUrl);
                return isAjaxResponse(request,response);
            }
            return true;
        } catch (Exception e) { // ignore
            logger.error(
                    "控制用户在线数量【lyd-admin-->KickoutSessionFilter.onAccessDenied】异常!",
                    e);
            // 重启后,ajax请求,报错:java.lang.ClassCastException:
            // com.lyd.admin.pojo.AdminUser cannot be cast to
            // com.lyd.admin.pojo.AdminUser
            // 处理 ajax请求
            return isAjaxResponse(request,response);
        }
    }
    /**
     *
     * @描述:response输出json
     * @创建人:wyait
     * @创建时间:2018年4月24日 下午5:14:22
     * @param response
     * @param result
     */
    public static void out(ServletResponse response, ResponseResult result){
        PrintWriter out = null;
        try {
            response.setCharacterEncoding("UTF-8");//设置编码
            response.setContentType("application/json");//设置返回类型
            out = response.getWriter();
            out.println(objectMapper.writeValueAsString(result));//输出
            logger.error("用户在线数量限制【wyait-manager-->KickoutSessionFilter.out】响应json信息成功");
        } catch (Exception e) {
            logger.error("用户在线数量限制【wyait-manager-->KickoutSessionFilter.out】响应json信息出错", e);
        }finally{
            if(null != out){
                out.flush();
                out.close();
            }
        }
    }
 
    private boolean isAjaxResponse(ServletRequest request,
            ServletResponse response) throws IOException {
        // ajax请求
        /**
         * 判断是否已经踢出
         * 1.如果是Ajax 访问,那么给予json返回值提示。
         * 2.如果是普通请求,直接跳转到登录页
         */
        //判断是不是Ajax请求
        ResponseResult responseResult = new ResponseResult();
        if (ShiroFilterUtils.isAjax(request) ) {
            logger.debug(getClass().getName()+ "当前用户已经在其他地方登录,并且是Ajax请求!");
            responseResult.setCode(IStatusMessage.SystemStatus.MANY_LOGINS.getCode());
            responseResult.setMessage("您已在别处登录,请您修改密码或重新登录");
            out(response, responseResult);
        }else{
            // 重定向
            WebUtils.issueRedirect(request, response, kickoutUrl);
        }
        return false;
    }
 
}