就像我们看到的各个大的网站,用户在登录时默认情况下是不出现验证码的,如果用户连续错误输入3次,就将出现验证码,这样做的优势,此处不再赘述。下面开始详细的配置
一、软件环境
1、cas-client:cas-client-3.2.1-release
2、cas-server:cas-server-3.5.2-release
二、验证码配置及验证
1、既然要使用验证码,那么得有生成验证码的程序,验证码的程序在网上一搜一大把的。
此处提供一个示例
package org.wy.captcha;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.Random;
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;
}
// 给定范围获得随机颜色
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>
*
* @ClassName: ValidatorCode
* @version V1.0
* @date 2012-4-9 下午07:24:14
* @author 罗伟俊
*
*/
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;
}
}
}
2、验证码已经生成,那么怎么将验证码推送到登录页面
package org.wy.captcha;
import java.io.IOException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.wy.captcha.ValidatorCodeUtil.ValidatorCode;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
public class CaptchaImageCreateController implements Controller, InitializingBean {
private Logger log = LoggerFactory.getLogger(CaptchaImageCreateController.class);
public void afterPropertiesSet() throws Exception {
}
public ModelAndView handleRequest(HttpServletRequest request,
HttpServletResponse response) throws Exception {
ValidatorCode codeUtil = ValidatorCodeUtil.getCode();
log.info("验证码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输出流中。
sos = response.getOutputStream();
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(sos);
encoder.encode(codeUtil.getImage());
sos.flush();
sos.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != sos) {
try {
sos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
登录页面接受验证码,login.jsp
<!-- 验证码 -->
<div>
<label for="code" class="fl-label">验证码:</label>
<input class="required" type="text" tabindex="3" id="code" size="10" name="code" autocomplete="off" />
<div style="height:30px;width:150px;text-align:center;margin-left:15px; vertical-align:middle; display: table-cell;">
<a href="javascript:refresh();" οnclick="refresh();" style="width:130px ;height:30px;">
<span style="display: block; float:left; width:60px; height:25px; float:left;">
<img id="vali" width="60" height="30" src="captcha.htm" />
</span>
<span style="display:block;width:60px;height:100%;float:left;vertical-align:middle; display: table-cell;margin-left:15px;">看不清楚?换一个</span>
</a>
</div>
</div>
那么现在有创建和接受的了,那么怎么将这两者联系起来哪?我们看下cas-servlet.xml
在文件中添加
<bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/logout">logoutController</prop> <prop key="/serviceValidate">serviceValidateController</prop> <prop key="/validate">legacyValidateController</prop> <prop key="/proxy">proxyController</prop> <prop key="/proxyValidate">proxyValidateController</prop> <prop key="/samlValidate">samlValidateController</prop> <prop key="/services/add.html">addRegisteredServiceSimpleFormController</prop> <prop key="/services/edit.html">editRegisteredServiceSimpleFormController</prop> <prop key="/services/loggedOut.html">serviceLogoutViewController</prop> <prop key="/services/viewStatistics.html">viewStatisticsController</prop> <prop key="/services/*">manageRegisteredServicesMultiActionController</prop> <prop key="/openid/*">openIdProviderController</prop> <prop key="/authorizationFailure.html">passThroughController</prop> <prop key="/403.html">passThroughController</prop> <prop key="/status">healthCheckController</prop> </props> </property>
添加如下代码:
<prop key="/captcha.htm">captchaImageCreateController</prop>
<!-- 验证码处理类 --> <bean id="captchaImageCreateController" class="org.wy.captcha.CaptchaImageCreateController"></bean>
3、修改验证流程中处理类
我们打开login-webflow.xml,看到
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
只有检验用户名和密码的,我们现在要校验验证码,就要扩张此类
package org.wy.captcha;
import java.util.Map;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.jasig.cas.authentication.principal.RememberMeUsernamePasswordCredentials;
public class CaptchaImageLoginCredentials extends RememberMeUsernamePasswordCredentials {
private static final long serialVersionUID = 1L;
private Map<String, Object> param;
//验证码
@NotNull
@Size(min = 1, message = "login.code.tip")//验证码为空
private String code;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Map<String, Object> getParam() {
return param;
}
public void setParam(Map<String, Object> param) {
this.param = param;
}
}
并用此类替换login-webfolw.xml
<var name="credentials" class="org.wy.captcha.CaptchaImageLoginCredentials" />
在再login-webflow.xml中找到
<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="realSubmit">
<evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>
在<binder>中添加验证码属性
<binding property="code" />
4、修该验证码校验处理类
在cas-servlet.xml找到
<bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction"
p:centralAuthenticationService-ref="centralAuthenticationService"
p:warnCookieGenerator-ref="warnCookieGenerator"/>
我们在AuthenticationViaFormAction中看到验证登录的流程,验证码的校验也要在此处进行,扩张此类
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.wy.captcha;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import org.jasig.cas.CentralAuthenticationService;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.principal.Credentials;
import org.jasig.cas.authentication.principal.Service;
import org.jasig.cas.ticket.TicketException;
import org.jasig.cas.web.bind.CredentialsBinder;
import org.jasig.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.util.StringUtils;
import org.springframework.web.util.CookieGenerator;
import org.springframework.webflow.execution.RequestContext;
/**
* Action to authenticate credentials and retrieve a TicketGrantingTicket for those credentials. If there is a request for renew, then it also generates the Service Ticket required.
*
* @author Scott Battaglia
* @version $Revision$ $Date$
* @since 3.0.4
*/
public class ImageVaditeAuthenticationViaFormAction {
// 验证码参数:
private String code = "code";
/**
* Binder that allows additional binding of form object beyond Spring defaults.
*/
private CredentialsBinder credentialsBinder;
/** Core we delegate to for handling all ticket related tasks. */
@NotNull
private CentralAuthenticationService centralAuthenticationService;
@NotNull
private CookieGenerator warnCookieGenerator;
protected Logger logger = LoggerFactory.getLogger(getClass());
public final void doBind(final RequestContext context, final Credentials credentials) throws Exception {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
if (this.credentialsBinder != null && this.credentialsBinder.supports(credentials.getClass())) {
this.credentialsBinder.bind(request, credentials);
}
}
public final String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext) throws Exception {
// 检测验证码
if (credentials instanceof CaptchaImageLoginCredentials) {
// 这个类也是我们自己搞的,里面能取到验证码
CaptchaImageLoginCredentials rmupc = (CaptchaImageLoginCredentials) credentials;
// 从session中取出生成验证码的时候就保存在session中的验证码
String sessionCode = (String) WebUtils.getHttpServletRequest(context).getSession().getAttribute(code);
// 如果验证码为null
if (rmupc.getCode() == null) {
// 写入日志
logger.warn("验证码为空");
// 错误信息,会在配置文件(messages_zh_CN.properties)里面先定义好
final String code = "login.code.tip";
// 发送错误信息到前台
messageContext.addMessage(new MessageBuilder().error().code(code).arg("").defaultText(code).build());
return "error";
}
// 如果验证码不正确
if (!rmupc.getCode().toUpperCase().equals(sessionCode.toUpperCase())) {
logger.warn("验证码检验有误");
final String code = "login.code.error";
messageContext.addMessage(new MessageBuilder().error().code(code).arg("").defaultText(code).build());
return "error";
}
}
// Validate login ticket
final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context);
final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context);
if (!authoritativeLoginTicket.equals(providedLoginTicket)) {
this.logger.warn("Invalid login ticket " + providedLoginTicket);
final String code = "INVALID_TICKET";
messageContext.addMessage(new MessageBuilder().error().code(code).arg(providedLoginTicket).defaultText(code).build());
return "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, credentials);
WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
putWarnCookieIfRequestParameterPresent(context);
return "warn";
} catch (final TicketException e) {
if (isCauseAuthenticationException(e)) {
populateErrorsInstance(e, messageContext);
return getAuthenticationExceptionEventId(e);
}
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
if (logger.isDebugEnabled()) {
logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials", e);
}
}
}
try {
WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials));
putWarnCookieIfRequestParameterPresent(context);
return "success";
} catch (final TicketException e) {
populateErrorsInstance(e, messageContext);
if (isCauseAuthenticationException(e))
return getAuthenticationExceptionEventId(e);
return "error";
}
}
private void populateErrorsInstance(final TicketException e, final MessageContext messageContext) {
try {
messageContext.addMessage(new MessageBuilder().error().code(e.getCode()).defaultText(e.getCode()).build());
} catch (final Exception fe) {
logger.error(fe.getMessage(), fe);
}
}
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 String getAuthenticationExceptionEventId(final TicketException e) {
final AuthenticationException authEx = getAuthenticationExceptionAsCause(e);
if (this.logger.isDebugEnabled())
this.logger.debug("An authentication error has occurred. Returning the event id " + authEx.getType());
return authEx.getType();
}
private boolean isCauseAuthenticationException(final TicketException e) {
return e.getCause() != null && AuthenticationException.class.isAssignableFrom(e.getCause().getClass());
}
public final void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) {
this.centralAuthenticationService = centralAuthenticationService;
}
/**
* Set a CredentialsBinder for additional binding of the HttpServletRequest to the Credentials instance, beyond our default binding of the Credentials as a Form Object in Spring WebMVC parlance. By the time we invoke this CredentialsBinder, we have already engaged in default binding such that for each HttpServletRequest parameter, if there was a JavaBean property of the Credentials implementation of the same name, we have set that property to be the value of the corresponding request parameter. This CredentialsBinder plugin point exists to allow consideration of things other than HttpServletRequest parameters in populating the Credentials (or more sophisticated consideration of the HttpServletRequest parameters).
*
* @param credentialsBinder
* the credentials binder to set.
*/
public final void setCredentialsBinder(final CredentialsBinder credentialsBinder) {
this.credentialsBinder = credentialsBinder;
}
public final void setWarnCookieGenerator(final CookieGenerator warnCookieGenerator) {
this.warnCookieGenerator = warnCookieGenerator;
}
}
然后将xml中的配置替换成扩展后的类,如下
<bean id="authenticationViaFormAction" class="org.wy.captcha.ImageVaditeAuthenticationViaFormAction" p:centralAuthenticationService-ref="centralAuthenticationService" p:warnCookieGenerator-ref="warnCookieGenerator"/>
5、要是程序能正常运行还需要一下配置
web.xml中添加
<servlet-mapping> <servlet-name>cas</servlet-name> <url-pattern>/captcha.htm</url-pattern> </servlet-mapping>
文件messages_zh_CN.properties添加,如下
login.code.tip=请输入验证码
login.code.error=验证码输入有误
三、效果
至此,验证码正常可以使用