?
目錄貼: ?跟我學(xué)Shiro目錄貼
?
在做用戶登錄功能時(shí),很多時(shí)候都需要驗(yàn)證碼支持,驗(yàn)證碼的目的是為了防止機(jī)器人模擬真實(shí)用戶登錄而惡意訪問(wèn),如暴力破解用戶密碼/惡意評(píng)論等。目前也有一些驗(yàn)證碼比較簡(jiǎn)單,通過(guò)一些OCR工具就可以解析出來(lái);另外還有一些驗(yàn)證碼比較復(fù)雜(一般通過(guò)如扭曲、加線條/噪點(diǎn)等干擾)防止OCR工具識(shí)別;但是在中國(guó)就是人多,機(jī)器干不了的可以交給人來(lái)完成,所以在中國(guó)就有很多打碼平臺(tái),人工識(shí)別驗(yàn)證碼;因此即使比較復(fù)雜的如填字、算數(shù)等類型的驗(yàn)證碼還是能識(shí)別的。所以驗(yàn)證碼也不是絕對(duì)可靠的,目前比較可靠還是手機(jī)驗(yàn)證碼,但是對(duì)于用戶來(lái)說(shuō)相對(duì)于驗(yàn)證碼還是比較麻煩的。
?
對(duì)于驗(yàn)證碼圖片的生成,可以自己通過(guò)如Java提供的圖像API自己去生成,也可以借助如JCaptcha這種開源Java類庫(kù)生成驗(yàn)證碼圖片;JCaptcha提供了常見的如扭曲、加噪點(diǎn)等干擾支持。本章代碼基于《第十六章?綜合實(shí)例》。
?
一、添加 JCaptcha 依賴 ?
<dependency> <groupId>com.octo.captcha</groupId> <artifactId>jcaptcha</artifactId> <version>2.0-alpha-1</version> </dependency> <dependency> <groupId>com.octo.captcha</groupId> <artifactId>jcaptcha-integration-simple-servlet</artifactId> <version>2.0-alpha-1</version> <exclusions> <exclusion> <artifactId>servlet-api</artifactId> <groupId>javax.servlet</groupId> </exclusion> </exclusions> </dependency>?
com.octo.captcha . jcaptcha?提供了jcaptcha?核心;而jcaptcha-integration-simple-servlet提供了與Servlet集成。
?
二、 GMailEngine
來(lái)自 https://code.google.com/p/musicvalley/source/browse/trunk/musicvalley/doc/springSecurity/springSecurityIII/src/main/java/com/spring/security/jcaptcha/GMailEngine.java?spec=svn447&r=447 (目前無(wú)法訪問(wèn)了),仿照J(rèn)Captcha2.0編寫類似GMail驗(yàn)證碼的樣式;具體請(qǐng)參考com.github.zhangkaitao.shiro.chapter22.jcaptcha.GMailEngine。
?
三、 MyManageableImageCaptchaService
提供了判斷倉(cāng)庫(kù)中是否有相應(yīng)的驗(yàn)證碼存在。?
public class MyManageableImageCaptchaService extends DefaultManageableImageCaptchaService { public MyManageableImageCaptchaService( com.octo.captcha.service.captchastore.CaptchaStore captchaStore, com.octo.captcha.engine.CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds, int maxCaptchaStoreSize, int captchaStoreLoadBeforeGarbageCollection) { super(captchaStore, captchaEngine, minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize, captchaStoreLoadBeforeGarbageCollection); } public boolean hasCapcha(String id, String userCaptchaResponse) { return store.getCaptcha(id).validateResponse(userCaptchaResponse); } }
??
?
四、 JCaptcha 工具類
提供相應(yīng)的API來(lái)驗(yàn)證當(dāng)前請(qǐng)求輸入的驗(yàn)證碼是否正確。??
public class JCaptcha { public static final MyManageableImageCaptchaService captchaService = new MyManageableImageCaptchaService(new FastHashMapCaptchaStore(), new GMailEngine(), 180, 100000, 75000); public static boolean validateResponse( HttpServletRequest request, String userCaptchaResponse) { if (request.getSession(false) == null) return false; boolean validated = false; try { String id = request.getSession().getId(); validated = captchaService.validateResponseForID(id, userCaptchaResponse) .booleanValue(); } catch (CaptchaServiceException e) { e.printStackTrace(); } return validated; } public static boolean hasCaptcha( HttpServletRequest request, String userCaptchaResponse) { if (request.getSession(false) == null) return false; boolean validated = false; try { String id = request.getSession().getId(); validated = captchaService.hasCapcha(id, userCaptchaResponse); } catch (CaptchaServiceException e) { e.printStackTrace(); } return validated; } }?
validateResponse():驗(yàn)證當(dāng)前請(qǐng)求輸入的驗(yàn)證碼否正確;并從CaptchaService中刪除已經(jīng)生成的驗(yàn)證碼;
hasCaptcha():驗(yàn)證當(dāng)前請(qǐng)求輸入的驗(yàn)證碼是否正確;但不從CaptchaService中刪除已經(jīng)生成的驗(yàn)證碼(比如Ajax驗(yàn)證時(shí)可以使用,防止多次生成驗(yàn)證碼);
?
五、 JCaptchaFilter
用于生成驗(yàn)證碼圖片的過(guò)濾器。??
public class JCaptchaFilter extends OncePerRequestFilter { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { response.setDateHeader("Expires", 0L); response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); response.setContentType("image/jpeg"); String id = request.getRequestedSessionId(); BufferedImage bi = JCaptcha.captchaService.getImageChallengeForID(id); ServletOutputStream out = response.getOutputStream(); ImageIO.write(bi, "jpg", out); try { out.flush(); } finally { out.close(); } } }?
CaptchaService使用當(dāng)前會(huì)話ID當(dāng)作key獲取相應(yīng)的驗(yàn)證碼圖片;另外需要設(shè)置響應(yīng)內(nèi)容不進(jìn)行瀏覽器端緩存。?
?
<!-- 驗(yàn)證碼過(guò)濾器需要放到Shiro之后 因?yàn)镾hiro將包裝HttpSession 如果不,可能造成兩次的sesison id 不一樣 --> <filter> <filter-name>JCaptchaFilter</filter-name> <filter-class> com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaFilter </filter-class> </filter> <filter-mapping> <filter-name>JCaptchaFilter</filter-name> <url-pattern>/jcaptcha.jpg</url-pattern> </filter-mapping>?
這樣就可以在頁(yè)面使用/jcaptcha.jpg地址顯示驗(yàn)證碼圖片。
?
六、 JCaptchaValidateFilter
用于驗(yàn)證碼驗(yàn)證的Shiro過(guò)濾器。??
public class JCaptchaValidateFilter extends AccessControlFilter { private boolean jcaptchaEbabled = true;//是否開啟驗(yàn)證碼支持 private String jcaptchaParam = "jcaptchaCode";//前臺(tái)提交的驗(yàn)證碼參數(shù)名 private String failureKeyAttribute = "shiroLoginFailure"; //驗(yàn)證失敗后存儲(chǔ)到的屬性名 public void setJcaptchaEbabled(boolean jcaptchaEbabled) { this.jcaptchaEbabled = jcaptchaEbabled; } public void setJcaptchaParam(String jcaptchaParam) { this.jcaptchaParam = jcaptchaParam; } public void setFailureKeyAttribute(String failureKeyAttribute) { this.failureKeyAttribute = failureKeyAttribute; } protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { //1、設(shè)置驗(yàn)證碼是否開啟屬性,頁(yè)面可以根據(jù)該屬性來(lái)決定是否顯示驗(yàn)證碼 request.setAttribute("jcaptchaEbabled", jcaptchaEbabled); HttpServletRequest httpServletRequest = WebUtils.toHttp(request); //2、判斷驗(yàn)證碼是否禁用 或不是表單提交(允許訪問(wèn)) if (jcaptchaEbabled == false || !"post".equalsIgnoreCase(httpServletRequest.getMethod())) { return true; } //3、此時(shí)是表單提交,驗(yàn)證驗(yàn)證碼是否正確 return JCaptcha.validateResponse(httpServletRequest, httpServletRequest.getParameter(jcaptchaParam)); } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //如果驗(yàn)證碼失敗了,存儲(chǔ)失敗key屬性 request.setAttribute(failureKeyAttribute, "jCaptcha.error"); return true; } }
?
七、 MyFormAuthenticationFilter
用于驗(yàn)證碼驗(yàn)證的Shiro攔截器在用于身份認(rèn)證的攔截器之前運(yùn)行;但是如果驗(yàn)證碼驗(yàn)證攔截器失敗了,就不需要進(jìn)行身份認(rèn)證攔截器流程了;所以需要修改下如FormAuthenticationFilter身份認(rèn)證攔截器,當(dāng)驗(yàn)證碼驗(yàn)證失敗時(shí)不再走身份認(rèn)證攔截器。?
public class MyFormAuthenticationFilter extends FormAuthenticationFilter { protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { if(request.getAttribute(getFailureKeyAttribute()) != null) { return true; } return super.onAccessDenied(request, response, mappedValue); } }?
即如果之前已經(jīng)錯(cuò)了,那直接跳過(guò)即可。
?
八、 spring-config-shiro.xml ? ? ???
<!-- 基于Form表單的身份驗(yàn)證過(guò)濾器 --> <bean id="authcFilter" class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.MyFormAuthenticationFilter"> <property name="usernameParam" value="username"/> <property name="passwordParam" value="password"/> <property name="rememberMeParam" value="rememberMe"/> <property name="failureKeyAttribute" value="shiroLoginFailure"/> </bean> <bean id="jCaptchaValidateFilter" class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaValidateFilter"> <property name="jcaptchaEbabled" value="true"/> <property name="jcaptchaParam" value="jcaptchaCode"/> <property name="failureKeyAttribute" value="shiroLoginFailure"/> </bean> <!-- Shiro的Web過(guò)濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="filters"> <util:map> <entry key="authc" value-ref="authcFilter"/> <entry key="sysUser" value-ref="sysUserFilter"/> <entry key="jCaptchaValidate" value-ref="jCaptchaValidateFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /static/** = anon /jcaptcha* = anon /login = jCaptchaValidate,authc /logout = logout /authenticated = authc /** = user,sysUser </value> </property> </bean>
?
九、 login.jsp 登錄頁(yè)面
<c:if test="${jcaptchaEbabled}"> 驗(yàn)證碼: <input type="text" name="jcaptchaCode"> <img class="jcaptcha-btn jcaptcha-img" src="${pageContext.request.contextPath}/jcaptcha.jpg" title="點(diǎn)擊更換驗(yàn)證碼"> <a class="jcaptcha-btn" href="javascript:;">換一張</a> <br/> </c:if>?
根據(jù)jcaptchaEbabled來(lái)顯示驗(yàn)證碼圖片。
?
十、測(cè)試
輸入 http://localhost:8080/chapter22 將重定向到登錄頁(yè)面;輸入正確的用戶名/密碼/驗(yàn)證碼即可成功登錄,如果輸入錯(cuò)誤的驗(yàn)證碼,將顯示驗(yàn)證碼錯(cuò)誤頁(yè)面:?
?
?
示例源代碼: https://github.com/zhangkaitao/shiro-example ;可加群 231889722?探討Spring/Shiro技術(shù)。
? ? ? ??
??
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
