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/

posted @ 2017-04-08 23:08  彩虹过后的羽翼  阅读(3469)  评论(0编辑  收藏  举报