结合shiro 的图形验证码生成

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。

在做用户登录功能时,很多时候都需要验证码支持,验证码的目的是为了防止机器人模拟真实用户登录而恶意访问,如暴力破解用户密码/恶意评论等。目前也有一些验证码比较简单,通过一些OCR工具就可以解析出来;另外还有一些验证码比较复杂(一般通过如扭曲、加线条/噪点等干扰)防止OCR工具识别;但是在中国就是人多,机器干不了的可以交给人来完成,所以在中国就有很多打码平台,人工识别验证码;因此即使比较复杂的如填字、算数等类型的验证码还是能识别的。所以验证码也不是绝对可靠的,目前比较可靠还是手机验证码,但是对于用户来说相对于验证码还是比较麻烦的。

 

对于验证码图片的生成,可以自己通过如Java提供的图像API自己去生成,也可以借助如JCaptcha这种开源Java类库生成验证码图片;JCaptcha提供了常见的如扭曲、加噪点等干扰支持。

 

一、添加JCaptcha依赖 

Java代码  收藏代码
  1. <dependency>  
  2.     <groupId>com.octo.captcha</groupId>  
  3.     <artifactId>jcaptcha</artifactId>  
  4.     <version>2.0-alpha-1</version>  
  5. </dependency>  
  6. <dependency>  
  7.     <groupId>com.octo.captcha</groupId>  
  8.     <artifactId>jcaptcha-integration-simple-servlet</artifactId>  
  9.     <version>2.0-alpha-1</version>  
  10.     <exclusions>  
  11.         <exclusion>  
  12.             <artifactId>servlet-api</artifactId>  
  13.             <groupId>javax.servlet</groupId>  
  14.         </exclusion>  
  15.     </exclusions>  
  16. </dependency>   

com.octo.captcha . jcaptcha 提供了jcaptcha 核心;而jcaptcha-integration-simple-servlet提供了与Servlet集成。

 

二、GMailEngine

来自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(目前无法访问了),仿照JCaptcha2.0编写类似GMail验证码的样式;具体请参考com.github.zhangkaitao.shiro.chapter22.jcaptcha.GMailEngine。

 

三、MyManageableImageCaptchaService

提供了判断仓库中是否有相应的验证码存在。 

Java代码  收藏代码
  1. public class MyManageableImageCaptchaService extends   
  2.   DefaultManageableImageCaptchaService {   
  3.     public MyManageableImageCaptchaService(  
  4.       com.octo.captcha.service.captchastore.CaptchaStore captchaStore,        
  5.       com.octo.captcha.engine.CaptchaEngine captchaEngine,  
  6.       int minGuarantedStorageDelayInSeconds,   
  7.       int maxCaptchaStoreSize,   
  8.       int captchaStoreLoadBeforeGarbageCollection) {  
  9.         super(captchaStore, captchaEngine, minGuarantedStorageDelayInSeconds,   
  10.             maxCaptchaStoreSize, captchaStoreLoadBeforeGarbageCollection);  
  11.     }  
  12.     public boolean hasCapcha(String id, String userCaptchaResponse) {  
  13.         return store.getCaptcha(id).validateResponse(userCaptchaResponse);  
  14.     }  
  15. }  

  

 

四、JCaptcha工具类

提供相应的API来验证当前请求输入的验证码是否正确。  

