cas添加验证码以及默认错误几次以后才出现验证码

趁着周末加班,抽点时间完善下关于cas服务端的改造,其实过了好久,有些东西不看也想不起来了,当然自己做过的东西熟悉起来那也是相当的快的,废话不多说


进入正题


获取验证码,其实就是后端返回的一张图片,首先定义获取图片的控制器

public class CaptchaImageCreateController implements Controller,InitializingBean{

	@Override
	public void afterPropertiesSet() throws Exception {
		// TODO Auto-generated method stub
		
	}

	@Override
	public ModelAndView handleRequest(HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		// TODO Auto-generated method stub
		ValidatorCode codeUtil = ValidatorCodeUtil.getCode();
        System. out.println("code=" +codeUtil.getCode());
                                                            
        request.getSession().setAttribute( "code", codeUtil.getCode());
        // 禁止图像缓存。
        response.setHeader( "Pragma", "no-cache" );
        response.setHeader( "Cache-Control", "no-cache" );
        response.setDateHeader( "Expires", 0);
        response.setContentType( "image/jpeg");
                                                    
        ServletOutputStream sos = null;
        try {
            // 将图像输出到 Servlet输出流中。
        	/*System.out.println("=========***********=============");*/
            sos = response.getOutputStream();
/*            System.out.println(codeUtil.getImage().toString());
            System.out.println("==============================");*/
            ImageIO.write(codeUtil.getImage(),"JPEG",sos);
           /* JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(sos) ;
            encoder.encode();*/
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
            if (null != sos) {
                try {
                    sos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null ;

	}

}
而生产图片的工具类是在网上搜到的,没必要重复造轮子

public class ValidatorCodeUtil {
	
	public static ValidatorCode getCode() {
        // 验证码图片的宽度。
        int width = 80;
        // 验证码图片的高度。
        int height = 30;
        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB );
        Graphics2D g = buffImg.createGraphics();
                                                   
        // 创建一个随机数生成器类。
        Random random = new Random();
                                                   
        // 设定图像背景色(因为是做背景,所以偏淡)
        g.setColor(Color. WHITE);
        g.fillRect(0, 0, width, height);
        // 创建字体,字体的大小应该根据图片的高度来定。
        Font font = new Font("", Font.HANGING_BASELINE, 28);
        // 设置字体。
        g.setFont(font);
                                                   
        // 画边框。
        g.setColor(Color. BLACK);
        g.drawRect(0, 0, width - 1, height - 1);
        // 随机产生155条干扰线,使图象中的认证码不易被其它程序探测到。
        // g.setColor(Color.GRAY);
        // g.setColor(getRandColor(160, 200));
        // for (int i = 0; i < 155; i++) {
        // int x = random.nextInt(width);
        // int y = random.nextInt(height);
        // int xl = random.nextInt(12);
        // int yl = random.nextInt(12);
        // g.drawLine(x, y, x + xl, y + yl);
        // }
                                                   
        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
        StringBuffer randomCode = new StringBuffer();
                                                   
        // 设置默认生成4个验证码
        int length = 4;
        // 设置备选验证码:包括"a-z"和数字"0-9"
        String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ;
                                                   
        int size = base.length();
                                                   
        // 随机产生4位数字的验证码。
        for (int i = 0; i < length; i++) {
            // 得到随机产生的验证码数字。
            int start = random.nextInt(size);
            String strRand = base.substring(start, start + 1);
                                                             
            // 用随机产生的颜色将验证码绘制到图像中。
            // 生成随机颜色(因为是做前景,所以偏深)
            // g.setColor(getRandColor(1, 100));
                                                               
            // 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
            g.setColor( new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(strRand, 15 * i + 6, 24);
                                                   
            // 将产生的四个随机数组合在一起。
            randomCode.append(strRand);
        }
                                                   
        // 图象生效
        g.dispose();
        ValidatorCode code = new ValidatorCode();
        code.image = buffImg;
        code.code = randomCode.toString();
        return code;
    }
          
	public static ValidatorCode getCodeNew() {
		int width = 200;
		int height = 60;
		BufferedImage image = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_RGB); // 创建BufferedImage类的对象
		Graphics g = image.getGraphics(); // 创建Graphics类的对象
		Graphics2D g2d = (Graphics2D) g; // 通过Graphics类的对象创建一个Graphics2D类的对象
		Random random = new Random(); // 实例化一个Random对象
		Font mFont = new Font("华文宋体", Font.BOLD, 30); // 通过Font构造字体
		g.setColor(getRandColor(200, 250)); // 改变图形的当前颜色为随机生成的颜色
		g.fillRect(0, 0, width, height); // 绘制一个填色矩形

		// 画一条折线
		BasicStroke bs = new BasicStroke(2f, BasicStroke.CAP_BUTT,
				BasicStroke.JOIN_BEVEL); // 创建一个供画笔选择线条粗细的对象
		g2d.setStroke(bs); // 改变线条的粗细
		g.setColor(Color.DARK_GRAY); // 设置当前颜色为预定义颜色中的深灰色
		int[] xPoints = new int[3];
		int[] yPoints = new int[3];
		for (int j = 0; j < 3; j++) {
			xPoints[j] = random.nextInt(width - 1);
			yPoints[j] = random.nextInt(height - 1);
		}
		g.drawPolyline(xPoints, yPoints, 3);
		// 生成并输出随机的验证文字
		g.setFont(mFont);
		String sRand = "";
		int itmp = 0;
		for (int i = 0; i < 4; i++) {
			if (random.nextInt(2) == 1) {
				itmp = random.nextInt(26) + 65; // 生成A~Z的字母
			} else {
				itmp = random.nextInt(10) + 48; // 生成0~9的数字
			}
			char ctmp = (char) itmp;
			sRand += String.valueOf(ctmp);
			Color color = new Color(20 + random.nextInt(110),
					20 + random.nextInt(110), 20 + random.nextInt(110));
			g.setColor(color);
			/**** 随机缩放文字并将文字旋转指定角度 **/
			// 将文字旋转指定角度
			Graphics2D g2d_word = (Graphics2D) g;
			AffineTransform trans = new AffineTransform();
			trans.rotate(random.nextInt(45) * 3.14 / 180, 15 * i + 10, 7);
			// 缩放文字
			float scaleSize = random.nextFloat() + 0.8f;
			if (scaleSize > 1.1f)
				scaleSize = 1f;
			trans.scale(scaleSize, scaleSize);
			g2d_word.setTransform(trans);
			/************************/
			g.drawString(String.valueOf(ctmp), 30 * i + 40, 16);

		}
		g.dispose();
		ValidatorCode code = new ValidatorCode();
		code.image = image;
		code.code = sRand.toString();
		return code;
	}
	
    // 给定范围获得随机颜色
    static Color getRandColor( int fc, int bc) {
        Random random = new Random();
        if (fc > 255)
            fc = 255;
        if (bc > 255)
            bc = 255;
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
                                                   
    /**
     *
     * <p class="detail">
     * 验证码图片封装
     * </p>
     *
     *
     */
    public static class ValidatorCode {
        private BufferedImage image ;
        private String code ;
                                                   
        /**
         * <p class="detail">
         * 图片流
         * </p>
         *
         * @return
         */
        public BufferedImage getImage() {
            return image ;
        }
                                                   
        /**
         * <p class="detail">
         * 验证码
         * </p>
         *
         * @return
         */
        public String getCode() {
            return code ;
        }
    }

}
在控制器中 我们生产图片以后,将对应的code保存到session中,以方便后期对code进行验证

登录页面如何才能拿到验证码图片呢,当然拿图片是一个后端请求,需要在web.xml中添加对验证码请求的mapping

<servlet-mapping>
    <servlet-name> cas</servlet-name>
    <url-pattern>/captcha.htm</url-pattern>
  </servlet-mapping>
响应的配置对/captcha.htm 地址处理的handler

在cas-server.xml中 id为handlerMappingC的bean

<bean
      id="handlerMappingC"
      class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
      p:alwaysUseFullPath="true">
    <property name="mappings">
      <util:properties>
        <prop key="/serviceValidate">serviceValidateController</prop>
        <prop key="/proxyValidate">proxyValidateController</prop>
        
        <prop key="/p3/serviceValidate">v3ServiceValidateController</prop>
        <prop key="/p3/proxyValidate">v3ProxyValidateController</prop>
        
        <prop key="/validate">legacyValidateController</prop>
        <prop key="/proxy">proxyController</prop>
        <prop key="/authorizationFailure.html">passThroughController</prop>
        <prop key="/status">healthCheckController</prop>
        <prop key="/statistics">statisticsController</prop>
	//添加对验证码请求的处理器
        <prop key="/captcha.htm">captchaImageCreateController</prop>
        <prop key="/oauth20/QQ/login">qQOAuth20LoginController</prop>
      </util:properties>
    </property>
    <!--
     uncomment this to enable sending PageRequest events.
     <property
       name="interceptors">
       <list>
         <ref bean="pageRequestHandlerInterceptorAdapter" />
       </list>
     </property>
      -->
  </bean>

前端登录页面 只需要通过

<a href="javascript:refresh();"><img id="vali" width="60"
												height="30" src="" style='padding-left: 5px;' /></a>
											<script type="text/javascript">
												function refresh() {
													fm1.vali.src = "";
													fm1.vali.src = 'captcha.htm?t='
															+ new Date()
																	.getTime();
												}
												refresh();
											</script>
来显示验证码


自定义验证器 CustomImgeAuthenticationViaFormAction

public class CustomImgeAuthenticationViaFormAction {

	/** 验证码参数:*/
    private String code = "code";
	/** 缓存key前缀 */
    public static final String CAS_REDIS_PREFIX = "cas_redis:"; 
    
	/** 验证成功返回值. */
    public static final String SUCCESS = "success";

    /** 验证成功警告. */
    public static final String SUCCESS_WITH_WARNINGS = "successWithWarnings";

    /** 验证成功警告 warn. */
    public static final String WARN = "warn";

    /** 验证失败 */
    public static final String AUTHENTICATION_FAILURE = "authenticationFailure";

    /** 错误. */
    public static final String ERROR = "error";

    private RedisManager redisManager;
    /**
     * 表单对象绑定器 (用户凭证)
     */
    private CredentialsBinder credentialsBinder;

    /** 委托票据认证业务类. */
    @NotNull
    private CentralAuthenticationService centralAuthenticationService;

    /** ticket注册服务. */
    @NotNull
    private TicketRegistry ticketRegistry;

    @NotNull
    private CookieGenerator warnCookieGenerator;

    /** Flag indicating whether message context contains warning messages. */
    private boolean hasWarningMessages;

    /** Logger instance. **/
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    public final void doBind(final RequestContext context, final Credential credential) throws Exception {
        final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
        //获取请求IP
        String requestIp = RequestHelper.getRemoteHost(request);
        request.getSession().removeAttribute("errorMsg");
        this.getRedisManager().set(CAS_REDIS_PREFIX+credential.getId(), requestIp);
        if (this.credentialsBinder != null && this.credentialsBinder.supports(credential.getClass())) {
            this.credentialsBinder.bind(request, credential);
        }
    }
    
    public Integer getErrorNum(Credential credential){
    	 String requestIp = this.getRedisManager().get(CAS_REDIS_PREFIX+credential.getId());
         String errorNumStr = this.getRedisManager().get(CAS_REDIS_PREFIX+requestIp);
         return Integer.parseInt(errorNumStr==null?"0":errorNumStr);
    }
   /** 认证类 */
    public final Event submit(final RequestContext context, final Credential credential,
            final MessageContext messageContext) throws Exception {
    	//Validate code
    	if (credential instanceof CustomUsernamePasswordCredential) {
            // 这个类也是我们自己搞的,里面能取到验证码
    		CustomUsernamePasswordCredential rmupc = (CustomUsernamePasswordCredential) credential;
            // 从session中取出生成验证码的时候就保存在session中的验证码
            String sessionCode = (String) WebUtils.getHttpServletRequest(context).getSession().getAttribute(code);
           
            Integer errorNum = getErrorNum(credential);
            //判断验证码错误次数 大于等于5才需要验证
            if(errorNum>=5){
            	 // 如果验证码为null
                if (rmupc.getCode() == null) {
                    // 写入日志
                    logger.warn( "验证码为空" );
                    // 错误信息,会在配置文件(messages_zh_CN.properties)里面先定义好
                    final String code = "required.captcha" ;
                    // 发送错误信息到前台
                    messageContext.addMessage( new MessageBuilder().error().code(code).arg("" ).defaultText(code).build());
                    return newEvent(ERROR);
                }
                // 如果验证码不正确
                if (!rmupc.getCode().toUpperCase().equals(sessionCode.toUpperCase())) {
                    logger.warn( "验证码检验有误" );
                    final String code = "error.authentication.code.bad" ;
                    messageContext.addMessage( new MessageBuilder().error().code(code).arg("" ).defaultText(code).build());
                    return newEvent(ERROR);
                }
            }      
        }

    	
    	
        // Validate login ticket
        final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context);
        final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context);
        if (!authoritativeLoginTicket.equals(providedLoginTicket)) {
            logger.warn("Invalid login ticket {}", providedLoginTicket);
            messageContext.addMessage(new MessageBuilder().code("error.invalid.loginticket").build());
            return newEvent(ERROR);
        }

        final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
        final Service service = WebUtils.getService(context);
        if (StringUtils.hasText(context.getRequestParameters().get("renew")) && ticketGrantingTicketId != null
                && service != null) {

            try {
                final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(
                        ticketGrantingTicketId, service, credential);
                WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
                putWarnCookieIfRequestParameterPresent(context);
                return newEvent(WARN);
            } catch (final AuthenticationException e) {
                return newEvent(AUTHENTICATION_FAILURE, e);
            } catch (final TicketCreationException e) {
                logger.warn(
                        "Invalid attempt to access service using renew=true with different credential. "
                        + "Ending SSO session.");
                this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
            } catch (final TicketException e) {
                return newEvent(ERROR, e);
            }
        }

        try {
            final String tgtId = this.centralAuthenticationService.createTicketGrantingTicket(credential);
            WebUtils.putTicketGrantingTicketInFlowScope(context, tgtId);
            putWarnCookieIfRequestParameterPresent(context);
            final TicketGrantingTicket tgt = (TicketGrantingTicket) this.ticketRegistry.getTicket(tgtId);
            for (final Map.Entry<String, HandlerResult> entry : tgt.getAuthentication().getSuccesses().entrySet()) {
                for (final Message message : entry.getValue().getWarnings()) {
                    addWarningToContext(messageContext, message);
                }
            }
            if (this.hasWarningMessages) {
                return newEvent(SUCCESS_WITH_WARNINGS);
            }
            return newEvent(SUCCESS);
        } catch (final AuthenticationException e) {
            return newEvent(AUTHENTICATION_FAILURE, e);
        } catch (final Exception e) {
            return newEvent(ERROR, e);
        }
    }

    private void putWarnCookieIfRequestParameterPresent(final RequestContext context) {
        final HttpServletResponse response = WebUtils.getHttpServletResponse(context);

        if (StringUtils.hasText(context.getExternalContext().getRequestParameterMap().get("warn"))) {
            this.warnCookieGenerator.addCookie(response, "true");
        } else {
            this.warnCookieGenerator.removeCookie(response);
        }
    }

    private AuthenticationException getAuthenticationExceptionAsCause(final TicketException e) {
        return (AuthenticationException) e.getCause();
    }

    private Event newEvent(final String id) {
        return new Event(this, id);
    }

    private Event newEvent(final String id, final Exception error) {
        return new Event(this, id, new LocalAttributeMap("error", error));
    }

    public final void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) {
        this.centralAuthenticationService = centralAuthenticationService;
    }

