单一用户登陆

参考连接:
单一用户登陆实现:https://www.cnblogs.com/leechenxiang/p/6171151.html
shiro拦截器介绍:http://jinnianshilongnian.iteye.com/blog/2025656
shiro全配置文件:http://www.cnblogs.com/leechenxiang/p/6136017.html

需求:

现在项目应用在web端和app端,一个用户要么登陆在web端要么app端,并且不能重复登陆,如果重复登陆,新登陆的客户端会踢掉已经登陆的客户端用户。即一个用户只能同时在线一个客户端

实现:

第一种:不适用框架,待补充
第二种:使用shiro框架来实现
如果使用shiro框架来实现,系统的认证授权模块都由shiro框架来实现,可能需要重构权限框架
直接看如下连接:
https://www.cnblogs.com/leechenxiang/p/6171151.html

权限相关知识和shiro框架介绍见shiro权限
sso单点登陆见单点登陆
单一用户登陆实现步骤如下:

  1. 导包,maven或者直接下载,直接百度
  2. 利用shiro框架搭建权限模块,见上文
  3. 利用shiro新建自定义拦截器
    自定义拦截器可以继承shiro相关拦截器,扩展AccessControlFilter,其继承了PathMatchingFilter,并扩展了了两个方法:
    isAccessAllowed:即是否允许访问,返回true表示允许;
    onAccessDenied:表示isAccessAllowed方法返回为false时,即用户访问拒绝时,是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。
//导包省略
//自定义单一登陆shiro拦截器,可以继承AccessControlFilter类
public class SessionKickoutControlFilter extends AccessControlFilter{
    //缓存,存入登陆用户的sessionId,参数string为kickout+用户名+web/app,Deque<Serializable>为sessionId集合(可能同时多个)
    private Cache<String, Deque<Serializable>> sessionKickoutCache;
    private boolean kickoutAfter = false;
    private int maxSession = 1;//最大登陆session人数
    private SessionManager sessionManager;
    private static final String keyPrefix = "kickout";

    @Autowired
    AdminService adminService;

    @Autowired
    UserLogService userLogService;

    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //request获得当前用户  
        Subject subject = getSubject(request,response);

        if(!subject.isAuthenticated() && !subject.isRemembered()) {  
            //如果没有登录,返回true登陆即可
            return true;  
        }
        Session session = subject.getSession();  
        String username = (String) subject.getPrincipal();  
        Serializable sessionId = session.getId();  
        String client = null;
        String[] clients = request.getParameterValues("client");
        if(clients != null && clients.length > 0){
            client = "1".equals(clients[0]) ? "APP" : "WEB";
        }else {
            client = ShiroUtil.getLoginClient();
        }
        //需要同步缓存值
        synchronized(this.sessionKickoutCache){

            Deque<Serializable> deque = sessionKickoutCache.get(keyPrefix + client + username); 
            if(deque == null || deque.size() == 0) {  
                deque = new LinkedList<Serializable>();  
            }  

            //如果队列里没有此sessionId,且用户没有被踢出;放入队列  
            if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {  
                deque.push(sessionId);  
            }

            //如果队列里的sessionId数超出最大会话数,开始踢人  
            while(deque.size() > maxSession) {  
                Serializable kickoutSessionId = null;  
                if(kickoutAfter) { //如果踢出后者  
                    kickoutSessionId = deque.removeFirst();  
                } else { //否则踢出前者  
                    kickoutSessionId = deque.removeLast();  
                }  
                try {  
                    Session kickoutSession =  sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));  
                    if(kickoutSession != null) {  
                        //设置会话的kickout属性表示踢出了  
                        kickoutSession.setAttribute("kickout", true);  
                    }  
                } catch (Exception e) {//ignore exception  
                }  
            } 
            sessionKickoutCache.put(keyPrefix + client  + username, deque);
        }
        //如果被踢出了,直接退出,重定向到踢出后的地址  
        if (session.getAttribute("kickout") != null && (Boolean)session.getAttribute("kickout")) {  
            session.setAttribute("kickout", false);
            //会话被踢出了  
            try {
                String loginIp = BaseUtil.getIpAddr((HttpServletRequest)request);
                userLogService.insertUserLog(ShiroUtil.getUserNo(),ShiroUtil.getUserName(), ShiroUtil.getLoginClient(), loginIp, UserLogService.USER_LOG_LOGOUT);
                subject.logout();  
            } catch (Exception e) { //ignore  
            }  
            saveRequest(request);  
            response.getWriter().write(JSONObject.toJSONString(adminService.kickoutLogin()));
            //如果踢出以前登陆用户,可以让他重定向kickoutUrl(登陆页面)
            WebUtils.issueRedirect(request, response, kickoutUrl);  
            return false;  
        }  
        return true;  
    }
    //返回false不允许直接访问,要进行拦截,返回true,则可以直接访问,一般继承这个类就是为了不能直接访问,要进行拦截所以要返回false
    @Override
    protected boolean isAccessAllowed(ServletRequest request,
            ServletResponse response, Object mappedValue) throws Exception {
        return false;
    }

    public void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

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

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

    public void setCacheManager(CacheManager cacheManager){
        this.sessionKickoutCache = cacheManager.getCache("sessionKickoutCache");
    }
}
  1. 配置SessionKickoutControlFilter拦截器
    第一种:未整合spring
    在shrio.ini文件中加入