Java代码  收藏代码
  1. public class JCaptcha {  
  2.     public static final MyManageableImageCaptchaService captchaService  
  3.             = new MyManageableImageCaptchaService(new FastHashMapCaptchaStore(),   
  4.                             new GMailEngine(), 18010000075000);  
  5.     public static boolean validateResponse(  
  6.         HttpServletRequest request, String userCaptchaResponse) {  
  7.         if (request.getSession(false) == nullreturn false;  
  8.         boolean validated = false;  
  9.         try {  
  10.             String id = request.getSession().getId();  
  11.             validated =   
  12.                 captchaService.validateResponseForID(id, userCaptchaResponse)  
  13.                             .booleanValue();  
  14.         } catch (CaptchaServiceException e) {  
  15.             e.printStackTrace();  
  16.         }  
  17.         return validated;  
  18.     }   
  19.     public static boolean hasCaptcha(  
  20.         HttpServletRequest request, String userCaptchaResponse) {  
  21.         if (request.getSession(false) == nullreturn false;  
  22.         boolean validated = false;  
  23.         try {  
  24.             String id = request.getSession().getId();  
  25.             validated = captchaService.hasCapcha(id, userCaptchaResponse);  
  26.         } catch (CaptchaServiceException e) {  
  27.             e.printStackTrace();  
  28.         }  
  29.         return validated;  
  30.     }  
  31. }   

validateResponse():验证当前请求输入的验证码否正确;并从CaptchaService中删除已经生成的验证码;

hasCaptcha():验证当前请求输入的验证码是否正确;但不从CaptchaService中删除已经生成的验证码(比如Ajax验证时可以使用,防止多次生成验证码);

 

五、JCaptchaFilter

用于生成验证码图片的过滤器。  

Java代码  收藏代码
  1. public class JCaptchaFilter extends OncePerRequestFilter {  
  2.     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {  
  3.   
  4.         response.setDateHeader("Expires", 0L);  
  5.         response.setHeader("Cache-Control""no-store, no-cache, must-revalidate");  
  6.         response.addHeader("Cache-Control""post-check=0, pre-check=0");  
  7.         response.setHeader("Pragma""no-cache");  
  8.         response.setContentType("image/jpeg");  
  9.         String id = request.getRequestedSessionId();  
  10.         BufferedImage bi = JCaptcha.captchaService.getImageChallengeForID(id);  
  11.         ServletOutputStream out = response.getOutputStream();  
  12.         ImageIO.write(bi, "jpg", out);  
  13.         try {  
  14.             out.flush();  
  15.         } finally {  
  16.             out.close();  
  17.         }  
  18.     }  
  19. }   

CaptchaService使用当前会话ID当作key获取相应的验证码图片;另外需要设置响应内容不进行浏览器端缓存。 

 

Java代码  收藏代码
  1. <!-- 验证码过滤器需要放到Shiro之后 因为Shiro将包装HttpSession 如果不,可能造成两次的sesison id 不一样 -->  
  2. <filter>  
  3.   <filter-name>JCaptchaFilter</filter-name>  
  4.   <filter-class>   
  5.     com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaFilter  
  6.   </filter-class>  
  7.   </filter>  
  8.   <filter-mapping>  
  9.     <filter-name>JCaptchaFilter</filter-name>  
  10.     <url-pattern>/jcaptcha.jpg</url-pattern>  
  11. </filter-mapping>   

这样就可以在页面使用/jcaptcha.jpg地址显示验证码图片。

 

六、JCaptchaValidateFilter

用于验证码验证的Shiro过滤器。  

Java代码  收藏代码
  1. public class JCaptchaValidateFilter extends AccessControlFilter {  
  2.     private boolean jcaptchaEbabled = true;//是否开启验证码支持  
  3.     private String jcaptchaParam = "jcaptchaCode";//前台提交的验证码参数名  
  4.     private String failureKeyAttribute = "shiroLoginFailure"//验证失败后存储到的属性名  
  5.     public void setJcaptchaEbabled(boolean jcaptchaEbabled) {  
  6.         this.jcaptchaEbabled = jcaptchaEbabled;  
  7.     }  
  8.     public void setJcaptchaParam(String jcaptchaParam) {  
  9.         this.jcaptchaParam = jcaptchaParam;  
  10.     }  
  11.     public void setFailureKeyAttribute(String failureKeyAttribute) {  
  12.         this.failureKeyAttribute = failureKeyAttribute;  
  13.     }  
  14.     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  15.         //1、设置验证码是否开启属性,页面可以根据该属性来决定是否显示验证码  
  16.         request.setAttribute("jcaptchaEbabled", jcaptchaEbabled);  
  17.   
  18.         HttpServletRequest httpServletRequest = WebUtils.toHttp(request);  
  19.         //2、判断验证码是否禁用 或不是表单提交(允许访问)  
  20.         if (jcaptchaEbabled == false || !"post".equalsIgnoreCase(httpServletRequest.getMethod())) {  
  21.             return true;  
  22.         }  
  23.         //3、此时是表单提交,验证验证码是否正确  
  24.         return JCaptcha.validateResponse(httpServletRequest, httpServletRequest.getParameter(jcaptchaParam));  
  25.     }  
  26.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
  27.         //如果验证码失败了,存储失败key属性  
  28.         request.setAttribute(failureKeyAttribute, "jCaptcha.error");  
  29.         return true;  
  30.     }  
  31. }  

 

