springboot + shiro之登录人数限制、登录判断重定向、session时间设置

springboot + shiro之登录人数控制

项目

前篇:spring boot + mybatis + layui + shiro后台权限管理系统

本文是基于spring boot + mybatis + layui + shiro后台权限管理系统开发的,新增功能:

  1. shiro并发登陆人数控制(超出登录用户最大配置数量,清理用户)功能;
  2. 解决父子页面判断用户未登录之后,重定向到页面中嵌套显示登录界面问题;
  3. 解决ajax请求,判断用户未登录之后,重定向到登录页面问题;
  4. 解决完成了功能1,导致的session有效时间冲突问题。

项目源码

项目源码:(包含数据库源码)
github源码: https://github.com/wyait/manage.git
码云:https://gitee.com/wyait/manage.git
github对应项目源码目录:wyait-manage-1.2.0
码云对应项目源码目录:wyait-manage-1.2.0

场景

同一个用户,先在A电脑登录;之后在B电脑登录时,退出A电脑的登录状态;反之相同。或者限制同一个用户在不同的设备上,同时在线的数量;

技术实现

基于shiro和ehcache实现

解决思路

spring security就直接提供了相应的功能;
Shiro的话没有提供默认实现,不过可以在Shiro中加入这个功能。就是使用shiro强大的自定义访问控制拦截器:AccessControlFilter,集成这个接口后要实现下面这2个方法。

    /**
     * Returns <code>true</code> if the request is allowed to proceed through the filter normally, or <code>false</code>
     * if the request should be handled by the
     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(request,response,mappedValue)}
     * method instead.
     *
     * @param request     the incoming <code>ServletRequest</code>
     * @param response    the outgoing <code>ServletResponse</code>
     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
     * @return <code>true</code> if the request should proceed through the filter normally, <code>false</code> if the
     *         request should be processed by this filter‘s
     *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
     * @throws Exception if an error occurs during processing.
     */
    protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;

    ... ...
    /**
     * Processes requests where the subject was denied access as determined by the
     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
     * method.
     *
     * @param request  the incoming <code>ServletRequest</code>
     * @param response the outgoing <code>ServletResponse</code>
     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
     *         handle/render the response directly.
     * @throws Exception if there is an error processing the request.
     */
    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;

查看抽象类AccessControlFilter:

  • isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;
  • onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
  • onPreHandle:会自动调用这两个方法决定是否继续处理;

另外AccessControlFilter还提供了如下方法用于处理如登录成功后/重定向到上一个请求:

void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp
String getLoginUrl()
Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject实例
boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求
void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面
void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求
void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面

要进行用户访问控制,可以继承AccessControlFilter。

  • 思路:
    a. 登陆成功时将用户保存到了shiro提供的session中,并同时添加到ehcache缓存中;
    b. KickoutSessionFilter拿到了session之后先判断能不能通过缓存取到值,如果取得到再和服务器端session进行匹配(用户的名字(每个用户的名字必须不同));
    c. 如果匹配,系统会为新登录的用户新建一个session;之前的session确认失效并踢出,老用户就无法继续操作而被迫下线;

shiro技术实现流程

下面就是自定义的访问控制拦截器:KickoutSessionFilter:

自定义过滤器类KickoutSessionFilter

package com.wyait.manage.filter;

import java.io.Serializable;
import java.util.ArrayDeque;
import java.util.Deque;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import com.wyait.manage.pojo.User;
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.lyd.admin.pojo.AdminUser;

/**
 *
 * @项目名称:wyait-manager
 * @类名称:KickoutSessionFilter
 * @类描述:自定义过滤器,进行用户访问控制
 * @创建人:wyait
 * @创建时间:2018年4月24日 下午5:18:29
 * @version:
 */
public class KickoutSessionFilter extends AccessControlFilter {

    private static final Logger logger = LoggerFactory
            .getLogger(KickoutSessionFilter.class);

    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()) {
            // 如果没有登录,直接进行之后的流程
            return true;
        }
        Session session = subject.getSession();
        logger.debug("==session时间设置:" + String.valueOf(session.getTimeout())
                + "===========");
        try {
            // 当前用户
            User user = (User) subject.getPrincipal();
            String username = user.getUsername();
            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
                }
            }
            // ajax请求

            // 如果被踢出了,(前者或后者)直接退出,重定向到踢出后的地址
            if ((Boolean) session.getAttribute("kickout") != null
                    && (Boolean) session.getAttribute("kickout") == true) {
                // 会话被踢出了
                try {
                    // 退出登录
                    subject.logout();
                } catch (Exception e) { // ignore
                }
                saveRequest(request);
                logger.debug("==踢出后用户重定向的路径kickoutUrl:" + kickoutUrl);
                // 重定向
                WebUtils.issueRedirect(request, response, kickoutUrl);
                return false;
            }
            return true;
        } catch (Exception e) { // ignore
            //重定向到登录界面
            WebUtils.issueRedirect(request, response, "/login");
            return false;
        }
    }

}

设置ShiroConfig配置类

  • SessionDAO 用于会话的CRUD;查看该接口源码:
public interface SessionDAO {
    /*如DefaultSessionManager在创建完session后会调用该方法;
      如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;
      返回会话ID;主要此处返回的ID.equals(session.getId());
    */
    Serializable create(Session session);    

    //根据会话ID获取会话
    Session readSession(Serializable sessionId) throws UnknownSessionException;    

    //更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用
    void update(Session session) throws UnknownSessionException;    

    //删除会话;当会话过期/会话停止(如用户退出时)会调用
    void delete(Session session);    

    //获取当前所有活跃用户,如果用户量多此方法影响性能
    Collection<Session> getActiveSessions();
}

SessionDAO实现类:

a. AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;
b. CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的CacheManager即可;
c. MemorySessionDAO直接在内存中进行会话维护;
d. EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
  1. ShiroConfig配置类中EnterpriseCacheSessionDAO配置:

    /**
     * EnterpriseCacheSessionDAO shiro sessionDao层的实现;
     * 提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。
     */
    @Bean
    public EnterpriseCacheSessionDAO enterCacheSessionDAO() {
        EnterpriseCacheSessionDAO enterCacheSessionDAO = new EnterpriseCacheSessionDAO();
        //添加缓存管理器
        //enterCacheSessionDAO.setCacheManager(ehCacheManager());
        //添加ehcache活跃缓存名称(必须和ehcache缓存名称一致)
        enterCacheSessionDAO.setActiveSessionsCacheName("shiro-activeSessionCache");
        return enterCacheSessionDAO;
    }
  2. SessionManager配置:
/**
     *
     * @描述:sessionManager添加session缓存操作DAO
     * @创建人:wyait
     * @创建时间:2018年4月24日 下午8:13:52
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //sessionManager.setCacheManager(ehCacheManager());
        sessionManager.setSessionDAO(enterCacheSessionDAO());
        return sessionManager;
    }
  1. kickoutSessionFilter配置
/**
     *
     * @描述:kickoutSessionFilter同一个用户多设备登录限制
     * @创建人:wyait
     * @创建时间:2018年4月24日 下午8:14:28
     * @return
     */
    public KickoutSessionFilter kickoutSessionFilter(){
        KickoutSessionFilter kickoutSessionFilter = new KickoutSessionFilter();
        //使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;
        //这里我们还是用之前shiro使用的ehcache实现的cacheManager()缓存管理
        //也可以重新另写一个,重新配置缓存时间之类的自定义缓存属性
        kickoutSessionFilter.setCacheManager(ehCacheManager());
        //用于根据会话ID,获取会话进行踢出操作的;
        kickoutSessionFilter.setSessionManager(sessionManager());
        //是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;踢出顺序。
        kickoutSessionFilter.setKickoutAfter(false);
        //同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;
        kickoutSessionFilter.setMaxSession(1);
        //被踢出后重定向到的地址;
        kickoutSessionFilter.setKickoutUrl("/toLogin?kickout=1");
        return kickoutSessionFilter;
    }
  1. 将SessionManager交给SecurityManager管理
