• Shiro的 rememberMe 功能使用指导(为什么rememberMe设置了没作用?)


    问题

    shiro中提供了rememberMe功能。它用起来是这样的

    UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getUsername(),loginForm.getPassword());
    		
    		if(loginForm.getRememberMe() != null && "Y".equals(loginForm.getRememberMe())){
    			token.setRememberMe(true);
    		}

    你能够自己设置一个标志位,然后依据这个标志位推断一下用户是否勾选了记住我,假设勾选了就使用 token.setRememberMe(true) 设置为记住我。

    相信非常多人跟我一開始想的一样,认为这样设置完了。然后不退出直接关浏览器再打开浏览器,进入我们的站点就会自己主动登陆。

    可是结果是:当你重开了浏览器后,进入站点依旧让你输入用户名和password。

    那么,到底这个功能要怎么使用呢?

    原理解释

    shiro对cookie做了什么?

    事实上你设置了这个rememberMe之后shiro还是有做一点事情的,它会生成一个cookie值叫 rememberMe 并保存在你的浏览器里面,而且这个參数会随着你调用 subject.logout() 会被自己主动清除。这个參数的值是一串非常长的Base64加密过的字符串,大概长这样
    名称:	rememberMe
    内容:	6gYvaCGZaDXt1c0xwriXj/Uvz6g8OMT3VSaAK4WL0Fvqvkcm0nf3CfTwkWWTT4EjeSS/EoQjRfCPv4WKUXezQDvoNwVgFMtsLIeYMAfTd17ey5BrZQMxW+xU1lBSDoEM1yOy/i11ENh6eXjmYeQFv0yGbhchGdJWzk5W3MxJjv2SljlW4dkGxOSsol3mucoShzmcQ4VqiDjTcbVfZ7mxSHF/0M1JnXRphi8meDaIm9IwM4Hilgjmai+yzdVHFVDDHv/vsU/fZmjb+2tJnBiZ+jrDhl2Elt4qBDKxUKT05cDtXaUZWYQmP1bet2EqTfE8eiofa1+FO3iSTJmEocRLDLPWKSJ26bUWA8wUl/QdpH07Ymq1W0ho8EIdFhOsELxM66oMcj7a/8LVzypJXAXZdMFaNe8cBSN2dXpv4PwiktCs3J9P9vP4XrmYees5x27UmXNqYFk86xQhRjFdJsw5A9ctDKXzPYvJmWFouo3qT5hugX0uxWALCfWg8MHJnG9w7QgVKM8oy3Xy4Ut8lSvYlA==

    这串字符串事实上是对你登陆后的 Principal 进行了序列化后再Base64的结果。

    Principal 是 shiro 的一个概念,表示一个唯一的字符串能表示你这个用户的,假设你依照最简单的用户名password登陆的方式,而且使用的是 SimpleAuthenticationInfo 对象。那么这个 Principal 事实上就是一个字符串,就是你的用户名 username

    所以这串东西解密出来就是你的username

    shiro认为rememberMe不安全

    shiro认为不能把rememberMe等同于已经登陆了,这样不安全。所以shiro 认为就算 rememberMe = true 也不能算是 authc 的而是 user 级别的。
    我们一般设置路径拦截是这样设置的
    /** = authc

    这样就保证了全部路径都须要登陆才干訪问。

    就算你是 rememberMe=true也不能訪问,官方说你假设设置成拦截级别为user就能訪问,比方

    /** = user

    这样就能够訪问了。可是官方建议不敏感的部分用user。敏感的部分还是要让用户再登陆一次,就像你上淘宝网就算不登陆,仅仅要上一次有登陆过,你依旧能够直接看我的淘宝那个页面,可是点击 我的宝贝的时候就又要让你登陆了。
    可是!我们的确有非常多时候是须要记住用户就相当于用户登录了!

    设置成user这个方法另一个问题,就是我们实际项目中在登陆后有做了非常多设置用户上下文的工作,比方设置session等,假设我们仅仅是设置拦截级别为user。那么再次进入的时候尽管能够訪问,可是session是空的,我们的页面必定异常频出。

    解决方式

    前提条件

    採用这个解决方式的前提是,你必须自己先实现一个realm。只是这个我相信大家都会实现的,毕竟默认的不是jdbcRealm ,真正的项目都是要查数据库才干确定用户是否登录的。那么我就假定大家的项目中都有那么一个负责验证登录的 JdbcRealm, 而且是採用用户名password认证的。在 doGetAuthenticationInfo 方法里面是採用例如以下的方法来做认证
    ...
    info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());

    这个前提条件保证你的principal是username,相信大部分人依据教程做shiro的时候都採用了这样的方式

    STEP1  复写 FormAuthenticationFilter 的 isAccessAllowed 方法

    做一个新类继承FormAuthenticationFilter ,并复写 isAccessAllowed  方法
    package com.yqr.jxc.shiro;
    
    import javax.annotation.Resource;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
    
    import com.yqr.jxc.service.global.GlobalUserService;
    
    public class RememberAuthenticationFilter extends FormAuthenticationFilter {
    	
    	@Resource(name="globalUserService")
    	private GlobalUserService globalUserService;
    	
            /**
            * 这种方法决定了能否让用户登录
            */
    	@Override
    	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            Subject subject = getSubject(request, response);
    
            //假设 isAuthenticated 为 false 证明不是登录过的,同一时候 isRememberd 为true 证明是没登陆直接通过记住我功能进来的
            if(!subject.isAuthenticated() && subject.isRemembered()){
    
                    //获取session看看是不是空的
            	Session session = subject.getSession(true);
                    
                    //随便拿session的一个属性来看session当前是否是空的。我用userId,你们的项目能够自行发挥
            	if(session.getAttribute("userId") == null){
    
            		//假设是空的才初始化,否则每次都要初始化,项目得慢死
                            //这边依据前面的前提假设,拿到的是username
            		String username = subject.getPrincipal().toString();
            		
            		//在这种方法里面做初始化用户上下文的事情,比方通过查询数据库来设置session值,你们自己发挥
            		globalUserService.initUserContext(username, subject);
            	}
            }
    
            //这种方法本来仅仅返回 subject.isAuthenticated() 如今我们加上 subject.isRemembered() 让它同一时候也兼容remember这样的情况
            return subject.isAuthenticated() || subject.isRemembered();
        }
    }

    STEP2 设置使用这个新的 AuthenticationFilter (认证过滤器)

    假设你用的是spring那么
    <!-- 整合了rememberMe功能的filter -->
    <bean id="rememberAuthFilter" class="com.yqr.jxc.shiro.RememberAuthenticationFilter" ></bean>
    
    <!--将之前的 /** = authc 替换成 rememberAuthFilter
    ...
    /** = rememberAuthFilter
    ...

    假设你用的是 ini 文件。那么
    rememberAuthFilter=com.yqr.jxc.shiro.RememberAuthenticationFilter
    
    #将之前的 /** = authc 替换成 rememberAuthFilter
    ...
    /** = rememberAuthFilter

    然后重新启动项目我们来測试一下。先登录一次系统。然后直接关掉浏览器。然后打开浏览器直接输入系统某个页面的地址,发现能够直接进去了。session什么的也设置好了

    看起来非常美?可是。

    忙活了半天。最后我还是决定在我的系统中撤下了这个功能。为什么呢?由于这个功能有个致命的安全缺陷就是随便谁把这个cookie值拿到别的浏览器都能够登录。就算你用再牛逼的加密,或者是这个cookie值依据浏览器的各个别的属性来达到仅供这个浏览器使用。可是对于黑客来说,仅仅要你是通过表单把东西发送出去,这整个表单都是能够伪造的。

    就算是添加了过期时间。在这段时间之内还是有被伪造的风险,我眼下没有想到什么好的解决方式。

    唯一能想到的就是对于使用场景的选择,在严格的业务系统中不能使用记住我这个功能。在非严格的系统中,比方不敏感的系统,像看看流量看看微博之类的。还是能够使用以上的方式来解决rememberMe的问题的。
    所以。请慎重选择是否要将 rememberMe 功能范围扩大化!

    最后感谢来自俄罗斯的 meri 的这篇精辟的shiro研究文 http://meri-stuff.blogspot.com/2011/03/apache-shiro-part-1-basics.html 
    本文是依据meri 和 blurblurNick 精彩的问答写成的

  • 相关阅读:
    多线程--同步--方法块和同步块synchronized
    CentOS7.6安装Nodejs(Npm)
    [原][译]关于osgEarth::VirtualProgram说明
    [转]opengl的学习网站
    [转]OpenGL中的功能与OSG对应功能
    [原]最简单的c语言,出错输出,日志打印 以及 C预定义的宏
    [转]netcdf入门
    [NetCDF][C++] 使用NetCDF 的接口读取数值
    [python] pip安装国外软件库(包)失败,解决方案
    [原][python]安装python,读取、遍历excel表格
  • 原文地址:https://www.cnblogs.com/mqxnongmin/p/10613995.html
Copyright © 2020-2023  润新知