項目下載地址: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/