/**
     * shiro安全管理器设置realm认证、ehcache缓存管理、session管理器、Cookie记住我管理器
     * @return
     */
    @Bean public org.apache.shiro.mgt.SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(shiroRealm());
        // //注入ehcache缓存管理器;
        securityManager.setCacheManager(ehCacheManager());
        // //注入session管理器;
        securityManager.setSessionManager(sessionManager());
        //注入Cookie记住我管理器
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }
  1. 配置filterChainDefinitionMap
...
//添加kickout认证
HashMap<String,Filter> hashMap=new HashMap<String,Filter>();
hashMap.put("kickout",kickoutSessionFilter());
shiroFilterFactoryBean.setFilters(hashMap);
...
filterChainDefinitionMap.put("/**", "kickout,authc");
...

解决子页面,重定向之后,出现页面嵌套的问题

新增登录中转页面toLogin.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<!--head部分-->
<head th:include="layout :: htmlhead" th:with="title=‘利易达贷款后台‘">
</head>
<script type="text/javascript">
var href=window.location.href;
if(href.indexOf("kickout")>0){
    setTimeout("top.location.href=‘/login?kickout‘;", 0);
}else{
    setTimeout("top.location.href=‘/login‘;", 0);
}
</script>
</html>

更改shiro中filterChainDefinitionMap配置

// 指定要求登录时的链接
shiroFilterFactoryBean.setLoginUrl("/toLogin");
...
// 配置不会被拦截的链接 从上向下顺序判断
filterChainDefinitionMap.put("/login", "anon");

上面两个配置,即可解决页面重定向后,嵌套问题。

ajax请求问题

如果对用户在线数量进行限制,踢出了之前登录的用户A;这时候用户A在系统中,发送了一个ajax请求,会出现弹框空白等问题;

解决方案

  1. 自定义ShiroFilterUtils工具类判断请求是否为ajax
package com.wyait.manage.utils;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 *
 * @项目名称:wyait-manager
 * @类名称:ShiroFilterUtils
 * @类描述:shiro工具类
 * @创建人:wyait
 * @创建时间:2018年4月24日 下午5:12:04
 * @version:
 */
public class ShiroFilterUtils {
    private static final Logger logger = LoggerFactory
            .getLogger(ShiroFilterUtils.class);
    /**
     *
     * @描述:判断请求是否是ajax
     * @创建人:wyait
     * @创建时间:2018年4月24日 下午5:00:22
     * @param request
     * @return
     */
    public static boolean isAjax(ServletRequest request){
        String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
        if("XMLHttpRequest".equalsIgnoreCase(header)){
            logger.debug("shiro工具类【wyait-manager-->ShiroFilterUtils.isAjax】当前请求,为Ajax请求");
            return Boolean.TRUE;
        }
        logger.debug("shiro工具类【wyait-manager-->ShiroFilterUtils.isAjax】当前请求,非Ajax请求");
        return Boolean.FALSE;
    }
}
  1. 调整KickoutSessionFilter过滤器,新增ajax请求判断和响应
private final static ObjectMapper objectMapper = new ObjectMapper();
...
// 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);
}
...
/**
 *
 * @描述: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();
        }
    }
}
  1. 前端编写公共判断用户是否登录方法isLogin
/**
 * 判断是否登录,没登录刷新当前页,促使Shiro拦截后跳转登录页
 * @param result    ajax请求返回的值
 * @returns {如果没登录,刷新当前页}
 */
function isLogin(result){
    if(result && result.code && result.code == ‘1101‘){
        window.location.reload(true);//刷新当前页
    }
    return true;//返回true
}
  1. js中ajax调用isLogin方法
$.post("/user/delUser",{"id":id},function(data){
    //判断用户是否登录
    if(isLogin(data)){
        if(data=="ok"){
            //回调弹框
            layer.alert("删除成功!",function(){
                layer.closeAll();
                //加载load方法
                load(obj);//自定义
            });
        }else{
            layer.alert(data);//弹出错误提示
        }
    }
});

只改动了userList.js用户列表界面,其他界面//TODO

  1. 测试
    同一个用户在线冲突测试,然后点击先登录用户界面中其中一个ajax方法,如果后台用户已退出,前台isLogin刷新页面,重新请求重定向到/toLogin?kickout页面,最终跳转到登录界面。