    public void setTicketRegistry(final TicketRegistry ticketRegistry) {
        this.ticketRegistry = ticketRegistry;
    }

    public final void setCredentialsBinder(final CredentialsBinder credentialsBinder) {
        this.credentialsBinder = credentialsBinder;
    }

    public final void setWarnCookieGenerator(final CookieGenerator warnCookieGenerator) {
        this.warnCookieGenerator = warnCookieGenerator;
    }

    /**
     * 添加警告信息到 MessageContext中.
     *
     * @param context Message context.
     * @param warning Warning message.
     */
    private void addWarningToContext(final MessageContext context, final Message warning) {
        final MessageBuilder builder = new MessageBuilder()
                .warning()
                .code(warning.getCode())
                .defaultText(warning.getDefaultMessage())
                .args(warning.getParams());
        context.addMessage(builder.build());
        this.hasWarningMessages = true;
    }
    
	public RedisManager getRedisManager() {
		return redisManager;
	}
	public void setRedisManager(RedisManager redisManager) {
		this.redisManager = redisManager;
	}
    
}
修改cas-server.xml文件id为authenticationViaFormAction

 <bean id="authenticationViaFormAction" class="com.cn.instai.cas.flow.CustomImgeAuthenticationViaFormAction"
        p:centralAuthenticationService-ref="centralAuthenticationService"
        p:warnCookieGenerator-ref="warnCookieGenerator"
        p:ticketRegistry-ref="ticketRegistry"
        p:redisManager-ref="redisManager"
        />
