shiro单用户登录实现

1.需求:账号同一时间只能在一处登录、将之前的用户踢出:

shiro实现策略:获取当前用户session列表,直接删除该用户的其他登录信息


import org.apache.shiro.session.mgt.eis.SessionDAO;
@Autowired
private SessionDAO sessionDAO;

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String userName = (String)authenticationToken.getPrincipal();

    //处理session
    Collection<Session> sessions = sessionDAO.getActiveSessions();//获取当前已登录的用户session列表
    for(Session session:sessions){
        //清除该用户以前登录时保存的session
        Object obj =session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

        SimplePrincipalCollection coll = (SimplePrincipalCollection) obj;
        if(coll !=null){
            UserDO userddo =  (UserDO)coll.getPrimaryPrincipal();
            if(userName.equals(userddo.getUsername())){
                sessionDAO.delete(session);
            }
        }

    }

    String pwd = null;
    return new SimpleAuthenticationInfo(userName,pwd,getName());
}

UserDO : 保存用户信息的实体类

直接将session用户信息删除,shiro会直接跳到登录界面。

用户体验不是太好,所增加一个友好界面提示用户

:实体类增加字段 name: conflict   type: boolean

   获取当前已登录的用户session列表、增加字段判断,拦截器判断该字段是否符合条件进行跳转。

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String userName = (String)authenticationToken.getPrincipal();

    //处理session
    Collection<Session> sessions = sessionDAO.getActiveSessions();//获取当前已登录的用户session列表
    for(Session session:sessions){
        //清除该用户以前登录时保存的session
        Object obj =session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);

        SimplePrincipalCollection coll = (SimplePrincipalCollection) obj;
        if(coll !=null){
            UserDO userddo =  (UserDO)coll.getPrimaryPrincipal();
            if(userName.equals(userddo.getUsername())){
                    //sessionDAO.delete(session);
                    userddo.setConflict(true);
                    sessionDAO.update(session);
            }
        }

    }

    String pwd = null;
    return new SimpleAuthenticationInfo(userName,pwd,getName());
}

拦截器判断是否有效

public class LoginFormFilter extends FormAuthenticationFilter {

    @Override
    public boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {//登录
        String ip =IPUtils.getLastRealIP((HttpServletRequest) request);
        if(!ShiroUtils.getSubjct().isAuthenticated() && ModuleConfig.IpWhite().containsKey(ip)){
            IPToken token = new IPToken(ModuleConfig.IpWhite().get(ip),ip);
            Subject subject = getSubject(request, response);
            subject.login(token);
        }
        String urlString = ((HttpServletRequest) request).getRequestURI();
        System.out.println(urlString);
        //这个判断必须要添加不然会无限循环
        if(!urlString.equals("isCasNot") && !urlString.endsWith("logout")){

            if(ShiroUtils.getSubjct().isAuthenticated()){

                if(ShiroUtils.getUser().getConflict()){
                    //跳转到单用户提示页面
                    WebUtils.issueRedirect(request, response, "isCasNot");
                    //返回false表示不执行后续的过滤器,直接返回跳转单用户提示页面
                    return false;
                }

            }
        }


        return  super.preHandle(request,response);
    }
}

Controller:

@GetMapping("/isCasNot")
    String isCasNot() {
        return prefix + "/isCasNot";
    }

    @GetMapping("/CasNot")
    ModelAndView CasNot() {
        ModelAndView mv = new ModelAndView();

        ShiroUtils.logout();
        mv.setViewName("redirect:/login");

		return mv;
    }
isCasNot.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<style>
    body {
        margin: 0;
        padding: 0;
        background: #E6EAEB;
        font-family: Arial, '微软雅黑', '宋体', sans-serif
    }
    .alert-box {
        display: none;
        position: relative;
        margin: 96px auto 0;
        padding: 180px 85px 22px;
        border-radius: 10px 10px 0 0;
        background: #FFF;
        box-shadow: 5px 9px 17px rgba(102,102,102,0.75);
        width: 286px;
        color: #FFF;
        text-align: center
    }
    .alert-box p {
        margin: 0
    }
    .alert-circle {
        position: absolute;
        top: -50px;
        left: 111px
    }
    .alert-sec-circle {
        stroke-dashoffset: 0;
        stroke-dasharray: 735;
        transition: stroke-dashoffset 1s linear
    }
    .alert-sec-text {
        position: absolute;
        top: 11px;
        left: 190px;
        width: 76px;
        color: #000;
        font-size: 68px
    }
    .alert-sec-unit {
        font-size: 34px
    }
    .alert-body {
        margin: 35px 0
    }
    .alert-head {
        color: #242424;
        font-size: 28px
    }
    .alert-concent {
        margin: 25px 0 14px;
        color: #7B7B7B;
        font-size: 18px
    }
    .alert-concent p {
        line-height: 27px
    }
    .alert-btn {
        display: block;
        border-radius: 10px;
        background-color: #4AB0F7;
        height: 55px;
        line-height: 55px;
        width: 286px;
        color: #FFF;
        font-size: 20px;
        text-decoration: none;
        letter-spacing: 2px
    }
    .alert-btn:hover {
        background-color: #6BC2FF
    }
    .alert-footer {
        margin: 0 auto;
        height: 42px;
        width: 120px
    }
    .alert-footer-icon {
        float: left
    }
    .alert-footer-text {
        float: left;
        border-left: 2px solid #EEE;
        padding: 3px 0 0 5px;
        height: 40px;
        color: #0B85CC;
        font-size: 12px;
        text-align: left
    }
    .alert-footer-text p {
        color: #7A7A7A;
        font-size: 22px;
        line-height: 18px
    }
