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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM