UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getUsername(),loginForm.getPassword());  

        if(loginForm.getRememberMe() != null && "Y".equals(loginForm.getRememberMe())){  

            token.setRememberMe(true);  

        }  

你可以自己设置一个标志位,然后根据这个标志位判断一下用户是否勾选了记住我,如果勾选了就使用 token.setRememberMe(true) 设置为记住我。

相信很多人跟我一开始想的一样,觉得这样设置完了,然后不退出直接关浏览器再打开浏览器,进入我们的网站就会自动登陆。但是结果是:当你重开了浏览器后,进入网站依然让你输入用户名和密码!

那么,究竟这个功能要怎么使用呢?

原理解释

shiro对cookie做了什么?

其实你设置了这个rememberMe之后shiro还是有做一点事情的,它会生成一个cookie值叫 rememberMe 并保存在你的浏览器里面,而且这个参数会随着你调用 subject.logout() 会被自动清除。这个参数的值是一串很长的Base64加密过的字符串,大概长这样

  1. 名称: rememberMe

  2. 内容: 6gYvaCGZaDXt1c0xwriXj/Uvz6g8OMT3VSaAK4WL0Fvqvkcm0nf3CfTwkWWTT4EjeSS/EoQjRfCPv4WKUXezQDvoNwVgFMtsLIeYMAfTd17ey5BrZQMxW+xU1lBSDoEM1yOy/i11ENh6eXjmYeQFv0yGbhchGdJWzk5W3MxJjv2SljlW4dkGxOSsol3mucoShzmcQ4VqiDjTcbVfZ7mxSHF/0M1JnXRphi8meDaIm9IwM4Hilgjmai+yzdVHFVDDHv/vsU/fZmjb+2tJnBiZ+jrDhl2Elt4qBDKxUKT05cDtXaUZWYQmP1bet2EqTfE8eiofa1+FO3iSTJmEocRLDLPWKSJ26bUWA8wUl/QdpH07Ymq1W0ho8EIdFhOsELxM66oMcj7a/8LVzypJXAXZdMFaNe8cBSN2dXpv4PwiktCs3J9P9vP4XrmYees5x27UmXNqYFk86xQhRjFdJsw5A9ctDKXzPYvJmWFouo3qT5hugX0uxWALCfWg8MHJnG9w7QgVKM8oy3Xy4Ut8lSvYlA==

这串字符串其实是对你登陆后的 Principal 进行了序列化后再Base64的结果。Principal 是 shiro 的一个概念,表示一个唯一的字符串能表示你这个用户的,如果你按照最简单的用户名密码登陆的方式,并且使用的是 SimpleAuthenticationInfo 对象,那么这个 Principal 其实就是一个字符串,就是你的用户名 username

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

shiro觉得rememberMe不安全

shiro觉得不能把rememberMe等同于已经登陆了,这样不安全。所以shiro 觉得就算 rememberMe = true 也不能算是 authc 的而是 user 级别的。

我们一般设置路径拦截是这样设置的