</style>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Yek系统</title>
    <meta name="keywords" content="Yek系统">
    <meta name="description" content="Yek系统">
</head>
<body class="ie8">
<div id="js-alert-box" class="alert-box"> <svg class="alert-circle" width="234" height="234">
    <circle cx="117" cy="117" r="108" fill="#FFF" stroke="#43AEFA" stroke-width="17"></circle>
    <circle id="js-sec-circle" class="alert-sec-circle" cx="117" cy="117" r="108" fill="transparent" stroke="#F4F1F1" stroke-width="18" transform="rotate(-90 117 117)"></circle>
    <text class="alert-sec-unit" x="82" y="172" fill="#BDBDBD">secs</text>
</svg>
    <div id="js-sec-text" class="alert-sec-text"></div>
    <div class="alert-body">
        <div id="js-alert-head" class="alert-head"></div>
        <div class="alert-concent">
            <p>您可以选择重新登录或者修改密码</p>
            <p>您将在我们的网站享受更多</p>
        </div>
        <a id="js-alert-btn" class="alert-btn" href="CasNot">立即前往登录</a> </div>
    <div class="alert-footer clearfix"> <svg width="46px" height="42px" class="alert-footer-icon">
        <circle fill-rule="evenodd" clip-rule="evenodd" fill="#7B7B7B" stroke="#DEDFE0" stroke-width="2" stroke-miterlimit="10" cx="21.917" cy="21.25" r="17"/>
        <path fill="#FFF" d="M22.907,27.83h-1.98l0.3-2.92c-0.37-0.22-0.61-0.63-0.61-1.1c0-0.71,0.58-1.29,1.3-1.29s1.3,0.58,1.3,1.29 c0,0.47-0.24,0.88-0.61,1.1L22.907,27.83z M18.327,17.51c0-1.98,1.61-3.59,3.59-3.59s3.59,1.61,3.59,3.59v2.59h-7.18V17.51z M27.687,20.1v-2.59c0-3.18-2.59-5.76-5.77-5.76s-5.76,2.58-5.76,5.76v2.59h-1.24v10.65h14V20.1H27.687z"/>
        <circle fill-rule="evenodd" clip-rule="evenodd" fill="#FEFEFE" cx="35.417" cy="10.75" r="6.5"/>
        <polygon fill="#7B7B7B" stroke="#7B7B7B" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="35.417,12.16 32.797,9.03 31.917,10.07 35.417,14.25 42.917,5.29 42.037,4.25 "/>
    </svg>
        <div class="alert-footer-text">
            <p>secure</p>
            安全加密 </div>
    </div>
</div>

<i style="position: fixed;bottom:2%;left: 40%;font-style:normal">@2018 上海科技CSDN有限公司
</i>
<!-- 全局js -->
<script th:src="@{/js/jquery.min.js?v=2.1.4}"></script>

</body>
<script>
    function alertSet(e) {
        document.getElementById("js-alert-box").style.display = "block",
            document.getElementById("js-alert-head").innerHTML = e;
        var t = 10,
            n = document.getElementById("js-sec-circle");
        document.getElementById("js-sec-text").innerHTML = t,
            setInterval(function() {
                    if (0 == t){
                        location.href="CasNot";//#时间到后跳转地址
                    }else {
                        t -= 1,
                            document.getElementById("js-sec-text").innerHTML = t;
                        var e = Math.round(t / 10 * 735);
                        n.style.strokeDashoffset = e - 735
                    }
                },
                970);
    }

</script>
<script language="javascript">
    //防止页面受弹窗影响
        if (window != top){
            top.location.href = location.href;
        };

    //防止页面后退
    history.pushState(null, null, document.URL);
    window.addEventListener('popstate', function () {
        history.pushState(null, null, document.URL);
    });
</script>
</html>
<script>alertSet('账号已在别处登录');</script>

页面效果:

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值