SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)

2023-10-26

项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821

(一)验证码

首先login.jsp里增加了获取验证码图片的标签:

<body style="margin-left: 500px">
     <h1 style="margin-left: 30px">登录页面----</h1>
     <form action="<%=basePath%>/login" method="post">
         用户名 : <input type="text" name="email" id="email"/><br>
         密码: <input type="password" name="pswd" id="pswd"/><br>
         验证码:<input type="text" name="gifCode" id="gifCode"/>
         <img alt="验证码" src="<%=basePath%>gif/getGifCode"><br>
         <input type="checkbox" name="rememberMe" />记住我<br>
         <input style="margin-left: 100px" type="submit" value="登录"/><input style="left: 50px" οnclick="register()" type="button" value="注册"/>
     </form>
     <h1 style="color: red">${message }</h1>
</body>
获取图片是请求后台,所以需要在shiro配置类中配置该url可以直接匿名访问:

/**
     * 加载ShiroFilter权限控制规则
     */
    private void loadShiroFilterChain(ShiroFilterFactoryBean factoryBean) {
        /**下面这些规则配置最好配置到配置文件中*/
        Map<String, String> filterChainMap = new LinkedHashMap<String, String>();
        //配置记住我或认证通过可以访问的地址
        filterChainMap.put("/index", "user");
        filterChainMap.put("/", "user");
        /** authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器
         * org.apache.shiro.web.filter.authc.FormAuthenticationFilter */
        //filterChainMap.put("/tUser", "authc");//输入http://localhost:8080/myEra/tUser会跳到登录页面
        //filterChainMap.put("/tUser/edit/**", "authc,perms[user:edit]");
        // anon:它对应的过滤器里面是空的,什么都没做,可以理解为不拦截
        //authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainMap.put("/permission/userInsert", "anon");
        filterChainMap.put("/error", "anon");
        filterChainMap.put("/tUser/insert","anon");
        filterChainMap.put("/gif/getGifCode","anon");
        filterChainMap.put("/**", "authc");

        factoryBean.setFilterChainDefinitionMap(filterChainMap);
}
中的filterChainMap.put("/gif/getGifCode","anon");

后台返回验证码前需把该验证码放入session中:

@Controller
@RequestMapping("gif")
public class GifCodeController {

    /**
     * 获取验证码(Gif版本)
     * @param response
     */
    @RequestMapping(value="/getGifCode",method= RequestMethod.GET)
    public void getGifCode(HttpServletResponse response, HttpServletRequest request){
        try {
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            response.setContentType("image/gif");
            /**
             * gif格式动画验证码
             * 宽,高,位数。
             */
            Captcha captcha = new GifCaptcha(146,33,4);
            //输出
            captcha.out(response.getOutputStream());
            HttpSession session = request.getSession(true);
            //存入Session
            session.setAttribute("gifCode",captcha.text().toLowerCase());
        } catch (Exception e) {
            System.err.println("获取验证码异常:"+e.getMessage());
        }
    }

}

这里生成验证码的类可以在我的项目里找到。

进入后台登录方法后,直接传入String类型的参数,需要把输入的验证码和session中保存的验证码对比:

