shiro中提供了rememberMe功能,它用起来是这样的html
[java] view plaincopyjava
UsernamePasswordToken token = new UsernamePasswordToken(loginForm.getUsername(),loginForm.getPassword()); web
if(loginForm.getRememberMe() != null && "Y".equals(loginForm.getRememberMe())){ spring
token.setRememberMe(true); 数据库
} apache
你能够本身设置一个标志位,而后根据这个标志位判断一下用户是否勾选了记住我,若是勾选了就使用 token.setRememberMe(true) 设置为记住我。浏览器
相信不少人跟我一开始想的同样,以为这样设置完了,而后不退出直接关浏览器再打开浏览器,进入咱们的网站就会自动登录。可是结果是:当你重开了浏览器后,进入网站依然让你输入用户名和密码!安全
那么,究竟这个功能要怎么使用呢?cookie
其实你设置了这个rememberMe以后shiro仍是有作一点事情的,它会生成一个cookie值叫 rememberMe 并保存在你的浏览器里面,并且这个参数会随着你调用 subject.logout() 会被自动清除。这个参数的值是一串很长的Base64加密过的字符串,大概长这样session
[plain] view plaincopy
名称: rememberMe
内容: 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 = true 也不能算是 authc 的而是 user 级别的。
咱们通常设置路径拦截是这样设置的
[plain] view plaincopy
/** = authc
这样就保证了全部路径都须要登录才能访问。就算你是 rememberMe=true也不能访问,官方说你若是设置成拦截级别为user就能访问,好比
[html] view plaincopy
/** = user
这样就能够访问了,可是官方建议不敏感的部分用user,敏感的部分仍是要让用户再登录一次,就像你上淘宝网就算不登录,只要上一次有登录过,你依然能够直接看个人淘宝那个页面,可是点击 个人宝贝的时候就又要让你登录了。
可是!咱们的确有不少时候是须要记住用户就至关于用户登陆了!
设置成user这个方案还有一个问题,就是咱们实际项目中在登录后有作了不少设置用户上下文的工做,好比设置session等,若是咱们只是设置拦截级别为user,那么再次进入的时候虽然能够访问,可是session是空的,咱们的页面必然异常频出。
采用这个解决方案的前提是,你必须本身先实现一个realm,不过这个我相信你们都会实现的,毕竟默认的不是jdbcRealm ,真正的项目都是要查数据库才能肯定用户是否登陆的。那么我就假定你们的项目中都有那么一个负责验证登陆的 JdbcRealm, 而且是采用用户名密码认证的,在 doGetAuthenticationInfo 方法里面是采用以下的方法来作认证
[java] view plaincopy
...
info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
这个前提条件保证你的principal是username,相信大部分人根据教程作shiro的时候都采用了这种方式
作一个新类继承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();
}
}
若是你用的是spring那么
[html] view plaincopy
<!-- 整合了rememberMe功能的filter -->
<bean id="rememberAuthFilter" class="com.yqr.jxc.shiro.RememberAuthenticationFilter" ></bean>
<!--将以前的 /** = authc 替换成 rememberAuthFilter
...
/** = rememberAuthFilter
...
若是你用的是 ini 文件,那么
[plain] view plaincopy
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 精彩的问答写成的