其中redisManager为自定义的redis管理器  记录登录失败次数

虽然这里定义了 但是我们不知道什么时候调用这个bean  这里先简单说明下  cas 4.0的登录过程是通过webflow来控制的 对应的login-webflow.xml文件

在login-webflow.xml中定义了登录请求过程的一系列流程,其中

<view-state id="viewLoginForm" view="casLoginView" model="credential">
        <binder>
            <binding property="username" />
            <binding property="password" />
            <binding property="requestUrl"/>
            <binding property="code"/>
        </binder>
        <on-entry>
            <set name="viewScope.commandName" value="'credential'" />
        </on-entry>
		<transition on="submit" bind="true" validate="true" to="realSubmit">
            <evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credential)" />
        </transition>
	</view-state>
binder节点为用户凭证需要绑定的属性  code即为验证码,而在transition中 submit的时候即点登录按钮时候 调用了 authenticationViaFormAction的doBind方法

该方法为:

 public final void doBind(final RequestContext context, final Credential credential) throws Exception {
        final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
        //获取请求IP
        String requestIp = RequestHelper.getRemoteHost(request);
        request.getSession().removeAttribute("errorMsg");
        this.getRedisManager().set(CAS_REDIS_PREFIX+credential.getId(), requestIp);
        if (this.credentialsBinder != null && this.credentialsBinder.supports(credential.getClass())) {
            this.credentialsBinder.bind(request, credential);
        }
    }