[filters]  
kickout=cn.paic.rep.pare.shiro.SessionKickoutControlFilter
[urls]  
/**=kickout//表示对所有的连接都需要进行拦截判断

第二种:spring整合(常用)
在application-shiro.xml文件中配置

    <!--第一步:在spring中注入自定义的拦截器
楼主定义了多个拦截器:先判断单点登陆,然后单一用户登陆,最后权限验证,形成一个责任链
-->
    <bean id="ssoFilter" class="cn.paic.rep.pare.shiro.SSOFilter" />
    <bean id="_permissionsFilter" class="cn.paic.rep.pare.shiro._PermissionsFilter"></bean>

   <!--这是的单一用户登陆配置项-->
    <bean id="sessionKickoutControlFilter" class="cn.paic.rep.pare.shiro.SessionKickoutControlFilter">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="sessionManager" ref="sessionManager"/>  
        <property name="kickoutAfter" value="false"/>  
        <property name="maxSession" value="1"/>  
<!--跳转页面    <property name="kickoutUrl" value="/login"/>        
 --> </bean>

    <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <!-- 调用我们配置的权限管理器 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 配置我们的登录请求地址 -->
        <!-- <property name="loginUrl" value="/login" /> -->
        <!-- 配置我们在登录页登录成功后的跳转地址,如果你访问的是非/login地址,则跳到您访问的地址 -->
        <!-- <property name="successUrl" value="/home" /> -->
        <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 -->
        <!-- <property name="unauthorizedUrl" value="/api/admin/unauthorized" /> -->
        <!-- 权限配置 -->
        <!-- 第二步:以下为配置自定义的拦截器 -->
        <property name="filters">
            <map>
                <entry key="ssoFilter" value-ref="ssoFilter" />
                <entry key="perms" value-ref="_permissionsFilter" />
                <entry key="kickout" value-ref="sessionKickoutControlFilter"/>
            </map>
        </property>
    </bean>

    <!-- 第三步:权限资源配置 -->  
    <bean id="filterChainDefinitionsService"    class="cn.paic.rep.pare.shiro.SimpleFilterChainDefinitionsService">  
        <property name="definitions">  
            <value>  
                <!--不需要登陆和拦截即可访问-->
                 /api/file/getBbsImg/*=anon
                 <!--左边对api下的请求都要进行拦截判断-->            
                 /api/** =kickout,ssoFilter
            </value>  
        </property>  
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值