一、使用场景
在登录界面,我们需要放置记住登录的选择框,当用户勾选“记住登录”后,在用户下次打开浏览器进入系统时,能够无感登录,即不需要进入到登录界面输入用户信息
二、功能开发
- 1、当用户勾选“记住登录”后,我们在登录接口需要接受是否记住登录这个参数
@ApiOperation(value = "登录验证") @ApiImplicitParams({ @ApiImplicitParam(name = "account", value = "账号", required = true), @ApiImplicitParam(name = "password", value = "密码", required = true), @ApiImplicitParam(name = "resCode", value = "验证码", required = true), @ApiImplicitParam(name = "rememberMe", value = "记住登录", required = true) }) @PostMapping("doLogin") @ResponseBody public R doLogin(@RequestBody LoginVO loginVO, HttpServletRequest request) { // 验证码 if (!KaptchaUtil.validate(loginVO.getResCode(), request)) { return R.no(StatusEnums.KAPTCH_ERROR); } try { //获取当前的用户 Subject subject = SecurityUtils.getSubject(); // 验证帐号和密码,记住登录 UsernamePasswordToken token = new UsernamePasswordToken(loginVO.getAccount(), loginVO.getPassword(), loginVO.getRememberMe()); // 执行登录 subject.login(token); // 将用户保存到session中 request.getSession().setAttribute(SystemConst.SYSTEM_USER_SESSION, ShiroUtils.getSysUserInfo()); // 保存登录日志 sysLoginLogService.save(loginVO.getAccount(), 0, "用户登录成功"); return R.ok("登录成功,欢迎回来!"); } catch (UnknownAccountException e) { sysLoginLogService.save(loginVO.getAccount(), 1, "登录账户不存在"); return R.no("账户不存在"); } catch (DisabledAccountException e) { sysLoginLogService.save(loginVO.getAccount(), 1, "登录账户已被冻结"); return R.no("账户已被冻结"); } catch (IncorrectCredentialsException e) { sysLoginLogService.save(loginVO.getAccount(), 1, "登录密码不正确"); return R.no("密码不正确"); } catch (ExcessiveAttemptsException e) { sysLoginLogService.save(loginVO.getAccount(), 1, "密码连续输入错误超过5次,锁定半小时"); return R.no("密码连续输入错误超过5次,锁定半小时"); } catch (RuntimeException e) { sysLoginLogService.save(loginVO.getAccount(), 1, "未知错误"); return R.no("未知错误"); } }
这样我们在 UsernamePasswordToken 对象里多传入了一个 rememberMe 的参数,true 表示是,false 表示否
- 2、注册 rememberMe 的cookie管理器
/** * 记住登录Cookie信息 * * @return */ @Bean public SimpleCookie rememberMeCookie() { SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); // 防止xss攻击,窃取cookie内容 simpleCookie.setHttpOnly(true); // 30天有效期 simpleCookie.setMaxAge(30 * 24 * 60 * 60); return simpleCookie; }
因我我们记住登录的信息是放置在 cookie 对象中,所以我们设置了记住登录的有效期为30天,30天过后,会失效,要求用户重新登录
- 3、注册 rememberMe 管理对象
/** * 记住登录管理对象 * * @return */ @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); cookieRememberMeManager.setCipherKey(Base64.decode("4BxVhuFKUs0KTA3Kprsdag==")); return cookieRememberMeManager; }
我们设置了管理器的 cookie 对象,和对信息加密的 key
- 4、注入安全管理器
/** * 安全管理器 */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置自定义的relam securityManager.setRealm(loginRelam()); // 设置记住登录管理器 securityManager.setRememberMeManager(rememberMeManager()); return securityManager; }
5、修改地址过滤器
/** * 地址过滤器 * * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 设置登录url shiroFilterFactoryBean.setLoginUrl("/login"); // 设置主页url shiroFilterFactoryBean.setSuccessUrl("/"); // 设置未授权的url shiroFilterFactoryBean.setUnauthorizedUrl("/403"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 注销登录 filterChainDefinitionMap.put("/loginOut", "logout"); // 开放登录接口 filterChainDefinitionMap.put("/doLogin", "anon"); // 开放获取登录验证码接口 filterChainDefinitionMap.put("/kaptcha/**", "anon"); // 开放Api接口 filterChainDefinitionMap.put("/api/**", "anon"); // 开放微信接口 filterChainDefinitionMap.put("/weixin/**", "anon"); // 开放websocket接口 filterChainDefinitionMap.put("/websocket/**", "anon"); // 开放接口文档 filterChainDefinitionMap.put("/doc.html", "anon"); filterChainDefinitionMap.put("/service-worker.js", "anon"); filterChainDefinitionMap.put("/swagger-resources/**", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/v2/**", "anon"); // 开放静态资源 filterChainDefinitionMap.put("/css/**", "anon"); filterChainDefinitionMap.put("/img/**", "anon"); filterChainDefinitionMap.put("/js/**", "anon"); filterChainDefinitionMap.put("/layui/**", "anon"); filterChainDefinitionMap.put("/layuimini/**", "anon"); filterChainDefinitionMap.put("/module/**", "anon"); filterChainDefinitionMap.put("/upload/**", "anon"); // 其余url全部拦截,必须放在最后 filterChainDefinitionMap.put("/**", "user"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
除了我们设置的 url 不拦截外,其余的 url 都会拦截,拦截规则为 user,拦截规则详解如下:
(1)anon:匿名过滤器,表示通过了url配置的资源都可以访问,例:“/statics/**=anon”表示statics目录下所有资源都能访问
(2)authc:基于表单的过滤器,表示通过了url配置的资源需要登录验证,否则跳转到登录,例:“/unauthor.jsp=authc”如果用户没有登录访问unauthor.jsp则直接跳转到登录
(3)authcBasic:Basic的身份验证过滤器,表示通过了url配置的资源会提示身份验证,例:“/welcom.jsp=authcBasic”访问welcom.jsp时会弹出身份验证框
(4)user:用户过滤器,表示可以使用登录验证/记住我的方式访问通过了url配置的资源,例:“/welcom.jsp=user”表示访问welcom.jsp页面可以通过登录验证或使用记住我后访问,否则直接跳转到登录
(5)logout:退出拦截器,表示执行logout方法后,跳转到通过了url配置的资源,例:“/logout.jsp=logout”表示执行了logout方法后直接跳转到logout.jsp页面