七、MyFormAuthenticationFilter

用于验证码验证的Shiro拦截器在用于身份认证的拦截器之前运行;但是如果验证码验证拦截器失败了,就不需要进行身份认证拦截器流程了;所以需要修改下如FormAuthenticationFilter身份认证拦截器,当验证码验证失败时不再走身份认证拦截器。 

Java代码  收藏代码
  1. public class MyFormAuthenticationFilter extends FormAuthenticationFilter {  
  2.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  3.         if(request.getAttribute(getFailureKeyAttribute()) != null) {  
  4.             return true;  
  5.         }  
  6.         return super.onAccessDenied(request, response, mappedValue);  
  7.     }  
  8. }   

即如果之前已经错了,那直接跳过即可。

 

八、spring-config-shiro.xml       

Java代码  收藏代码
  1. <!-- 基于Form表单的身份验证过滤器 -->  
  2. <bean id="authcFilter"   
  3.   class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.MyFormAuthenticationFilter">  
  4.     <property name="usernameParam" value="username"/>  
  5.     <property name="passwordParam" value="password"/>  
  6.     <property name="rememberMeParam" value="rememberMe"/>  
  7.     <property name="failureKeyAttribute" value="shiroLoginFailure"/>  
  8. </bean>  
  9. <bean id="jCaptchaValidateFilter"   
  10.   class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaValidateFilter">  
  11.     <property name="jcaptchaEbabled" value="true"/>  
  12.     <property name="jcaptchaParam" value="jcaptchaCode"/>  
  13.     <property name="failureKeyAttribute" value="shiroLoginFailure"/>  
  14. </bean>  
  15. <!-- Shiro的Web过滤器 -->  
  16. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  17.     <property name="securityManager" ref="securityManager"/>  
  18.     <property name="loginUrl" value="/login"/>  
  19.     <property name="filters">  
  20.         <util:map>  
  21.             <entry key="authc" value-ref="authcFilter"/>  
  22.             <entry key="sysUser" value-ref="sysUserFilter"/>  
  23.             <entry key="jCaptchaValidate" value-ref="jCaptchaValidateFilter"/>  
  24.         </util:map>  
  25.     </property>  
  26.     <property name="filterChainDefinitions">  
  27.         <value>  
  28.             /static/** = anon  
  29.             /jcaptcha* = anon  
  30.             /login = jCaptchaValidate,authc  
  31.             /logout = logout  
  32.             /authenticated = authc  
  33.             /** = user,sysUser  
  34.         </value>  
  35.     </property>  
  36. </bean>  

 

九、login.jsp登录页面

Java代码  收藏代码
  1. <c:if test="${jcaptchaEbabled}">  
  2.     验证码:  
  3.     <input type="text" name="jcaptchaCode">  
  4. <img class="jcaptcha-btn jcaptcha-img"   
  5. src="${pageContext.request.contextPath}/jcaptcha.jpg" title="点击更换验证码">  
  6.     <a class="jcaptcha-btn" href="javascript:;">换一张</a>  
  7.     <br/>  
  8. </c:if>   

根据jcaptchaEbabled来显示验证码图片。

 

十、测试

输入http://localhost:8080/chapter22将重定向到登录页面;输入正确的用户名/密码/验证码即可成功登录,如果输入错误的验证码,将显示验证码错误页面: 


  

 

示例源代码:https://github.com/zhangkaitao/shiro-example;

 

 

 

十一、另附讲解:

先说明错误原因:
用<a href="http://lib.csdn.net/base/javaee" class="replace_word" title="Java EE知识库" target="_blank" >spring</a>安全拦截器进行验证码的验证的时候抛出异常。
throw new RuntimeException("captcha validation failed due to exception", cse);前台提交数据后跳转到如下方法:

 

 

  1. package com.davidstudio.gbp.core.security.jcaptcha;  
  2.   
  3. import org.acegisecurity.captcha.CaptchaServiceProxy;  
  4.   
  5. import org.apache.log4j.Logger;  
  6.   
  7. import com.octo.captcha.service.CaptchaService;  
  8. import com.octo.captcha.service.CaptchaServiceException;  
  9.   
  10. /** 
  11.  * 调用CaptchaService类,完jcaptcha的验证过程 
  12.  *  
  13.  * 
  14.  *  
  15.  * 
  16.  */  
  17. public class JCaptchaServiceProxyImpl implements CaptchaServiceProxy {  
  18.   
  19.     /** 
  20.      * Logger for this class 
  21.      */  
  22.     private static final Logger logger = Logger.getLogger(JCaptchaServiceProxyImpl.class);  
  23.   
  24.     private CaptchaService jcaptchaService;  
  25.   
  26.     public boolean validateReponseForId(String id, Object response) {  
  27.         if (logger.isDebugEnabled()) {  
  28.             logger.debug("validating captcha response");  
  29.         }  
  30.    
  31.         try {  
  32.             boolean isHuman = false;  
  33.               
  34.             isHuman = jcaptchaService.validateResponseForID(id, response).booleanValue();  
  35.               
  36.             if (isHuman) {  
  37.                 if (logger.isDebugEnabled()) {  
  38.                     logger.debug("captcha passed");  
  39.                 }  
  40.             } else {  
  41.                 if (logger.isDebugEnabled()) {  
  42.                     logger.debug("captcha failed");  
  43.                 }  
  44.             }  
  45.             return isHuman;  
  46.               
  47.         } catch (CaptchaServiceException cse) {  
  48.             // fixes known bug in JCaptcha  
  49.             logger.warn("captcha validation failed due to exception", cse);  
  50.             throw new RuntimeException("captcha validation failed due to exception", cse);  
  51.         }  
  52.     }  
  53.   
  54.     public void setJcaptchaService(CaptchaService jcaptchaService) {  
  55.         this.jcaptchaService = jcaptchaService;  
  56.     }  
  57. }  

 

 

 

 

 