[plain] view plaincopy

  1. /** = authc

这样就保证了所有路径都需要登陆才能访问。就算你是 rememberMe=true也不能访问,官方说你如果设置成拦截级别为user就能访问,比如

[html] view plaincopy

  1. /** = user

这样就可以访问了,但是官方建议不敏感的部分用user,敏感的部分还是要让用户再登陆一次,就像你上淘宝网就算不登陆,只要上一次有登陆过,你依然可以直接看我的淘宝那个页面,但是点击 我的宝贝的时候就又要让你登陆了。

但是!我们的确有很多时候是需要记住用户就相当于用户登录了!

设置成user这个方案还有一个问题,就是我们实际项目中在登陆后有做了很多设置用户上下文的工作,比如设置session等,如果我们只是设置拦截级别为user,那么再次进入的时候虽然可以访问,但是session是空的,我们的页面必然异常频出。

解决方案

前提条件

采用这个解决方案的前提是,你必须自己先实现一个realm,不过这个我相信大家都会实现的,毕竟默认的不是jdbcRealm ,真正的项目都是要查数据库才能确定用户是否登录的。那么我就假定大家的项目中都有那么一个负责验证登录的 JdbcRealm, 并且是采用用户名密码认证的,在 doGetAuthenticationInfo 方法里面是采用如下的方法来做认证

[java] view plaincopy

  1. ...

  2. info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());

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

STEP1  复写 FormAuthenticationFilter 的 isAccessAllowed 方法

做一个新类继承FormAuthenticationFilter ,并复写 isAccessAllowed  方法

[java] view plaincopy

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那么

[html] view plaincopy在CODE上查看代码片派生到我的代码片

<!-- 整合了rememberMe功能的filter -->  

<bean id="rememberAuthFilter" class="com.yqr.jxc.shiro.RememberAuthenticationFilter" ></bean>  

<!--将之前的 /** = authc 替换成 rememberAuthFilter  

...  

/** = rememberAuthFilter  

...  

如果你用的是 ini 文件,那么

[plain] view plaincopy

  1. rememberAuthFilter=com.yqr.jxc.shiro.RememberAuthenticationFilter

  2. #将之前的 /** = authc 替换成 rememberAuthFilter

  3. ...

  4. /** = rememberAuthFilter

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

看起来很美?但是!

忙活了半天,最后我还是决定在我的系统中撤下了这个功能。为什么呢?因为这个功能有个致命的安全缺陷就是随便谁把这个cookie值拿到别的浏览器都可以登录。就算你用再牛逼的加密,或者是这个cookie值根据浏览器的各个别的属性来达到仅供这个浏览器使用,但是对于黑客来说,只要你是通过表单把东西发送出去,这整个表单都是可以伪造的。就算是增加了过期时间,在这段时间之内还是有被伪造的风险,我目前没有想到什么好的解决方案。

唯一能想到的就是对于使用场景的选择,在严格的业务系统中不能使用记住我这个功能,在非严格的系统中,比如不敏感的系统,像看看流量看看微博之类的,还是可以使用以上的方式来解决rememberMe的问题的。

所以,请谨慎选择是否要将 rememberMe 功能范围扩大化!

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

Shiro的 rememberMe 功能使用指导(为什么rememberMe设置了没作用?)的更多相关文章

  1. shiro + jwt 实现 请求头中的 rememberMe 时间限制功能

    前言: 上一篇提出, 通过修改 rememberMe 的编码来实现 rememberMe的功能的设想, 事后我去尝试实现了一番, 发现太麻烦, 还是不要那么做吧. 程序还是要越简单越好. 那功能总是要 ...

  2. [shiro] - 加入rememberMe功能

    shiro不加入rememberMe没事,一加入就出错. RememberMeAuthenticationToken : public interface RememberMeAuthenticati ...

  3. SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)

    首先在shiro配置类中注入rememberMe管理器 /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cooki ...

  4. Remember-Me功能

    Remember-Me功能 目录 1.1概述 1.2基于简单加密token的方法 1.3基于持久化token的方法 1.4Remember-Me相关接口和实现类 1.4.1TokenBasedReme ...

  5. Spring Security(12)——Remember-Me功能

    目录 1.1     概述 1.2     基于简单加密token的方法 1.3     基于持久化token的方法 1.4     Remember-Me相关接口和实现类 1.4.1    Toke ...

  6. Spring Security教程(七):RememberMe功能

    在之前的教程中一笔带过式的讲了下RememberMe记住密码的功能,那篇的Remember功能是最简易的配置,其功能和安全性都不强.这里就配置下security中RememberMe的各种方式. 一. ...

  7. springsecurity remember-me 功能

    本文基于spring-security-web-4.1.2.RELEASE. 要实现rememberMe,有两种方案. 1.基于简单加密token的方法 首先需要在配置文件中加入<remembe ...

  8. SpringSecurity remember-me功能

    1./login .and().formLogin().loginPage("/user/login.html") //在successHandler中,使用response返回登 ...

  9. SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)

    项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)验证码 首先login.jsp里增加了获取验证码图片的标签: <body s ...

随机推荐

  1. Mac上安装Jenkins持续部署初体验

    1.首先去官网下载安装包 https://jenkins.io/doc/book/installing/ 安装完成后,很不幸,无法访问他的默认站点 查了一堆资料,有的说环境变量没有配置,有的说没有执行 ...

  2. 使用Android Studio自带的NDK编译JNI

    /********************************************************************************** * 使用Android Stud ...

  3. python学习之函数和函数参数

    #方法的参数定义和默认参数的定义 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'): while True: ok = inp ...

  4. 【数据库】SQLite学习

    http://www.cnblogs.com/fnng/archive/2013/05/26/3099547.html

  5. Qt图形视图体系结构示例解析(视图、拖拽、动画)

    本博的示例来自与QT Example:C:\Qt\Qt5.9.3\Examples\Qt-5.9.3\widgets\graphicsview\dragdroprobot 将通过分析示例完成主要功能: ...

  6. 转载 eclipse中的include设置

    备注:在10.1版的niosii使用的eclipse中设置的方法是右键->properties->c/c++general->path and symbols->include ...

  7. LA2797 Monster Trap

    题意 PDF 分析 可以考虑建图,跑迷宫. 然后以线段端点,原点,和无穷大点建图,有边的条件是两点连线和墙没有交点. 但是对两个线段的交点处理就会有问题,所以把线段延长.另外还需要判断延长后在墙上,舍 ...

  8. Maven build标签

    前言: <build >设置,主要用于编译设置 1.分类 在Maven的pom.xml文件中,存在如下两种<build>: (1)全局配置(project build) 针对整 ...

  9. sqlalchemy的基本的使用

    参考链接:http://www.techweb.com.cn/network/system/2016-10-11/2407638.shtml http://www.cnblogs.com/renfan ...

  10. 在Activity中使用Menu

    1.右击res-->New-->Directory输入文件名menu,在此文件夹下新建main菜单文件:右击menu-->New-->Menu resource file 2. ...