上面我添加了自己的部分逻辑,保存请求ip到redis中

        RequestHelper类的代码:

public class RequestHelper {

	public static String getRemoteHost(javax.servlet.http.HttpServletRequest request){
        String ip = request.getHeader("x-forwarded-for");
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }
        return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
    }
}
然后看submit方法

if (credential instanceof CustomUsernamePasswordCredential) {
            // 这个类也是我们自己搞的,里面能取到验证码
    		CustomUsernamePasswordCredential rmupc = (CustomUsernamePasswordCredential) credential;
            // 从session中取出生成验证码的时候就保存在session中的验证码
            String sessionCode = (String) WebUtils.getHttpServletRequest(context).getSession().getAttribute(code);
           
            Integer errorNum = getErrorNum(credential);
            //判断验证码错误次数 大于等于5才需要验证
            if(errorNum>=5){
            	 // 如果验证码为null
                if (rmupc.getCode() == null) {
                    // 写入日志
                    logger.warn( "验证码为空" );
                    // 错误信息,会在配置文件(messages_zh_CN.properties)里面先定义好
                    final String code = "required.captcha" ;
                    // 发送错误信息到前台
                    messageContext.addMessage( new MessageBuilder().error().code(code).arg("" ).defaultText(code).build());
                    return newEvent(ERROR);
                }
                // 如果验证码不正确
                if (!rmupc.getCode().toUpperCase().equals(sessionCode.toUpperCase())) {
                    logger.warn( "验证码检验有误" );
                    final String code = "error.authentication.code.bad" ;
                    messageContext.addMessage( new MessageBuilder().error().code(code).arg("" ).defaultText(code).build());
                    return newEvent(ERROR);
                }
            }      
        }
  添加了自己的逻辑

