背景:
我们平常在登录网站的时候,经常会发现网页上有个 “下次自动登录” 或者 “记住我” 的功能,我们可以使用 shiro 来实现这个简单的功能。它主要是使用 cookie 来实现的。达到的最终效果就是第一次使用账号和密码登录(登录成功),此时退出浏览器,然后重新打开网页登录界面,就会自动登录到登录成功的网页上。
配置所需Bean:
在 shiroConfig 类中添加 SimpleCookie 、CookieRememberMeManager 和 FormAuthenticationFilter ,如下所示:
@Bean
public SimpleCookie rememberMeCookie(){
// 这个参数是 cookie 的名称,叫什么都行,我这块取名 rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
// setcookie 的 httponly 属性如果设为 true 的话,会增加对 xss 防护的安全系数,
// 只能通过http访问,javascript无法访问,防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
// 记住我 cookie 生效时间30天 ,单位是秒
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能,rememberMe管理器
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* FormAuthenticationFilter 过滤器 过滤记住我
* @return
*/
@Bean
public FormAuthenticationFilter formAuthenticationFilter(){
FormAuthenticationFilter formAuthenticationFilter = new FormAuthenticationFilter();
// 对应 rememberMeCookie() 方法中的 name
formAuthenticationFilter.setRememberMeParam("rememberMe");
return formAuthenticationFilter;
}
同样也是在 shiroConfig 中将 CookieRememberMeManager 注入到 SecurityManager 中,如下所示:
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 将 CookieRememberMeManager 注入到 SecurityManager 中,否则不会生效
securityManager.setRememberMeManager(rememberMeManager());
// 将 sessionManager 注入到 SecurityManager 中,否则不会生效
securityManager.setSessionManager(sessionManager());
return securityManager;
}
同样也是在 shiroConfig 中将 ShiroFilterFactoryBean 的认证属性改变,由 authc 改为 user ,如下所示:
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilter.setSecurityManager(securityManager);
//不输入地址的话会调用 /login 对应的地址
shiroFilter.setLoginUrl("/login");
Map<String, String> map = new LinkedHashMap<>();
map.put("/login", "anon");
map.put("/static/**", "anon");
// 对所有用户认证, authc 表示需要认证才能进行访问; user 表示配置记住我或认证通过可以访问的地址
map.put("/**", "user");//authc
shiroFilter.setFilterChainDefinitionMap(map);
return shiroFilter;
}
看下 LoginController 类是如何实现,需要注意的是 User 这个实体类必须要实现序列化接口,否则会报序列化异常,因为 Remember Me 会将用户信息加密然后以 cookie 保存。
@Controller
@Slf4j
public class LoginController {
@Autowired
UserService userService;
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
public String login(User user) {
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassword())) {
return "error";
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassword()
,user.isRememberMe());
//usernamePasswordToken.setRememberMe(true);
try {
// 用户账号和密码进行验证
subject.login(usernamePasswordToken);
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return "error";
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return "error";
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return "error";
}
return "shiro_index";
}
@GetMapping("/page_skip")
public String page_skip() {
return "page_skip";
}
@RequestMapping("/shiro_index")
public ModelAndView shiro_index() {
ModelAndView view = new ModelAndView();
view.setViewName("shiro_index");
return view;
}
}
看下前端页面 login.jsp 是如何实现,说白了就是携带参数,将请求发送到 Controller 进行拦截,如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="ISO-8859-1">
<title>一路发咨询网站</title>
</head>
<body>
<script type="text/javascript" src="/static/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="/static/js/login.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/login.css"/>
<h1>欢迎登录一路发咨询网站</h1>
<form action="/login">
<div id="father" style="background-image: url('/static/image/index.png');">
<div style="width:300px;height:100px;">
<div style="width:150px;float:left">
<span>用户名:<span></span>
</div>
<div style="width:150px;float:left;">
<input style="height:34px" type="text" name="userName"/>
</div>
</div>
<div style="width:300px;height:100px;">
<div style="width:150px;float:left;">
<span>密码:<span></span>
</div>
<div style="width:150px;float:left;">
<input style="height:34px" type="password" name="password"/>
</div>
</div>
<div style="width:300px;height:100px;">
<div style="width:64px;float:left;margin-left:280px">
<input style="height:34px;width:34px;" type="checkbox" name="rememberMe" />
</div>
<div style="width:150px;float:left;margin-top:-4px">
<span>记住我<span></span>
</div>
</div>
<div style="margin-left:190px">
<input style="height:50px;width:90px;font-size:34px;font-weight:bold" type="submit" value="提交"/>
</div>
</div>
</form>
</body>
</html>
测试:
此时,启动工程,输入网址 http://localhost:8080 ,第一次登录的时候,需要用户自己输入用户名和密码,选中记住的标签。登录成功之后,此时关掉浏览器,再打开浏览器,再次输入刚才的网址,即可发现这次登录不需要再输入用户名和密码,即可直接进入验证通过的界面。
cookie查看:
当我们第二次不输入账号和密码进入网页时,我们打开 F12 ,看下浏览器帮我们保存的 cookie 长什么样子,如下所示,其实就是将用户信息加密(包含密码)后放到前端,由于在项目中 user 对象信息过于庞大,不能全部存入 cookie ,且 cookie 对长度有一定的限制。