全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

SpringBoot+Shiro学习之密码加密和登录失败次数限制示例

这个项目写到现在,基本的雏形出来了,在此感谢一直关注的童鞋,送你们一句最近刚学习的一句鸡汤:念念不忘,必有回响。再贴一张ui图片:

前篇思考问题解决

前篇我们只是完成了同一账户的登录人数限制shiro拦截器的编写,对于手动踢出用户的功能只是说了采用在session域中添加一个key为kickout的布尔值,由之前编写的KickoutSessionControlFilter拦截器来判断是否将用户踢出,还没有说怎么获取当前在线用户的列表的核心代码,下面贴出来:

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author z77z
 * @since 2017-02-10
 */
@Service
public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> {
  @Autowired
  RedisSessionDAO redisSessionDAO;

  public Page<UserOnlineBo> getPagePlus(FrontPage<UserOnlineBo> frontPage) {
    // 因为我们是用redis实现了shiro的session的Dao,而且是采用了shiro+redis这个插件
    // 所以从spring容器中获取redisSessionDAO
    // 来获取session列表.
    Collection<Session> sessions = redisSessionDAO.getActiveSessions();
    Iterator<Session> it = sessions.iterator();
    List<UserOnlineBo> onlineUserList = new ArrayList<UserOnlineBo>();
    Page<UserOnlineBo> pageList = frontPage.getPagePlus();
    // 遍历session
    while (it.hasNext()) {
      // 这是shiro已经存入session的
      // 现在直接取就是了
      Session session = it.next();
      // 如果被标记为踢出就不显示
      Object obj = session.getAttribute("kickout");
      if (obj != null)
        continue;
      UserOnlineBo onlineUser = getSessionBo(session);
      onlineUserList.add(onlineUser);
    }
    // 再将List<UserOnlineBo>转换成mybatisPlus封装的page对象
    int page = frontPage.getPage() - 1;
    int rows = frontPage.getRows() - 1;
    int startIndex = page * rows;
    int endIndex = (page * rows) + rows;
    int size = onlineUserList.size();
    if (endIndex > size) {
      endIndex = size;
    }
    pageList.setRecords(onlineUserList.subList(startIndex, endIndex));
    pageList.setTotal(size);
    return pageList;
  }
  //从session中获取UserOnline对象
  private UserOnlineBo getSessionBo(Session session){
    //获取session登录信息。
    Object obj = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
    if(null == obj){
      return null;
    }
    //确保是 SimplePrincipalCollection对象。
    if(obj instanceof SimplePrincipalCollection){
      SimplePrincipalCollection spc = (SimplePrincipalCollection)obj;
      /**
       * 获取用户登录的,@link SampleRealm.doGetAuthenticationInfo(...)方法中
       * return new SimpleAuthenticationInfo(user,user.getPswd(), getName());的user 对象。
       */
      obj = spc.getPrimaryPrincipal();
      if(null != obj && obj instanceof SysUser){
        //存储session + user 综合信息
        UserOnlineBo userBo = new UserOnlineBo((SysUser)obj);
        //最后一次和系统交互的时间
        userBo.setLastAccess(session.getLastAccessTime());
        //主机的ip地址
        userBo.setHost(session.getHost());
        //session ID
        userBo.setSessionId(session.getId().toString());
        //session最后一次与系统交互的时间
        userBo.setLastLoginTime(session.getLastAccessTime());
        //回话到期 ttl(ms)
        userBo.setTimeout(session.getTimeout());
        //session创建时间
        userBo.setStartTime(session.getStartTimestamp());
        //是否踢出
        userBo.setSessionStatus(false);
        return userBo;
      }
    }
    return null;
  }
}

代码中注释比较完善,也可以去下载源码查看,这样结合看,跟容易理解,不懂的在评论区留言,看见必回!

对Ajax请求的优化:这里有一个前提,我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出了。也就是说在KickoutSessionControlFilter拦截器拦截后,正常如果被踢出,就会跳转到被踢出的提示页面,如果是Ajax请求,给用户的感觉就是没有感觉,核心解决代码如下:

Map<String, String> resultMap = new HashMap<String, String>();
//判断是不是Ajax请求
if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
  resultMap.put("user_status", "300");
  resultMap.put("message", "您已经在其他地方登录,请重新登录!");
  //输出json串
  out(response, resultMap);
}else{
  //重定向
  WebUtils.issueRedirect(request, response, kickoutUrl);
}

private void out(ServletResponse hresponse, Map<String, String> resultMap)
  throws IOException {
  try {
    hresponse.setCharacterEncoding("UTF-8");
    PrintWriter out = hresponse.getWriter();
    out.println(JSON.toJSONString(resultMap));
    out.flush();
    out.close();
  } catch (Exception e) {
    System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。");
  }
}