session有效时间设置

session默认有效时间:30分钟(1800s)

  • spring boot session时间配置:

    # 会话超时(秒)1天
    server.session.timeout=86400
  • session有效时间问题
    使用shiro进行用户在线数量限制功能中,securityManager配置sessionManager之后,springboot中配置的session有效时间无效(sessionManager管理器覆盖了springboot中session有效时间的配置)。

session过期问题

使用shiro进行用户在线数量限制功能;用户登录后,2分钟不操作,之后session失效。

原因

  1. spring boot整合shiro,在使用shiro进行用户在线数量限制时,重新配置了SessionManger,
// //注入session管理器;
securityManager.setSessionManager(sessionManager());

SessionManager,配置EnterpriseCacheSessionDAO:

sessionManager.setSessionDAO(enterCacheSessionDAO());

EnterpriseCacheSessionDAO类,存取session的时候,是通过ehcache缓存中操作的。

这里如果配置有缓存的话需要给其配置一个cache的键类似于:

shiro默认了一个默认值为:shiro-activeSessionCache,如果不相同(cache文件中的键值) 需要进行替换,最终进行session存取的类为CachingSessionDAO

缓存管理器使用的是org.apache.shiro.cache.ehcache.EhCacheManager,那么最终shiro在找session的时候也会调用getCache。

Ehcache.xml配置

<!-- shiro-activeSessionCache活跃用户session缓存策略 -->
    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           timeToIdleSeconds="120"
           timeToLiveSeconds="120"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
    </cache>

这里配置了session缓存时间为2分钟,故会出现登录2分钟无操作后,session失效问题。

  1. shiro拿到ehcache缓存中的session后,和服务器中的session校验匹配,这时,如果服务器的session失效,也会出现问题;
    假设设置服务器端当前用户的session为30s【

    SecurityUtils.getSubject().getSession().setTimeout(30000);//毫秒

    】,ehcache中session有效时间120s不变;在无操作30s后,请求后台,报错如下:

org.apache.shiro.session.ExpiredSessionException: Session with id [8aac0daf-c432-44b6-86cc-a618095ad2bd] has expired. Last access time: 18-4-24 上午11:32.  Current time: 18-4-24 上午11:33.  Session timeout is set to 30 seconds (0 minutes)
    at org.apache.shiro.session.mgt.SimpleSession.validate(SimpleSession.java:292) ~[shiro-core-1.3.1.jar:1.3.1]
    at org.apache.shiro.session.mgt.AbstractValidatingSessionManager.doValidate(AbstractValidatingSessionManager.java:186) ~[shiro-core-1.3.1.jar:1.3.1]
... ...

故ehcache缓存中session的有效时间和服务器端session有效时间必须配置一致。

解决方案

  1. 服务端session时间设置:
//session有效时间1天(毫秒)
SecurityUtils.getSubject().getSession().setTimeout(86400000);
  • 设置的最大时间,正负都可以,为负数时表示永不超时。

    SecurityUtils.getSubject().getSession().setTimeout(-1000l);

    注意:这里设置的时间单位是:ms,但是Shiro会把这个时间转成:s,而且是会舍掉小数部分,这样设置的是-1ms,转成s后就是0s,马上就过期了。所有要是除以1000以后还是负数,必须设置小于-1000

  1. 将Ehcache.xml时间配置和服务器设置的session有效时间保持一致。

    <!-- shiro-activeSessionCache活跃用户session缓存策略(秒) -->
    <cache name="shiro-activeSessionCache"
           maxElementsInMemory="10000"
           timeToIdleSeconds="86400"
           timeToLiveSeconds="86400"
           maxElementsOnDisk="10000000"
           diskExpiryThreadIntervalSeconds="120"
           memoryStoreEvictionPolicy="LRU">
    </cache>

    通过代码中查看session有效时间:

logger.debug("session设置的有效时间:"+request.getSession().getMaxInactiveInterval());
logger.debug("shiro中session设置的有效时间:"+SecurityUtils.getSubject().getSession().getTimeout());
//86400(秒)
//86400000(毫秒)

前篇:spring boot + mybatis + layui + shiro后台权限管理系统

