在做用戶登錄功能時(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這種開(kāi)源Java類庫(kù)生成驗(yàn)證碼圖片;JCaptcha提供了常見(jiàn)的如扭曲、加噪點(diǎn)等干擾支持。本章代碼基于《第十六章 綜合實(shí)例》。
一、添加JCaptcha依賴
Java代碼
<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)證碼存在。
Java代碼
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)證碼是否正確。
Java代碼
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ò)濾器。
Java代碼
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)行瀏覽器端緩存。
Java代碼
<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ò)濾器。
Java代碼
public class JCaptchaValidateFilter extends AccessControlFilter { private boolean jcaptchaEbabled =
true;
private String jcaptchaParam =
"jcaptchaCode";
private String failureKeyAttribute =
"shiroLoginFailure";
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 { request.setAttribute(
"jcaptchaEbabled", jcaptchaEbabled); HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
if (jcaptchaEbabled ==
false || !
"post".equalsIgnoreCase(httpServletRequest.getMethod())) {
return true; }
return JCaptcha.validateResponse(httpServletRequest, httpServletRequest.getParameter(jcaptchaParam)); }
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws Exception { 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)證攔截器。
Java代碼
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
Java代碼
<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>
<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è)面
Java代碼
<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
本文借鑒于:http://jinnianshilongnian.iteye.com/blog/2046041
總結(jié)
以上是生活随笔為你收集整理的Shiro学习(22)集成验证码的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。