这是在KickoutSessionControlFilter这个拦截器里面做的修改。

目标:

  1. 现在项目里面的密码整个流程都是以明文的方式传递的。这样在实际应用中是很不安全的,京东,开源中国等这些大公司都有泄库事件,这样对用户的隐私造成巨大的影响,所以将密码加密存储传输就非常必要了。
  2. 密码重试次数限制,也是出于安全性的考虑。

实现目标一:

shiro本身是有对密码加密进行实现的,提供了PasswordService及CredentialsMatcher用于提供加密密码及验证密码服务。

我就是自己实现的EDS加密,并且保存的加密明文是采用password+username的方式,减小了密码相同,密文也相同的问题,这里我只是贴一下,EDS的加密解密代码,另外我还改了MyShiroRealm文件,再查数据库的时候加密后再查,而且在创建用户的时候不要忘记的加密存到数据库。这里就补贴代码了。

/**
 * DES加密解密
 * 
 * @author z77z
 * @datetime 2017-3-13
 */
public class MyDES {
  /**
   * DES算法密钥
   */
  private static final byte[] DES_KEY = { 21, 1, -110, 82, -32, -85, -128, -65 };

  /**
   * 数据加密,算法(DES)
   * 
   * @param data
   *      要进行加密的数据
   * @return 加密后的数据
   */
  @SuppressWarnings("restriction")
  public static String encryptBasedDes(String data) {
    String encryptedData = null;
    try {
      // DES算法要求有一个可信任的随机数源
      SecureRandom sr = new SecureRandom();
      DESKeySpec deskey = new DESKeySpec(DES_KEY);
      // 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象
      SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
      SecretKey key = keyFactory.generateSecret(deskey);
      // 加密对象
      Cipher cipher = Cipher.getInstance("DES");
      cipher.init(Cipher.ENCRYPT_MODE, key, sr);
      // 加密,并把字节数组编码成字符串
      encryptedData = new sun.misc.BASE64Encoder().encode(cipher.doFinal(data.getBytes()));
    } catch (Exception e) {
      // log.error("加密错误,错误信息:", e);
      throw new RuntimeException("加密错误,错误信息:", e);
    }
    return encryptedData;
  }

  /**
   * 数据解密,算法(DES)
   * 
   * @param cryptData
   *      加密数据
   * @return 解密后的数据
   */
  @SuppressWarnings("restriction")
  public static String decryptBasedDes(String cryptData) {
    String decryptedData = null;
    try {
      // DES算法要求有一个可信任的随机数源
      SecureRandom sr = new SecureRandom();
      DESKeySpec deskey = new DESKeySpec(DES_KEY);
      // 创建一个密匙工厂,然后用它把DESKeySpec转换成一个SecretKey对象
      SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
      SecretKey key = keyFactory.generateSecret(deskey);
      // 解密对象
      Cipher cipher = Cipher.getInstance("DES");
      cipher.init(Cipher.DECRYPT_MODE, key, sr);
      // 把字符串解码为字节数组,并解密
      decryptedData = new String(cipher.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(cryptData)));
    } catch (Exception e) {
      // log.error("解密错误,错误信息:", e);
      throw new RuntimeException("解密错误,错误信息:", e);
    }
    return decryptedData;
  }

  public static void main(String[] args) {
    String str = "123456";
    // DES数据加密
    String s1 = encryptBasedDes(str);
    System.out.println(s1);
    // DES数据解密
    String s2 = decryptBasedDes(s1);
    System.err.println(s2);
  }
}

实现目标二

如在1个小时内密码最多重试5次,如果尝试次数超过5次就锁定1小时,1小时后可再次重试,如果还是重试失败,可以锁定如1天,以此类推,防止密码被暴力破解。我们使用redis数据库来保存当前用户登录次数,也就是执行身份认证方法:

MyShiroRealm.doGetAuthenticationInfo()的次数,如果登录成功就清空计数。超过就返回相应错误信息。(redis的具体操作可以去看我之前的springboot+redis的一篇博客)根据这个逻辑,修改MyShiroRealm.java如下:

/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
 * 
 * @param token
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
    AuthenticationToken authcToken) throws AuthenticationException {


  System.out.println("身份认证方法:MyShiroRealm.doGetAuthenticationInfo()");

  UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
  String name = token.getUsername();
  String password = String.valueOf(token.getPassword());
  //访问一次,计数一次
  ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
  opsForValue.increment(SHIRO_LOGIN_COUNT+name, 1);
  //计数大于5时,设置用户被锁定一小时
  if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+name))>=5){
    opsForValue.set(SHIRO_IS_LOCK+name, "LOCK");
    stringRedisTemplate.expire(SHIRO_IS_LOCK+name, 1, TimeUnit.HOURS);
  }
  if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK+name))){
    throw new DisabledAccountException("由于密码输入错误次数大于5次,帐号已经禁止登录!");
  }
  Map<String, Object> map = new HashMap<String, Object>();
  map.put("nickname", name);
  //密码进行加密处理 明文为 password+name
  String paw = password+name;
  String pawDES = MyDES.encryptBasedDes(paw);
  map.put("pswd", pawDES);
  SysUser user = null;
  // 从数据库获取对应用户名密码的用户
  List<SysUser> userList = sysUserService.selectByMap(map);
  if(userList.size()!=0){
    user = userList.get(0);
  } 
  if (null == user) {
    throw new AccountException("帐号或密码不正确!");
  }else if(user.getStatus()==0){
    /**
     * 如果用户的status为禁用。那么就抛出<code>DisabledAccountException</code>
     */
    throw new DisabledAccountException("此帐号已经设置为禁止登录!");
  }else{
    //登录成功
    //更新登录时间 last login time
    user.setLastLoginTime(new Date());
    sysUserService.updateById(user);
    //清空登录计数
    opsForValue.set(SHIRO_LOGIN_COUNT+name, "0");
  }
  return new SimpleAuthenticationInfo(user, password, getName());
}

demo下载地址:springboot_mybatisplus_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# SpringBoot  # 登录限制  # spring  # boot  # 密码加密  # boot整合shiro  # springboot整合shiro登录失败次数限制功能的实现代码  # SpringBoot实现账号登录错误次数的限制和锁定功能  # 踢出  # 错误信息  # 帐号  # 重试  # 这是  # 转换成  # 拦截器  # 一句  # 有一个  # 用它  # 用户登录  # 创建一个  # 清空  # 身份认证  # 加密解密  # 数源  # 就会  # 都有  # 还没有  # 我还 


相关文章: 武清网站制作公司,天津武清个人营业执照注销查询系统网站?  建站主机选购指南:核心配置与性价比推荐解析  成都网站制作报价公司,成都工业用气开户费用?  建站之星代理平台如何选择最佳方案?  网站制作企业,网站的banner和导航栏是指什么?  如何在阿里云服务器自主搭建网站?  如何通过二级域名建站提升品牌影响力?  c++怎么编写动态链接库dll_c++ __declspec(dllexport)导出与调用【方法】  如何通过cPanel快速搭建网站?  宝塔建站后网页无法访问如何解决?  武汉网站设计制作公司,武汉有哪些比较大的同城网站或论坛,就是里面都是武汉人的?  阿里云网站制作公司,阿里云快速搭建网站好用吗?  国美网站制作流程,国美电器蒸汽鍋怎么用官方网站?  如何选择长沙网站建站模板?H5响应式与品牌定制哪个更优?  韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐  电商网站制作价格怎么算,网上拍卖流程以及规则?  南平网站制作公司,2025年南平市事业单位报名时间?  如何通过商城自助建站源码实现零基础高效建站?  javascript中的try catch异常捕获机制用法分析  网站制作和推广的区别,想自己建立一个网站做推广,有什么快捷方法马上做好一个网站?  建站之星手机一键生成:多端自适应+小程序开发快速建站指南  公司网站制作价格怎么算,公司办个官网需要多少钱?  相册网站制作软件,图片上的网址怎么复制?  ,在苏州找工作,上哪个网站比较好?  如何快速重置建站主机并恢复默认配置?  高性能网站服务器配置指南:安全稳定与高效建站核心方案  非常酷的网站设计制作软件,酷培ai教育官方网站?  ,石家庄四十八中学官网?  北京建设网站制作公司,北京古代建筑博物馆预约官网?  攀枝花网站建设,攀枝花营业执照网上怎么年审?  网站制作价目表怎么做,珍爱网婚介费用多少?  高端建站如何打造兼具美学与转化的品牌官网?  如何在企业微信快速生成手机电脑官网?  如何获取PHP WAP自助建站系统源码?  美食网站链接制作教程视频,哪个教做美食的网站比较专业点?  Android使用GridView实现日历的简单功能  魔方云NAT建站如何实现端口转发?  如何在阿里云域名上完成建站全流程?  北京营销型网站制作公司,可以用python做一个营销推广网站吗?  视频网站app制作软件,有什么好的视频聊天网站或者软件?  如何选择美橙互联多站合一建站方案?  宝塔Windows建站如何避免显示默认IIS页面?  建站168自助建站系统:快速模板定制与SEO优化指南  建站主机选哪种环境更利于SEO优化?  寿县云建站:智能SEO优化与多行业模板快速上线指南  网站插件制作软件免费下载,网页视频怎么下到本地插件?  如何在Golang中使用replace替换模块_指定本地或远程路径  成都网站制作公司哪家好,四川省职工服务网是做什么用?  婚礼视频制作网站,学习*后期制作的网站有哪些?  如何彻底卸载建站之星软件? 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。