其中getErrorNum就是获取redis中缓存的登录失败次数

对于该数的记录当然离不开登录验证handler

 try {
            final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username,requestUrl);
            logger.debug("CAS: username is " + username + "---------");
            logger.debug("CAS: md5Password is " + md5Password + "---------");
            logger.debug("CAS: dbPassword is " + dbPassword + "---------");
            //添加判断是否是QQ登录  由于QQ登录没法获取用户真实密码  只能取得MD5密码
            if(!passWord.equals(dbPassword)){
            	if (!dbPassword.equals(md5Password)) {
            		//判断错误次数  如果第一次  设置错误次数为1 否则加1 放入缓存
            		logger.debug("CAS: ---------- UsernamePassword  error------------------------");
            		recordErrorNum(credential); 
            		throw new FailedLoginException("密码不正确");
            	}
            }
            cleanErrorNumCache(credential);
        } catch (final IncorrectResultSizeDataAccessException e) {
        	e.printStackTrace();
        	//判断错误次数  如果第一次  设置错误次数为1 否则加1 放入缓存
        	recordErrorNum(credential);
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + "账号不存在");
            } else {
                throw new FailedLoginException("登录失败 " + username);
            }
        } catch (final DataAccessException e) {
        	e.printStackTrace();
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }
也很简单 密码错误一次 记录一次,或者是登录异常记录一次