@RequestMapping(value="/login",method=RequestMethod.POST)
    public String login(@Valid User user, boolean rememberMe,String gifCode,
                        BindingResult bindingResult, RedirectAttributes redirectAttributes){
        if(bindingResult.hasErrors()){
            return "redirect:login";
        }
        String email = user.getEmail();
        if(StringUtils.isBlank(user.getEmail()) || StringUtils.isBlank(user.getPswd())){
            logger.info("用户名或密码为空! ");
            redirectAttributes.addFlashAttribute("message", "用户名或密码为空!");
            return "redirect:login";
        }
        //判断验证码
        if(StringUtils.isBlank(gifCode)){
            logger.info("验证码为空了!");
            redirectAttributes.addFlashAttribute("message", "验证码不能为空!");
            return "redirect:login";
        }
        Session session = SecurityUtils.getSubject().getSession();
        String code = (String) session.getAttribute("gifCode");
        if(!gifCode.equalsIgnoreCase(code)){
            logger.info("验证码错误!");
            redirectAttributes.addFlashAttribute("message", "验证码错误!");
            return "redirect:login";
        }
        //对密码进行加密后验证
        UsernamePasswordToken token = new UsernamePasswordToken(user.getEmail(), CommonUtils.encrypt(user.getPswd()),rememberMe);
        //获取当前的Subject
        Subject currentUser = SecurityUtils.getSubject();
        try {
            //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
            //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
            //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
            logger.info("对用户[" + email + "]进行登录验证..验证开始");
            currentUser.login(token);
            logger.info("对用户[" + email + "]进行登录验证..验证通过");
        }catch(UnknownAccountException uae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,未知账户");
            redirectAttributes.addFlashAttribute("message", "未知账户");
        }catch(IncorrectCredentialsException ice){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误的凭证");
            redirectAttributes.addFlashAttribute("message", "密码不正确");
        }catch(LockedAccountException lae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,账户已锁定");
            redirectAttributes.addFlashAttribute("message", "账户已锁定");
        }catch(ExcessiveAttemptsException eae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误次数大于5次,账户已锁定");
            redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数大于5次,账户已锁定");
        }catch (DisabledAccountException sae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,帐号已经禁止登录");
            redirectAttributes.addFlashAttribute("message", "帐号已经禁止登录");
        }catch(AuthenticationException ae){
            //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,堆栈轨迹如下");
            ae.printStackTrace();
            redirectAttributes.addFlashAttribute("message", "用户名或密码不正确");
        }
        //验证是否登录成功
        if(currentUser.isAuthenticated()){
            logger.info("用户[" + email + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
            //把当前用户放入session
            User tUser = permissionService.findByUserEmail(email);
            session.setAttribute("currentUser",tUser);
            return "/welcome";
        }else{
            token.clear();
            return "redirect:login";
        }
        // 此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
        // 登陆失败还到login页面
       // return "login";
}
页面展示:

当用户验证码输错提示:



(二)登录次数限制
使用redis缓存存储登录的次数,当用户成功登录后,清空该次数:

@Autowired
    private StringRedisTemplate stringRedisTemplate;

    //用户登录次数计数  redisKey 前缀
    private String SHIRO_LOGIN_COUNT = "shiro_login_count_";
    //用户登录是否被锁定    一小时 redisKey 前缀
    private String SHIRO_IS_LOCK = "shiro_is_lock_";

    @RequestMapping(value="/login",method=RequestMethod.POST)
    public String login(@Valid User user, boolean rememberMe,String gifCode,
                        BindingResult bindingResult, RedirectAttributes redirectAttributes){
        if(bindingResult.hasErrors()){
            return "redirect:login";
        }
        String email = user.getEmail();
        if(StringUtils.isBlank(user.getEmail()) || StringUtils.isBlank(user.getPswd())){
            logger.info("用户名或密码为空! ");
            redirectAttributes.addFlashAttribute("message", "用户名或密码为空!");
            return "redirect:login";
        }
        //判断验证码
        if(StringUtils.isBlank(gifCode)){
            logger.info("验证码为空了!");
            redirectAttributes.addFlashAttribute("message", "验证码不能为空!");
            return "redirect:login";
        }
        Session session = SecurityUtils.getSubject().getSession();
        String code = (String) session.getAttribute("gifCode");
        if(!gifCode.equalsIgnoreCase(code)){
            logger.info("验证码错误!");
            redirectAttributes.addFlashAttribute("message", "验证码错误!");
            return "redirect:login";
        }
        logger.info("进行登录次数验证");
        //访问一次,计数一次
        ValueOperations<String, String> opsForValue = stringRedisTemplate.opsForValue();
        if ("LOCK".equals(opsForValue.get(SHIRO_IS_LOCK+email))){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误次数大于5次,账户已锁定");
            redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数大于5次,账户已锁定");
            return "redirect:login";
        }
        opsForValue.increment(SHIRO_LOGIN_COUNT+email, 1);
        //计数大于3时,设置用户被锁定一小时
        if(Integer.parseInt(opsForValue.get(SHIRO_LOGIN_COUNT+email))>=5){
            opsForValue.set(SHIRO_IS_LOCK+email, "LOCK");
            stringRedisTemplate.expire(SHIRO_IS_LOCK+email, 1, TimeUnit.HOURS);
        }
        //对密码进行加密后验证
        UsernamePasswordToken token = new UsernamePasswordToken(user.getEmail(), CommonUtils.encrypt(user.getPswd()),rememberMe);
        //获取当前的Subject
        Subject currentUser = SecurityUtils.getSubject();
        try {
            //在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
            //每个Realm都能在必要时对提交的AuthenticationTokens作出反应
            //所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
            logger.info("对用户[" + email + "]进行登录验证..验证开始");
            currentUser.login(token);
            logger.info("对用户[" + email + "]进行登录验证..验证通过");
        }catch(UnknownAccountException uae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,未知账户");
            redirectAttributes.addFlashAttribute("message", "未知账户");
        }catch(IncorrectCredentialsException ice){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误的凭证");
            redirectAttributes.addFlashAttribute("message", "密码不正确");
        }catch(LockedAccountException lae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,账户已锁定");
            redirectAttributes.addFlashAttribute("message", "账户已锁定");
        }catch(ExcessiveAttemptsException eae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,错误次数大于5次,账户已锁定");
            redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数大于5次,账户已锁定");
        }catch (DisabledAccountException sae){
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,帐号已经禁止登录");
            redirectAttributes.addFlashAttribute("message", "帐号已经禁止登录");
        }catch(AuthenticationException ae){
            //通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
            logger.info("对用户[" + email + "]进行登录验证..验证未通过,堆栈轨迹如下");
            ae.printStackTrace();
            redirectAttributes.addFlashAttribute("message", "用户名或密码不正确");
        }
        //验证是否登录成功
        if(currentUser.isAuthenticated()){
            logger.info("用户[" + email + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
            //清空登录计数
            opsForValue.set(SHIRO_LOGIN_COUNT+email, "0");
            //设置未锁定状态
            opsForValue.set(SHIRO_IS_LOCK+email,"UNLOCK");
            //把当前用户放入session
            User tUser = permissionService.findByUserEmail(email);
            session.setAttribute("currentUser",tUser);
            return "/welcome";
        }else{
            token.clear();
            return "redirect:login";
        }
        // 此方法不处理登陆成功(认证成功),shiro认证成功会自动跳转到上一个请求路径
        // 登陆失败还到login页面
       // return "login";
}

当用户名或密码输错5次后,提示:


验证码和登录次数功能参考博客:http://z77z.oschina.io/

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot学习:整合shiro(验证码功能和登录次数限制功能) 的相关文章

  • 将 html 注入 thymeleaf 模板

    我的数据库中有百里香模板 首先 我检索模板并处理它 String processedTemplate templateEngine process databaseTemplate context So now processedTempl
  • Shiro 与 SpringSecurity [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我目前正在评估基于Java的安全框架 我是Spring 3 0用户 因此SpringSecurity似乎是正确的选择 但Spring安全性似乎过
  • 我不应该在 Spring Boot 项目中使用“new”关键字吗?

    我正在研究 Spring Boot Rest API 最后我确实使用了new关键字在这里和那里 我想知道 当我在程序中使用 new 关键字时 我是否做错了什么 而如果在实际项目中绝对禁止使用new关键字 如果答案是肯定的 我应该注释我编写的
  • 从 Spring Boot 发送推送通知

    我有一个 springboot 应用程序 托管在我自己的家庭服务器上 我也有 sql 数据库设置 对于前端 我计划使用 android 进行初始测试阶段 然后将其转移到 flutter 我想知道如何将通知从 Spring Boot 发送到前
  • 通过 somain 注册器 1and1 smtp 使用 Spring 邮件发送邮件时出现问题

    我正在尝试使用 Spring Mail 1 5 8 RELEASE JavaMailSender 从 Spring Boot 应用程序发送电子邮件 通过 gmail SMTP 发送就可以了 但是 通过我的域名注册商的 amtp 注册商名称
  • spring boot:如何动态设置 spring 属性

    Spring Boot应用程序的application properties中可以定义很多属性 但我想传递属性以将 ssl 配置为从代码内部启动 server ssl enabled true The format used for the
  • Spring MVC 中拦截器和过滤器的区别

    我有点困惑Filter and Interceptor目的 据我从文档中了解到 Interceptor在请求之间运行 另一方面Filter在渲染视图之前运行 但在控制器渲染响应之后运行 那么两者的区别在哪里postHandle 在拦截器和d
  • Spring Boot CSRF

    尝试在最新的Spring Boot上实现CSRF保护 互联网上的所有示例都是基于用户登录和身份验证 我不需要 我的网站没有任何需要身份验证的部分 我想 1 休息请求来自站点内部 不允许来自外部的 wget 直接请求 2 所有页面 路由 必须
  • 将 spring boot 应用导入到另一个项目中

    因此 我尝试在另一个项目 测试框架 中添加一个 spring boot 可执行 jar 作为依赖项 但是一旦添加到 pom 并导入 Java 导入无法正常工作 如果我查看 jar 内部 所有包都带有以下前缀 BOOT INF classes
  • kafka Avro 多个主题的消息反序列化器

    我正在尝试以 avro 格式反序列化 kafka 消息 我使用以下代码 https github com ivangfr springboot kafka debezium ksql blob master kafka research c
  • 如何将多部分文件从另一个服务发送到一个服务

    我有两个端点 api 它们是 uploadand 重定向 upload是我直接上传文件的地方 重定向是我接收文件并将其传递给上传并获取 JSON 响应的地方 upload 所以下面是我的代码 package com example impo
  • 在 Spring 中设置 WS https 调用超时 (HttpsUrlConnectionMessageSender)

    我正在尝试为 WS 调用设置超时 我延长了WebServiceGatewaySupport并尝试将发送者超时设置为如下 public Object marshalSendAndReceive Object requestPayload We
  • Flyway无序迁移

    想象一下我有以下飞行路线迁移 V1 create table sql V2 create table sql V4 create table sql 这些迁移已经应用到我的数据库中 是否可以添加以下脚本 V3 create table sq
  • 在 Spring Boot application.properties 中指定信任存储信息

    我在用springBoot版本1 2 0 RELEASE 我正在尝试通过配置我的密钥库和信任库application properties 当我添加以下设置时 我可以使密钥库正常工作 但不能使信任库正常工作 server ssl key s
  • Spring Boot中使用自定义令牌进行身份验证

    我需要保护我的 Spring Boot 应用程序 这就是我所拥有的 一个 Spring Boot 应用程序 公开了一些 REST API 与公开的 api 通信的前端 前端发送用于身份验证的自定义身份验证令牌 存储自定义身份验证令牌的数据库
  • 为什么spring boot 1.5.3 jar无法识别src/main/resources/META-INF/resources/中的jsp文件

    我使用了spring boot jsp 我想构建一个可执行的jar 如下这个帖子 http www logicbig com tutorials spring framework spring boot boot serve dynamic
  • tomcat在Spring Boot中不创建访问日志

    我按照中的说明进行操作this https stackoverflow com a 35001421 18573回答和弹簧靴 https docs spring io spring boot docs current reference h
  • 如何在多模块 spring-boot maven 项目中构建特定模块

    我创建了一个多模块 Spring Boot Maven 项目 但是当我使用 mvn clean package pl module2 spring boot run 在控制台中 它告诉我 module1 中的某些类找不到 但我已经在 mod
  • 在 Kotlin 中创建 Spring 的 ParameterizedTypeReference 实例

    我正在尝试学习 Kotlin 并测试它如何与 Spring Boot 配合使用 我的应用程序使用 mongo 数据库来存储数据 并且我有用于检索数据的 Jersey 资源 我正在使用它进行测试spring boot test and Res
  • Spring Boot 中的 JSTL 支持

    虽然我知道有JSP 支持中的一些限制 http docs spring io spring boot docs current reference html boot features developing web applications

随机推荐

  • Qt应用开发(基础篇)——布局管理 Layout Management

    一 前言 在实际项目开发中 经常需要使用到布局 让控件自动排列 不仅节省控件还易于管控 Qt布局系统提供了一种简单而强大的方式来自动布局小部件中的子小部件 以确保它们充分利用可用空间 所有QWidget子类都可以使用setLayout 设置
  • JavaScript面向对象详解

    声明 本人的所有博客皆为个人笔记 作为个人知识索引使用 因此在叙述上存在逻辑不通顺 跨度大等问题 希望理解 分享出来仅供大家学习翻阅 若有错误希望指出 感谢 面向对象 JavaScript中没有类的概念 我们可以把对象看作散列表 无非就是一
  • html中的checkbox不显示值,html checkbox的checked属性问题和value属性问题

    lt html xmlns http www w3 org 1999 xhtml gt lt head gt lt script type text JavaScript gt function onbutton document getE
  • 前端Img使用图片跨域问题是怎么引起的?该如何解决呢?

    在项目种遇到一个问题 图片在dom节点这种使用了 img 标签来加载可以正常显示 但是运用到style中没有经过标签的加载就会出现跨域的问题 怎么会引起这个问题呢 1 通过 img 加载的图片 浏览器默认情况下会将其缓存起来 2 当我们从
  • 为什么数据分析进行的预测不够成功

    行业专家有时感叹大规模分析和数据科学计划经常缺乏价值 虽然人们从许多努力中看到了其巨大的价值 但结果非常差的例子也屡见不鲜 专家认为 通常这些问题可以归结为一个基本的错误 也就是说 认为生成预测 预测或模拟就足够了 但事实却不是这样 预测是
  • go 打开文件句柄_Go中trace包的使用

    Go给我们提供了一个工具trace 可以在运行时启用trace 并获得程序执行情况的详细视图 应该怎么使用trace呢 一般有下面三种使用方式 运行go test的时候 带上 trace参数标记 go test trace trace ou
  • Android6.0软件崩溃问题

    概述 targetSdkVersion是23 在Android 6 0上运行就会遇到crash的问题 因为Android 6 0在原有的AndroidManifest xml声明权限的基础上 增加了运行时的权限 无法在AndroidMani
  • ZBrush中Tool工具的保存

    ZBrush软件的界面及操作方法与其他的三维软件完全不同 很多初学者常常会觉得有些困难 接下来我们就讲解一下ZBrush 最为基础的操作 Tool工具的保存 首先要明白什么是Tool工具 我们创建的每一个模型 以及ZBrush可以调用的模型
  • Photoshop cc2019 破解教程

    Photoshop cc2019 破解教程 内含破解器 1 下载替换文件 Photoshop exe 链接如下 链接 https pan baidu com s 11XrnXWvGsnQ7YMbIMb49Lw 提取码 t9ol 2 打开Ph
  • Nginx 学习 一(安装)

    1 从官网现在nginx wget https nginx org download nginx 1 16 1 tar gz 解压 共有如下目录文件 2 让nginx 配置文件 vim 语法高亮 原先的 复制contrib 目录下文件到当前
  • windows Server 2008 R2服务器IIS环境启用TLS 1.2

    windows Server 2008 R2服务器IIS环境启用TLS 1 2 配置TLS1 2 分为2步 添加TLS配置和禁用老的SSL版本 提供两种方法 选择其中一种就行了 手动设置 打开注册表 运行regedit 找到 HKEY LO
  • kettle转换js实现MD5加密

    Script here js文件与转换文件保存路径一样LoadScriptFile getVariable Internal Transformation Filename Directory MD5 js var pass usernam
  • python【2】python3 的CSV数据规整化1

    最近分析一套数据 是csv格式的数据 必须是python分析数据比较顺手啊 于是研究一下csv模块 由于py的版本问题 3的资料中文的很少 所以记录一下 方便以后的学习 点击打开链接 上面的链接是python官网给的文档 英语好的建议看原版
  • 回归预测

    回归预测 MATLAB实现GRNN广义回归神经网络多输入多输出预测 目录 回归预测 MATLAB实现GRNN广义回归神经网络多输入多输出预测 预测效果 基本介绍 程序设计 往期精彩 参考资料 预测效果 基本介绍 MATLAB实现GRNN广义
  • 组合特征(三)tfidf(word+article+length)

    特征拼接 拼接文章长度 1 载入特征 2 读文章长度 3 特征缩放 拼接特征 import pickle 载入特征 with open tfidf word article pkl rb as f x train y train y tes
  • 【数字 IC】从底层重新认识 D 触发器、建立时间和保持时间

    目录 1 NMOS 和 PMOS 2 MOS 管搭建逻辑门 3 锁存器和触发器 3 1 交叉耦合反相器 3 2 SR 锁存器 3 3 D 锁存器 3 4 D 触发器 4 D 触发器的建立 保持时间 1 NMOS 和 PMOS MOSFET
  • 制作镜像

    2017 03 10 一直以来 都没有自己制作过景象 这次我得自己尝试下 https docs openstack org image guide virt install html 这里是官方的教程 2017 03 11 目前实验室制作镜
  • whistle代理配置帮助文档

    背景 Android开发需要与服务器调试API接口 whistle可以帮助配置代理的方式访问测试环境 并且联调抓包也非常方便 一 安装whistle 1 下载Node js 下载地址 https nodejs org en 下载完成后安装即
  • Servlet详解(二):request和response对象

    什么是request和response request对象是服务器对浏览器请求的封装 而response是服务器对服务器响应的封装 request用来取出请求信息 而response则用来添加要返回给浏览器的信息 使用response对象设
  • SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)

    项目下载地址 http download csdn NET detail aqsunkai 9805821 一 验证码 首先login jsp里增加了获取验证码图片的标签 h1 style margin left 30px 登录页面 h1