1、什么是rememberMe
shiro为我们提供了rememberMe功能,也就是记住我功能,这个功能的作用很简单,就是记住我,很多网站上都会有15天免登陆之类的功能,当我们设置了rememberMe功能后,只要登录一次,在不手动退出登录的情况下直接关闭浏览器,然后再次打开浏览器,仍然可以直接访问到属于自己的信息,这里就实现了记住我功能。
2、rememberMe工作原理
- 我们打开浏览器,进入我们的权限验证页面,可以看到session信息
- 在我们勾选了记住我后,再次登陆,如果登录成功,则会在此位置生成记住我的一个cooike,shiro会记住我们这个识别码,用作登陆过用户的标记来实现user拦截器
- 我们查看shiro登录的部分源码,可以找到在AbstractRememberMeManager类的287行,部分判断记住我源码
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//always clear any previous identity:
forgetIdentity(subject);
//如果记住我是true,会执行下面这段代码
if (isRememberMe(token)) {
rememberIdentity(subject, token, info);
} else {
if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. " +
"RememberMe functionality will not be executed for corresponding account.");
}
}
}
继续深入查看,可以查看到最终调用了CookieRememberMeManager类中部分代码实现了写入cookie
protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
if (!WebUtils.isHttp(subject)) {
if (log.isDebugEnabled()) {
String msg = "Subject argument is not an HTTP-aware instance. This is required to obtain a servlet " +
"request and response in order to set the rememberMe cookie. Returning immediately and " +
"ignoring rememberMe operation.";
log.debug(msg);
}
return;
}
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject);
//base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
Cookie template = getCookie(); //the class attribute is really a template for the outgoing cookies
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}
3、使用rememberMe
- 在登录接口上设置setRememberMe(true),我们在页面上已经设置好,勾选选项即可
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
if (rememberMe != null) {
token.setRememberMe(rememberMe);
}
try {
// 登录
subject.login(token);
- 在ShiroController新增/user接口
@RequestMapping("/user")
public ReturnMap user() {
return new ReturnMap().success().data("user可以访问");
}
- 在shiro的 shirFilter方法中新增user拦截器,
注意要写在 “/**”, "authc"前面
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login", "anon");
// 配置退出 过滤器,其中具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/user", "user");
// 因为目前演示页面依附在此项目下,特为演示页面新增可无权限访问,前后端分离后无需此设置
filterChainDefinitionMap.put("/login.html", "anon");
// <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->因为保存在LinkedHashMap中,顺序很重要
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");// 设置/** 为user后,记住我才会生效
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面,前后端分离设置此为controller返回的未登录的接口
// --------------------------------------------------
// 前后端分离使用下面设置
// shiroFilterFactoryBean.setLoginUrl("/login.html");
shiroFilterFactoryBean.setLoginUrl("/unauthorized");// 前后端分离只需要把需要登录返回告诉前端页面即可
// ---------------------------------------------------
// 登录成功后跳转的链接,前后端分离不用设置
// shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权的界面
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
- 页面新增user接口按钮
- 进行测试
- 未登录状态直接点击user按钮(无法访问)
- 未勾选记住我,登录成功后访问user按钮(可以访问)
- 关闭浏览器后再打开,点击访问user(无权限,并且sessionid变成全新的)
- 勾选记住我登录,可以看到user接口可以访问,同时cookie下出现了rememberMe的cookie,有效期一年,默认httponly
- 重新打开浏览器,直接点击访问user接口(可以访问,在这可以看到,rememberMe一直存在,sessionid变成全新的了)
- 这里需要注意的是,即使你上次登录的是guest用户,选择了记住我功能,但是仍然不能使用rememberMe功能实现guest接口的访问,这也是shiro故意控制的一点
3 、使用自定义rememberMe
- 在ShiroConfig类中添加cookie对象
/**
* Cookie 对象 用户免登陆操作,但是需要配置filter /** 权限为user生效
*
* @return
*/
public SimpleCookie rememMeCookie() {
// 初始化设置cookie的名称
SimpleCookie simpleCookie = new SimpleCookie("boot-shiro");
simpleCookie.setMaxAge(2592000);// 设置cookie的生效时间
simpleCookie.setHttpOnly(true);
return simpleCookie;
}
- 添加cookie管理器bean
// remeberMe cookie 加密的密钥 各个项目不一样 默认AES算法 密钥长度(128 256 512)
private static final String ENCRYPTION_KEY = "3AvVhmFLUs0KTA3Kprsdag==";
/**
* cookie 管理对象,记住我功能
*
* @return
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememMeCookie());
// remeberMe cookie 加密的密钥 各个项目不一样 默认AES算法 密钥长度(128 256 512)
cookieRememberMeManager.setCipherKey(Base64.decode(ENCRYPTION_KEY));
return cookieRememberMeManager;
}
- 把cookie管理器交给SecurityManager
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
securityManager.setRememberMeManager(rememberMeManager());//把cookie管理器交给SecurityManager
return securityManager;
}
4、总结
- shiro的基础篇到此就结束了,后续会更新shiro使用ehcache和redis的总结以及遇到的问题的篇幅
- 需要源码可以点击这里获取!