总结

具体实现可以根据具体需求做调整;近期提供redis实现版本。

原文地址:http://blog.51cto.com/wyait/2107423

时间: 2024-08-25 22:09:57

springboot + shiro之登录人数限制、登录判断重定向、session时间设置的相关文章

springboot + shiro 权限注解、请求乱码解决、统一异常处理

springboot + shiro 权限注解.请求乱码解决.统一异常处理 前篇 后台权限管理系统 相关: spring boot + mybatis + layui + shiro后台权限管理系统 springboot + shiro之登录人数限制.登录判断重定向.session时间设置 springboot + shiro 动态更新用户信息 基于前篇,新增功能: 新增shiro权限注解: 请求乱码问题解决: 统一异常处理. 源码已集成到项目中: github源码: https://githu

第十八章 并发登录人数控制——《跟我学Shiro》(http://blog.csdn.net/lhacker/article/details/19334305)

第十八章 并发登录人数控制——<跟我学Shiro> 博客分类: 跟我学Shiro 跟我学Shiro 目录贴:跟我学Shiro目录贴 在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录:要么踢出前者登录(强制退出).比如spring security就直接提供了相应的功能:Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能. 示例代码基于<第十六章 综合实例>完成,通过Shiro Filter机制扩展Ki

SpringBoot 并发登录人数控制

通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 demo 技术选型 SpringBoot JWT Filter Redis + Redisson JWT(token)存储在Redis中,类似 JSessionId-Session的关系,用户登录后每次请求在Header中携带jwt 如果你是使用session的话,也完全可以借鉴本文的思路,只是代码上需要

springboot+Shiro+登录

1.springboot+Shiro+登录 2.引入相关支持 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.2</version> </dependency> <dependency> <groupId>org.apache.shiro&l

Springboot集成Shiro和Cas实现单点登录(服务端篇CAS5)

什么是单点登录? 先说一个需求场景,比如:一个企业的内部有N多个子系统,每个子系统都有一套自己的用户名和密码,那么企业的员工要登录N个子系统,这样一个员工 就要记住N个用户名和密码,就算各个子系统的用户名和密码都是统一的,登录每个子系统都要输入用户名和密码进行登录也是一个繁琐的操作过程,那么单点登录功能由此便应运而生了.单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应

页面在线访问人数统计&amp;&amp;在线登录人数统计一

一.页面在线访问人数统计 OnlineSessionListener监听器(实现HttpSessionListener)接口来实现页面在线访问人数统计,当有用户访问到页面就会创建一个session,此时会触发public   void   sessionCreated(HttpSessionEvent   se)方法,如果session失效,此时会触发public   void   sessionDestroyed(HttpSessionEvent   se)方法.在此方法里我们用OnlineL

php判断页面是电脑登录还是手机登录

首先说最根本的解决方法: 手机访问时,会附带发送user-agent信息,这个信息里面会有手机号码信息,那么如果能取得手机号码,则可以肯定是通过手机wap访问的.但是目前 中国移动已经屏蔽了user-agent信息,所以获取不到手机号码.有关系的朋友可以联系移动公司,把wap网站服务器的ip提交给中国移动,加入白名 单后即可取得ua信息.目前中国联通可以直接取到手机号,对联通用户此方案可完美实施. 接下来说我的解决方案: 手机访问,原理是手机通过移动公司的代理服务器进行的访问.那么我们就可以理解

spring security登录人数限制并且同一个账号可以踢掉前一个用户配置文件

一.限制用户登录数和session自动托管 1.maximumSessions:限制登录人数 2.exceptionIfMaximumExceeded: 为true同一账户只能登录一次, 为false同一账户可以登录多次如果配置了org.springframework.security.web.session.ConcurrentSessionFilter则会踢出前一个登录的session 3.sessionRegistry配置session管理 4.concurrentSessionFilte

vue 判断是否登录,未登录跳转到登录页

网页一进入判断是否登录,未登录跳转到登录页面 router.js export default new Router({ routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld, meta: { title: '首页', requiresAuth: true // 是否需要判断是否登录 } }, { path: '/login', name: 'login', component: login, meta: { title