zoukankan      html  css  js  c++  java
  • SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)

    项目下载地址: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" onclick="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/

  • 相关阅读:
    效率较高的排序算法
    django进阶
    django报错TypeError: __init__() missing 1 required positional argument: 'on_delete'
    DjangoORM基本增删改查
    C++中关键字static的作用
    Sqlite的安装和使用 (windows,C#)
    sqlserver查询时对于字符串类型的数据是否区分大小写
    C#自己无聊写的2048小游戏
    C#自己无聊写的俄罗斯方块游戏
    C#实现非枚举类型的在属性控件中可下拉选择(二)
  • 原文地址:https://www.cnblogs.com/aqsunkai/p/6690569.html
Copyright © 2011-2022 走看看