分享

CAS增加验证码、密码有效期、限制用户登录之类的功能

 时间要去哪 2014-06-21



最近项目要求增加验证码、密码有效期、限制用户登录之类的功能,于是花了三天去看CAS源码和耶鲁的官网User-manual。

基于CAS 3.3.5,Tomcat 6.29,JDK 1.6
以下部分红色表示新增,蓝色表示修改,绿色表示移除

一.增加验证码功能

 

配置:cas\WEB-INF\cas-servlet.xml,handlerMappingC下增加一个属性

<bean
id="handlerMappingC"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property
name="mappings">
<props>
…………
<prop key="/openid/*">openIdProviderController</prop>
<prop key="/captcha.htm">captchaImageCreateController</prop>
</props>
</property>
<property
name="alwaysUseFullPath" value="true" />
</bean>


cas-servlet.xml后面添加server的bean文件,这里包括验证码生成工具,密码验证类。

<bean id="captchaErrorCountAction" class="com.ist.cas.CaptchaErrorCountAction"/>

<bean id="captchaValidateAction" class="com.ist.cas.CaptchaValidateAction"
p:captchaService-ref="jcaptchaService"
p:captchaValidationParameter="j_captcha_response"/>

<bean id="captchaImageCreateController" class="com.ist.cas.CaptchaImageCreateController">
<property name="jcaptchaService" ref="jcaptchaService"/>
</bean>

<bean id="fastHashMapCaptchaStore" class="com.octo.captcha.service.captchastore.FastHashMapCaptchaStore" />
<bean id="jcaptchaService" class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService">
<constructor-arg type="com.octo.captcha.service.captchastore.CaptchaStore" index="0">
<ref bean="fastHashMapCaptchaStore"/>
</constructor-arg>
<constructor-arg type="com.octo.captcha.engine.CaptchaEngine" index="1">
<bean class="com.ist.cas.JCaptchaEngineEx"/>
</constructor-arg>
<constructor-arg index="2">
<value>180</value>
</constructor-arg>
<constructor-arg index="3">
<value>100000</value>
</constructor-arg>
<constructor-arg index="4">
<value>75000</value>
</constructor-arg>
</bean>



配置:cas\WEB-INF\web.xml,添加验证码JCaptcha

<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/captcha.htm</url-pattern>
</servlet-mapping>



配置:cas\WEB-INF\login-webflow.xml,修改

<action-state id="submit">

<action bean="authenticationViaFormAction" method="submit" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>

<action-state id="submit">
<action bean="authenticationViaFormAction" method="submit" />
<transition on="warn" to="warn" />
<transition on="success" to="captchaValidate" />
<transition on="error" to="viewLoginForm" />
</action-state>

添加流程节点:

<action-state id="captchaValidate">
<action bean="captchaValidateAction" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>


其中使用到的几个java文件:
CaptchaValidateAction.java

package com.ist.cas;

import org.jasig.cas.web.support.WebUtils;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import com.octo.captcha.service.CaptchaServiceException;
import com.octo.captcha.service.image.ImageCaptchaService;

public final class CaptchaValidateAction extends AbstractAction {
private ImageCaptchaService captchaService;
private String captchaValidationParameter = "j_captcha_response";

protected Event doExecute(final RequestContext context) {
String captcha_response = context.getRequestParameters().get(captchaValidationParameter);
boolean valid = false;

if (captcha_response != null) {
String id = WebUtils.getHttpServletRequest(context).getSession().getId();
if (id != null) {
try {
valid = captchaService.validateResponseForID(id, captcha_response).booleanValue();
} catch (CaptchaServiceException cse) {
}
}
}

if (valid) {
return success();
}
context.getRequestScope().put("captchaValidatorError", "bad");
return error();
}

public void setCaptchaService(ImageCaptchaService captchaService) {
this.captchaService = captchaService;
}

public void setCaptchaValidationParameter(String captchaValidationParameter) {
this.captchaValidationParameter = captchaValidationParameter;
}
}


CaptchaImageCreateController.java

package com.ist.cas;

import com.octo.captcha.service.image.ImageCaptchaService;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import java.io.ByteArrayOutputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.*;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class CaptchaImageCreateController implements Controller, InitializingBean {
private ImageCaptchaService jcaptchaService;
public CaptchaImageCreateController(){
}
public ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response) throws Exception {
byte captchaChallengeAsJpeg[] = null;
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
String captchaId = request.getSession().getId();
java.awt.image.BufferedImage challenge=jcaptchaService.getImageChallengeForID(captchaId,request.getLocale());
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);

jpegEncoder.encode(challenge);
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();response.setHeader("Cache-Control", "no-store");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0L);
response.setContentType("image/jpeg");
ServletOutputStream responseOutputStream = response.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
return null;
}
public void setJcaptchaService(ImageCaptchaService jcaptchaService) {
this.jcaptchaService = jcaptchaService;
}
public void afterPropertiesSet() throws Exception {
if(jcaptchaService == null)
throw new RuntimeException("Image captcha service wasn`t set!");
else
return;
}
}