到这里知道登录验证过程,也知道如何记录错误次数,那这个错误次数到底怎么用呢,前端如何收到这个次数呢 其实也很简单

同样在cas-server.xml中 有个id为loginHandlerAdapter的bean  它有三个属性 

<bean id="loginHandlerAdapter" class="org.jasig.cas.web.flow.SelectiveFlowHandlerAdapter"
        p:supportedFlowId="login" p:flowExecutor-ref="loginFlowExecutor" p:flowUrlHandler-ref="loginFlowUrlHandler" />
登录handler适配器   有什么用呢? 其实是在访问cas/login 时候对应的处理流程

这里我们只需要对loginFlowUrlHandler进行改造即可

自定义LoginFlowUrlHandler:

public class CustomLoginFlowUrlHandler extends DefaultFlowUrlHandler {

	
	private static final Logger logger = LoggerFactory.getLogger(CustomLoginFlowUrlHandler.class);
	
	private RedisManager redisManager;
	
	/** 缓存key前缀 */
	public static final String CAS_REDIS_PREFIX = "cas_redis:"; 
	public static final String DEFAULT_FLOW_EXECUTION_KEY_PARAMETER = "execution";
	private String flowExecutionKeyParameter = "execution";

	public void setFlowExecutionKeyParameter(String parameterName) {
		this.flowExecutionKeyParameter = parameterName;
	}

	public String getFlowExecutionKey(HttpServletRequest request) {
		System.out.println(request.getQueryString());
		return request.getParameter(this.flowExecutionKeyParameter);
	}