设置断点debug改语句不能顺利执行 

 

  1. jcaptchaService.validateResponseForID(id, response).booleanValue();  

 

 

查了网上的资料,这个方法的作用是: 根据HttpSession的 sessionId进行验证码的验证,原理是这样的,页面生成的验证码是通过Spring中的配置生成的,查了一下配置:

 

  1. <bean id="security.filter.manager" class="org.acegisecurity.util.FilterChainProxy">  
  2.         <property name="filterInvocationDefinitionSource">  
  3.             <value>  
  4.                 PATTERN_TYPE_APACHE_ANT  
  5.                 /**=security.filter.channel,security.filter.sessionIntegration,security.filter.logout,security.filter.thsso,security.filter.jcaptcha,security.filter.jcaptchachannel,security.filter.formAuth,security.filter.requestWrap,security.filter.exceptionTranslation,security.filter.filterInvocation  
  6.             </value>  
  7.         </property>  
  8.     </bean> 

 

 

 

 

这是一个过滤器链,其中登录的时候会进行如下过滤操作,

 

security.filter.channel,security.filter.sessionIntegration,security.filter.logout,security.filter.thsso,security.filter.jcaptcha,security.filter.jcaptchachannel,security.filter.formAuth,security.filter.requestWrap,security.filter.exceptionTranslation,security.filter.filterInvocation

一般配置的顺序不能变,因为这是这些配置定义了用户登录的一套认证机制。

看了一下命名还算规范,其中涉及到验证码的过滤:security.filter.jcaptcha

查了一下这个验证码的引用配置:

 

  1. <!-- jcaptacha过滤器 -->  
  2.     <bean id="security.filter.jcaptcha"  
  3.         class="org.acegisecurity.captcha.CaptchaValidationProcessingFilter">  
  4.         <property name="captchaService" ref="security.captcha.serviceproxy" />  
  5.         <property name="captchaValidationParameter" value="j_captcha_response" />  
  6.     </bean>  
  7.     <bean id="security.captcha.serviceproxy"  
  8.         class="com.davidstudio.gbp.core.security.jcaptcha.JCaptchaServiceProxyImpl">  
  9.         <property name="jcaptchaService" ref="security.captcha.service" />  
  10.     </bean>  
  11.     <bean id="security.captcha.service"  
  12.         class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService">  
  13.         <constructor-arg type="com.octo.captcha.service.captchastore.CaptchaStore" index="0">  
  14.             <bean class="com.octo.captcha.service.captchastore.FastHashMapCaptchaStore" />  
  15.         </constructor-arg>  
  16.         <constructor-arg type="com.octo.captcha.engine.CaptchaEngine" index="1">  
  17.             <bean class="com.davidstudio.gbp.core.security.jcaptcha.CaptchaEngine" />  
  18.         </constructor-arg>  
  19.         <constructor-arg index="2">  
  20.             <value>180</value>  
  21.         </constructor-arg>  
  22.         <constructor-arg index="3">  
  23.             <value>100000</value>  
  24.         </constructor-arg>  
  25.         <constructor-arg index="4">  
  26.             <value>75000</value>  
  27.         </constructor-arg>  
  28.     </bean>  

 

 

 

通过bean配置反复引用。

 

刚开始以为SecurityContext没有创建,查了一下配置也创建了:

 

 

  1. <!--  session整合过滤器。自动将用户身份信息存放在session里。 -->  
  2. <bean id="security.filter.sessionIntegration"  
  3.     class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">  
  4.     <property name="context" value="org.acegisecurity.captcha.CaptchaSecurityContextImpl" />  
  5. </bean>  

 

 

 

 copy

  仔细看了一下这个方法的作用:

 

  1. jcaptchaService.validateResponseForID(id, response).booleanValue();  

 

 

 

id就是httpSession的Id,response是从页面获得的输入的验证码,当调用这个方法的时候,根据httpSession的id找到相应的验证码,如果有sessionId并且sessionId对应的验证码和输入的验证码(这里就是response)一致的时候返回true,也就是用户通过了验证。

 

有一个疑问,验证码是怎么生成的?又怎么和httpSession进行绑定的?其实这套理论是可行的,当用户第一次访问页面的时候会生成一个sessionId,页面生成有验证码,关于验证码的生成,下面会进行介绍。就是画一个图片以留的方式显示到页面而已。用户访问的时候有一个对应的验证码和sessionId相对应。

如果验证码不清楚,点击换一张,因为浏览器没有关闭,sessionId依然是那个sessionId,只需要更新生成的验证码的值即可,这样就做到了一个sessionId和一个验证码进行绑定了,这个过程在生成验证码的过程中就发生了。

如果用户再次提交登录信息,其中的sessionId没有变,验证码是最新生成的验证码并且和sessionId进行了绑定,这样就可以调用:

 

 

  1. jcaptchaService.validateResponseForID(id, response).booleanValue(); 这个条件进行验证码的验证了,当然了验证码验证前面还可以有很多过滤器认证,比如说对用户名和密码的验证等等。形成一套的链式认证!  

 

 

 

      然而还有一个疑惑,这个sessionId是怎么和验证码进行绑定的呢?又是怎样进行存储的呢?

 我们看一下内存:

调用这段代码的时候内存中有sessionId和response验证码的值:

下面是验证码生成的线程中内存的状态:

由内存的状态可以看出和配置文件是一致的,首先调用了com.davidstudio.gbp.core.security.jcaptcha.JCaptchaServiceProxyImpl

这个代理实现,这个代理实现类 又去调用com.octo.captcha.service.image.DefaultManageableImageCaptchaService

这个类才是生成验证码的类:查下spring这个类的源码如下:

 

传入的参数都有相应的说明,其中这个类继承了AbstractManageableImageCaptchaService  

 

继续深入到这个类中看个究竟:

 

这个类中果然有我们想要的方法:

 

  相应的通过store.getCaptcha(ID)通过这个ID获得和这个sessionId匹配的验证码,再调用vilidateResponse方法进行验证,如果和输入的验证码相同就验证通过了。

 

验证通过后就把这个sessionId删除了,如果你再次登录,输入验证码的时候是同一个逻辑,之所以删除了这个ID我想是有好处的:

           原因如下,如果不进行删除,随着的登录访问用户的过多,hashMap中的值会越来越多,这样以后再进行验证的时候速度和效率都会受到印象,如果删除了这个sessionId,这样这个store中的hashMap只是存储了当前正在准备登录的sessionId和相应的验证码!这样效率就大大提高了,如果有10万个人同时登录,都不是问题!

       通过这个方法的调用我们就知道了sessionId是怎么和验证码绑定存储在hashMap中的!让我们进入源码验证一下:

 

 

上面就是CaptchaStore接口的实现类MapCaptchaStore,其中定义了一个hashMap,通过storeCaptcha(String id,Captcha captcha)方法来存储sessionId和captcha的键值对,这是进入登录页面生成的时候调用的方法,当进行验证的时候就需要hasCaptcha(String ID)方法和

 

但是我们是调用了

[java] view plain copy
  1. MapCaptchaStore 的子类FastHashMapCaptchaStore来存储信息的:同样看FastHashMapCaptchaStore这个类:
  2.  
  3.    17   public class FastHashMapCaptchaStore extends MapCaptchaStore {  
  4.    18       public FastHashMapCaptchaStore() {  
  5.    19           this.store = new FastHashMap();  
  6.    20       }  
  7.    21   }
  8.  
  9. 这就是这个类的全部了,再看一下FastHashMap类:  
  10.  
  11. public class FastHashMap extends HashMap {  
  12.    67     
  13.    68       /** 
  14.    69        * The underlying map we are managing. 
  15.    70        */  
  16.    71       protected HashMap map = null;  
  17.    72     
  18.    73       /** 
  19.    74        * Are we currently operating in "fast" mode? 
  20.    75        */  
  21.    76       protected boolean fast = false;  
  22.    77     
  23.    78       // Constructors  
  24.    79       // ----------------------------------------------------------------------  
  25.    80     
  26.    81       /** 
  27.    82        * Construct an empty map. 
  28.    83        */  
  29.    84       public FastHashMap() {  
  30.    85           super();  
  31.    86           this.map = new HashMap();  
  32.    87       }  
  33.    
  34. 这个类是HashMap的一个扩展,里面有两种方式操作,一种是快速的不同步,一种是同步的操作!  
  35.  
  36. 显然FastHashMapCaptchaStore就是一个HashMap。验证码的实现在这个类中:
  37.  
  38.    18    * Base implementation of the ImageCaptchaService.  
  39.    19    *  
  40.    20    * @author <a href="mailto:mag@jcaptcha.net">Marc-Antoine Garrigue</a>  
  41.    21    * @version 1.0  
  42.    22    */  
  43.    23   public abstract class AbstractManageableImageCaptchaService extends AbstractManageableCaptchaService  
  44.    24           implements ImageCaptchaService {  
  45.    25     
  46.    26       protected AbstractManageableImageCaptchaService(CaptchaStore captchaStore,  
  47.    27                                                       com.octo.captcha.engine.CaptchaEngine captchaEngine,  
  48.    28                                                       int minGuarantedStorageDelayInSeconds,  
  49.    29                                                       int maxCaptchaStoreSize,  
  50.    30                                                       int captchaStoreLoadBeforeGarbageCollection) {  
  51.    31           super(captchaStore, captchaEngine,  
  52.    32                   minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize,  
  53.    33                   captchaStoreLoadBeforeGarbageCollection);  
  54.    34       }
  55.  
  56.  
  57.    73       protected Object getChallengeClone(Captcha captcha) {  
  58.    74           BufferedImage challenge = (BufferedImage) captcha.getChallenge();  
  59.    75           BufferedImage clone = new BufferedImage(challenge.getWidth(), challenge.getHeight(), challenge.getType());  
  60.    76     
  61.    77           clone.getGraphics().drawImage(challenge, 00, clone.getWidth(), clone.getHeight(), null);  
  62.    78           clone.getGraphics().dispose();  
  63.    79     
  64.    80     
  65.    81           return clone;  
  66.    82       }
  67.  
  68. 在这个类中,只是定义了一种,Captcha也是一种接口。  
  69. 可以到内存中看一看有木有那个hashMap
  70. <span style="white-space:pre"><img src="https://img-my.csdn.net/uploads/201211/23/1353676134_4969.png" alt="">    </span>
  71.  
  72. 内存中清楚显示了hashTable中的key和value,这样就证明验证码生成成功。但是为什么每次验证都是报错呢?
  73. 后来无奈看了看发送到 sessionId在hashMap中是否有,结果是不一样,就是再hashMap中没有,为什么?不是每一次在验证码生成的时候都把sessionId放进去了吗?为什么会不一样呢?
  74.  
  75. 原因其实很简单,就是当点击登陆的时候服务器又给分配了一个sessionId,这样就和以前的sessionId不一样了,在hashMap中就找不到对应的验证码了。
  76. 原则上讲服务器在第一次访问的时候会给用户分配一个不重复的sessionId,如果服务器的session不超时就不会再给用户分配sessionId了,减少给服务器的压力,也带来了友好的体验。但是我的两次sessiId为什么不一样呢?
  77.  
  78.  后来通过fiddler2这个软件(这个软件好强大可以获得发送到form表单的内容,甚至可以修改),可以看到本地存储的cookie,但是cookie是空的,就是nodata,汗啊,难怪每次都分配不同的sessionId,服务器怎么判断每次提交过去的是同一个用户呢?
  79.  
  80. 通过sessionId,服务器会在客户端把sessionId写在Cookie中,这样用户再次提交请求的时候,服务器如果在内存中找到用户cookie中的sessionId而且没有超时,就不再重新分配sessionId,我看了下IE浏览器,cookie被禁止了,难怪每次都是一个新的sessionId,验证码就无法验证。就报错了。
  81. 学习中应该多去看源码,分析源码设计理念。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/452109.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

E24- please install the following Perl modules before executing ./mysql_install_db

2019独角兽企业重金招聘Python工程师标准>>> [roott-cet7 scripts]# ./mysql_install_db --basedir/usr/local/mysql/ --datadir/app/data/ --usermysql FATAL ERROR: please install the following Perl modules before executing ./mysql_install_db: Data::Dumpe…

SpringMVC异常报406 (Not Acceptable)的解决办法

使用SpsringMVC&#xff0c;使用restEasy调试&#xff0c;controller请求设置如下&#xff1a; Java代码 RequestMapping(value"/list",methodRequestMethod.GET,producesMediaType.APPLICATION_JSON_VALUE) ResponseBody public List<EditTimeout> list()…

Diango博客--25.使用Coverage统计测试覆盖率

文章目录1. 前言2. 安装 Coverage3. 简单配置 Coverage4. 运行 Coverage5. 完善 Coverage 配置6. 生成 HTML 报告7. 完善单元测试1. 前言 我们完成了对 blog 应用和 comment 应用这两个核心 app 的测试。现在我们想知道的是究竟测试效果怎么样呢&#xff1f;测试充分吗&#x…

Android动画之逐帧动画(FrameAnimation)详解

今天我们就来学习逐帧动画,废话少说直接上效果图如下: 帧动画的实现方式有两种&#xff1a; 一、在res/drawable文件夹下新建animation-list的XML实现帧动画 1、首先在res/drawable文件夹下添加img00-img24共25张图片 2、新建frame_anim.xml [html] view plaincopy <?xml v…

网络爬虫--1.通用爬虫和聚焦爬虫

文章目录一.前言二.通用爬虫1.工作原理2.通用爬虫的局限性三.聚焦爬虫一.前言 根据使用场景&#xff0c;网络爬虫可分为 通用爬虫 和 聚焦爬虫 两种。 其中通用网络爬虫是捜索引擎抓取系统&#xff08;Baidu、Google、Yahoo等&#xff09;的重要组成部分。主要目的是将互联网…

敏捷教练的工具箱

学习并不是简简单单的阅读和浏览&#xff0c;而是一个积累的过程&#xff0c;一个通过持续的学习&#xff0c;对自己的知识体系不断丰富、索引的过程。接下来我会从四个方面入手分享我的经验。 高质量的信息源和高效的学习 Google是一个很好的工具&#xff0c;通过它&#x…

python 发送邮件的两种方式【终极篇】

python 发送邮件的两种方式【终极篇】 一&#xff0c;利用python自带的库 smtplib简单高效 from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.header import Header import smtplib from django.conf import settingsmail_hos…

网络爬虫--2.HTTP和HTTPS

文章目录一.简介二.HTTP的请求与响应三.客户端HTTP请求1.格式2.请求方法四.常用的请求报头1.Host (主机和端口号)2.Connection (链接类型)3.Upgrade-Insecure-Requests (升级为HTTPS请求)4. User-Agent (浏览器名称)5. Accept (传输文件类型)6.Referer (页面跳转处)7.Accept-En…

IBM王阳:软件是凝聚创新力的最佳平台

导读&#xff1a;在IBM全球副总裁兼IBM中国开发中心总经理王阳博士看来&#xff0c;IBM百年不衰的根本原因在于将创新力凝结成软件然后进行合适的传播&#xff0c;其间最重要的是成功打造出了一个吸引人才、培养研发人才并激发出人才创新力的环境和氛围。而保持创新领导力的关键…

Jquery 多行拖拽图片排序 jq优化

<!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>jQuery图片拖动排序代码</title><style type"text/css">.item_container{position:relative;height:auto;overflow:hidden;} .item_content ul{li…

分享11款主流的开源编程工具

导读&#xff1a;有了开源编程工具&#xff0c;在基于开源许可证的情况下您可以轻松学习、修改、提高代码的质量&#xff0c;本文收集了11款最主流的且有价值的开源编程工具。或许会给您带来一丝惊喜。一起来看下吧。 NO.1 Rhomobile Rhodes Ruby或许是Github上第二大流行语言…

谁在告谁?移动专利混战图

移动领域激战正酣&#xff0c;同样是没有永远的朋友&#xff0c;只有永远的利益。 苹果刚刚起诉三星的Galaxy手机和平板电脑山寨了苹果的产品&#xff0c;而此前两家并没有过节。再比如微软和亚马逊以及HTC之间的授权协议争端。移动领域的争端如此之多&#xff0c;以至于看客无…

光棍节程序员闯关秀过关全攻略

maven/java/web/bootstrapQQ群&#xff1a;566862629。希望更多人一起帮助我学习。 光棍节程序员闯关秀过关全攻略。程序员的寂寞谁能懂?"SF光棍节程序员闯关秀"智力挑战小游戏火热上线&#xff0c;看看你能闯到第几关&#xff1f; 游戏地址: http://segmentfault…

jekins搭建

2019独角兽企业重金招聘Python工程师标准>>> 转自 https://www.cnblogs.com/hdwang/p/6081994.html &#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xff1d;&#xf…

块级元素的margin-left和margin-right的用法注意

此时是有效果显示的因为html文档流默认是从上往下&#xff0c;从左往右进行显示的&#xff0c;所以此时是有效果的。那如果此时把#son的块元素的margin-right:20px; 是没有效果的此时是没有效果的&#xff0c;如图所示&#xff1a;如果此时想要margin-right有效果的话&#xf…

Apache Tiles的基本使用

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1、概述 对于一个新的技术&#xff0c;了解其基本的概念和和原理是学好该技术的基础。 2、Tiles的概念 Tiles 是复合视图模式&#xff0…

网络爬虫--6.urllib库的基本使用(2)

文章目录一. urllib.parse.urlencode()和urllib.parse.unquote()二. Get方式三. 批量爬取百度贴吧数据四.POST方式五.关于CA六.处理HTTPS请求 SSL证书验证一. urllib.parse.urlencode()和urllib.parse.unquote() 编码工作使用urllib.parse的urlencode()函数&#xff0c;帮我们…

摩拜大数据杀熟?官方:老用户押金的确退款延迟

近日&#xff0c;有媒体曝出摩拜单车一些老用户出现押金难退现象。有的消费者点击退款后&#xff0c;系统不断奔溃&#xff1b;有的申请退款后&#xff0c;账户又莫名出现押金&#xff0c;就像未申请一样&#xff1b;也有人终于提交了退款&#xff0c;等候数日却迟迟不见到账。…

Junit Test使用样例

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 配置&#xff1a; 调用类&#xff1a; import java.util.List;import javax.annotation.Resource;import org.apache.shiro.crypto.Rand…

day212223:线程、进程、协程

1、程序工作原理 进程的限制&#xff1a;每一个时刻只能有一个线程来工作。多进程的优点&#xff1a;同时利用多个cpu&#xff0c;能够同时进行多个操作。缺点&#xff1a;对内存消耗比较高当进程数多于cpu数量的时候会导致不能被调用&#xff0c;进程不是越多越好&#xff0c;…