JCaptchaEngineEx.java

package com.ist.cas;

import java.awt.Color;
import java.awt.Font;

import com.octo.captcha.component.image.backgroundgenerator.BackgroundGenerator;
import com.octo.captcha.component.image.backgroundgenerator.GradientBackgroundGenerator;
import com.octo.captcha.component.image.color.SingleColorGenerator;
import com.octo.captcha.component.image.fontgenerator.FontGenerator;
import com.octo.captcha.component.image.textpaster.DecoratedRandomTextPaster;
import com.octo.captcha.component.image.textpaster.TextPaster;
import com.octo.captcha.component.image.textpaster.textdecorator.BaffleTextDecorator;
import com.octo.captcha.component.image.textpaster.textdecorator.LineTextDecorator;
import com.octo.captcha.component.image.textpaster.textdecorator.TextDecorator;
import com.octo.captcha.component.image.wordtoimage.ComposedWordToImage;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.word.wordgenerator.RandomWordGenerator;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.image.gimpy.GimpyFactory;

public class JCaptchaEngineEx extends ListImageCaptchaEngine {

protected void buildInitialFactories() {
/**
* Set Captcha Word Length Limitation which should not over 6
*/
Integer minAcceptedWordLength = new Integer(4);
Integer maxAcceptedWordLength = new Integer(4);
/**
* Set up Captcha Image Size: Height and Width
*/
Integer imageHeight = new Integer(28);
Integer imageWidth = new Integer(75);
/**
* Set Captcha Font Size between 50 and 55
*/
final Integer minFontSize = new Integer(22);
final Integer maxFontSize = new Integer(22);
/**
* We just generate digit for captcha source char
* Although you can use abcdefg......xyz
*/
WordGenerator wordGenerator = (new RandomWordGenerator("0123456789abcdefghijklmnopqrstuvwxyz"));
/**
* cyt and unruledboy proved that backgroup not a factor of Security.
* A captcha attacker won't affaid colorful backgroud, so we just use
* white color, like google and hotmail.
*/
Color bgColor = new Color(255, 255, 255);
BackgroundGenerator backgroundGenerator = new GradientBackgroundGenerator(
imageWidth, imageHeight, bgColor, bgColor);
/**
* font is not helpful for security but it really increase difficultness for attacker
*/
FontGenerator _fontGenerator = new FontGenerator() {
public Font getFont() {
return new Font("Arial", Font.ITALIC, 16);
}
public int getMinFontSize() {
return minFontSize.intValue();
}
public int getMaxFontSize() {
return maxFontSize.intValue();
}
};
/**
* Note that our captcha color is Blue
*/
SingleColorGenerator scg = new SingleColorGenerator(Color.BLACK);
/**
* decorator is very useful pretend captcha attack.
* we use two line text decorators.
*/
LineTextDecorator line_decorator = new LineTextDecorator(new Integer(2), Color.RED);
LineTextDecorator line_decorator2 = new LineTextDecorator(new Integer(3), Color.CYAN);
TextDecorator[] textdecorators = new TextDecorator[2];

textdecorators[0] = line_decorator;
textdecorators[1] = line_decorator2;

TextPaster _textPaster = new DecoratedRandomTextPaster(minAcceptedWordLength,
maxAcceptedWordLength, scg, new TextDecorator[]{new BaffleTextDecorator(new Integer(0), Color.WHITE)});

/**
* ok, generate the WordToImage Object for logon service to use.
*/
WordToImage wordToImage = new ComposedWordToImage(
_fontGenerator, backgroundGenerator, _textPaster);

addFactory(new GimpyFactory(wordGenerator, wordToImage));
}
}




CASPasswordEncoder.java

package com.ist.cas;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.authentication.handler.PasswordEncoder;

public class CASPasswordEncoder implements PasswordEncoder {
protected static final Log log = LogFactory.getLog(CASPasswordEncoder.class);

public String encode(String strSource) {
MD5 theMD5 = new MD5();
String strPassMD5 = theMD5.getMD5ofStr(strSource);
return strPassMD5;
}
}


页面配置方面,修改cas\WEB-INF\view\jsp\myth\ui\casLoginView.jsp在密码输入框下新增

<li>
<span class="itemname">验证码:</span>
<span class="iteminput">
<input name = "j_captcha_response" type = "text">
<img src = "captcha.htm">

<a href="#" onclick="javascript:window.location.reload();">看不清换一个</a>
</span>
</li>



另外发现一个问题,验证出错时的提示信息问题,官方没有给出一个提示的写法,而且提示时新增异常也会导致验证直接成功,所以验证码的提示信息在页面独立分开

<form:errors path="*" cssClass="errors" id="status" element="div" />
<c:if test="${not empty captchaValidatorError}"><div id="status" class="errors">验证码输入错误。</div></c:if>