	public int getErrorNum(HttpServletRequest request){
		if(logger.isDebugEnabled()){
			logger.debug("CAS: login flow to get errorNum from redis-------------------start");
		}
		String requestIp = RequestHelper.getRemoteHost(request);
        String errorNumStr = this.getRedisManager().get(CAS_REDIS_PREFIX+requestIp);
        if(logger.isDebugEnabled()){
			logger.debug("CAS: login flow to get errorNum from redis-------------------end");
		}
        return  Integer.parseInt(errorNumStr==null?"0":errorNumStr);
	}
	
	/**
	 * execution的值和session属性对象的字段conversationIdSequence(int类型)相关,如果conversationIdSequence值为1,
	 * 那execution的值即为e2--(conversationIdSequence自增1)。生成Execution的同时,正常情况session内容也相应变化,webflowConversationContainer属性中新增了id为2的conversation,session写入到redis中。
		但是,按照tomcat-redis-session-manager默认的session策略,webflowConversationContainer属性,key没有变,value地址没有变,认为session没有更新,新的session内容没有写入到redis中。
		导致下次请求,取到的webflowConversationContainer属性,没有id为2的conversation,于是cas server端认定execution 为无效。导致刷新页面,而非正常的流程:表单验证。
	 * Description 
	 * @param flowId
	 * @param flowExecutionKey
	 * @param request
	 * @return 
	 * @see org.springframework.webflow.context.servlet.DefaultFlowUrlHandler#createFlowExecutionUrl(java.lang.String, java.lang.String, javax.servlet.http.HttpServletRequest)
	 */
	public String createFlowExecutionUrl(String flowId,
			String flowExecutionKey, HttpServletRequest request) {
		logger.debug("set errorNum to session");
		//设置session更新属性  给出系统当前时间   保证 execution 值更新  
		request.getSession().setAttribute("session_flag", System.currentTimeMillis());
        request.getSession().setAttribute("errorNum", getErrorNum(request));
		StringBuffer builder = new StringBuffer();
		builder.append(request.getRequestURI());
		builder.append("?");
		Map<String, String> flowParams = new LinkedHashMap<String, String>(request.getParameterMap());
		flowParams.put(this.flowExecutionKeyParameter, flowExecutionKey);
		appendQueryParameters(builder, flowParams, getEncodingScheme(request));
		return builder.toString();
	}

	public String createFlowDefinitionUrl(String flowId, AttributeMap input,
			HttpServletRequest request) {
		return new StringBuilder()
				.append(request.getRequestURI())
				.append(request.getQueryString() != null ? new StringBuilder()
						.append("?").append(request.getQueryString())
						.toString() : "").toString();
	}

	public RedisManager getRedisManager() {
		return redisManager;
	}

	public void setRedisManager(RedisManager redisManager) {
		this.redisManager = redisManager;
	}

}
核心方法为:

public String createFlowExecutionUrl(String flowId,
			String flowExecutionKey, HttpServletRequest request) {
		logger.debug("set errorNum to session");
		//设置session更新属性  给出系统当前时间   保证 execution 值更新  
		request.getSession().setAttribute("session_flag", System.currentTimeMillis());
        request.getSession().setAttribute("errorNum", getErrorNum(request));
		StringBuffer builder = new StringBuffer();
		builder.append(request.getRequestURI());
		builder.append("?");
		Map<String, String> flowParams = new LinkedHashMap<String, String>(request.getParameterMap());
		flowParams.put(this.flowExecutionKeyParameter, flowExecutionKey);
		appendQueryParameters(builder, flowParams, getEncodingScheme(request));
		return builder.toString();
	}
将错误次数放入session中,这样我们可以在casLoginView.jsp中通过

<c:if test="${errorNum>=5}">
	验证码
</c:if>

通过一个标签来控制现不现实验证码,配合上我们自定义的验证器,超过次数才开始需要验证码使用,目前只是简单的ip来统计,这个可以自行扩展了

当然如果不需要控制登录多次失败以后才出验证码,可以去掉对错误次数的处理逻辑,相应的只是对错误次数处理拓宽的自定类自然也可以省略







你可能感兴趣的:(cas添加验证码以及默认错误几次以后才出现验证码)