二.密码有效期验证功能

 另外tb_user表中pwd_changedate表示密码更新时间,当前日期超过密码更新的有效期,就会提醒密码出错。

cas\WEB-INF\deployerConfigContext.xml,修改

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="casDataSource" />
<property name="sql" value="select password from tb_user where login_name = ?" />
<property name="passwordEncoder" ref="myPasswordEncoder"/>
</bean>

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="casDataSource" />
<property name="sql" value="select password from tb_user where login_name = ? and trunc(pwd_changedate) > trunc(SYSDATE) - ${cas.password.validate.date}" />
<property name="passwordEncoder" ref="myPasswordEncoder"/>
</bean>


cas\WEB-INF\cas.properties,增加密码有效期属性。

cas.password.validate.date=90


三.限制同一IP或用户名登录错误次数功能

 

限制用户登录,官方提供两种解决办法,一种是内存限制,一种是Inspektr。内存限制是最简单,所以这里用内存限制。

首先,在cas\WEB-INF\spring-configuration新建一个xml,throttleInterceptorTrigger.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www./schema/beans"
xmlns:xsi="http://www./2001/XMLSchema-instance"
xmlns:p="http://www./schema/p"
xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-2.0.xsd">

<bean id="throttleInterceptor"
class="org.jasig.cas.web.support.InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter"
p:failureRangeInSeconds="${throttleInterceptor.failureRangeInSeconds}"
p:failureThreshold="${throttleInterceptor.failureThreshold}" />

<bean id="throttleInterceptorJobDetail"
class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"
p:targetObject-ref="throttleInterceptor" p:targetMethod="decrementCounts" />

<bean id="periodicThrottleCleanerTrigger"
class="org.springframework.scheduling.quartz.SimpleTriggerBean"
p:jobDetail-ref="throttleInterceptorJobDetail" p:startDelay="0"
p:repeatInterval="${periodicThrottleCleanerTrigger.repeatInterval}" />
</beans>


四.客户端加密功能

修改cas\WEB-INF\view\jsp\myth\ui\casLoginView.jsp

JS方面引入jquery.md5.js,用于前台加密

<script type="text/javascript" src="js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="js/jquery.md5.js"></script>

<script language="javascript">

var password = document.getElementById("password").value;
document.getElementById("password").value = $.md5(password).toUpperCase();

</script>


配置:cas\WEB-INF\cas-servlet.xml,注释原密码加密功能

<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="casDataSource" />
<property name="sql" value="select password from tb_user where login_name = ? and trunc(pwd_changedate) > trunc(SYSDATE) - ${cas.password.validate.date}" />
<!--<property name="passwordEncoder" ref="myPasswordEncoder"/>-->
</bean>


五.记录错误日志功能

配置:cas\WEB-INF\cas-servlet.xml,增加一个记录类

<bean id="captchaErrorCountAction" class="com.ist.cas.CaptchaErrorCountAction">
<property name="dataSource" ref="casDataSource" />
<property name="sql" value="insert into tb_log values(10000, '0', '1', ?, ?)" />
</bean>

其中sql为写错误日志的Insert语句。?第一个参数默认配置当前时间,第二个参数默认配置错误描述。

配置:cas\WEB-INF\login-webflow.xml,修改

<action-state id="submit">

<action bean="authenticationViaFormAction" method="submit" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>
	<action-state id="captchaValidate">
<action bean="captchaValidateAction" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>

<action-state id="submit">
<action bean="authenticationViaFormAction" method="submit" />
<transition on="warn" to="warn" />
<transition on="success" to="captchaValidate" />
<transition on="error" to="errorCount" />
</action-state>

	<action-state id="captchaValidate">
<action bean="captchaValidateAction" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="errorCount" />
</action-state>
添加流程节点:

<action-state id="errorCount">
<action bean="captchaErrorCountAction" />
<transition on="success" to="viewLoginForm" />
</action-state>


在cas\WEB-INF\classes\com\ist\cas\添加captchaErrorCountAction.class

package com.ist.cas; import java.util.Date; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.webflow.action.AbstractAction; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import javax.sql.DataSource; import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; public final class CaptchaErrorCountAction extends AbstractAction { protected static final Log log = LogFactory.getLog(CaptchaErrorCountAction.class); private SimpleJdbcTemplate jdbcTemplate; private DataSource dataSource; private String sql; protected Event doExecute(final RequestContext context) { int count = 1; try { getJdbcTemplate().update(this.sql, new Object[]{new Date(), "登录失败"}); } catch (Exception e) { log.error(e); } return success(); } public final void setDataSource(DataSource dataSource) { this.jdbcTemplate = new SimpleJdbcTemplate(dataSource); this.dataSource = dataSource; } protected final SimpleJdbcTemplate getJdbcTemplate() { return this.jdbcTemplate; } protected final DataSource getDataSource() { return this.dataSource; } public void setSql(String sql) { this.sql = sql; } }



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多