說明
Jasig CAS登入頁增加圖形驗證碼的功能,圖形驗證碼採用
JCaptcha來實做,這部份參與的不多只有做一些小修改,因此大部份是同事的努力,這邊只是單純的詳實記錄下來,如同事有blogger也會提供相關連結以利參考。
CAS Server的相關設定請參考文章末的參考連結,這裡只著重在Jasig CAS跟JCaptcha的整合。
準備執行環境
Maven是為 build JCaptcha integration with CAS 做準備
- JDK (Java Development Kit) version 1.6+
- Apache Maven 2.2.1+
- Apache Tomcat 6+
- JA-SIG CAS 3.4.2.1+
- JCaptcha 1.0
下載JA-SIG CAS Server
- 下載CAS Server版本為3.4.2.1,並將下載檔案cas-server-3.4.3-release.zip解壓縮。
Build & Running JCaptcha integration with CAS
- 修改pom.xml,可在cas-server-3.4.3-release.zip解壓縮後的路徑cas-server-3.4.3\cas-server-core\pom.xml下找到,新增下列幾行
<!-- jCaptcha -->
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-all</artifactId>
<version>1.0-RC6</version>
</dependency>
- 新增
cas-server-3.4.3\cas-server-core\src\main\java\org\jasig\cas\captcha\CaptchaServiceSingleton.java
檔案,以產生圖形驗證的圖檔。
package org.jasig.cas.captcha;
import com.octo.captcha.service.image.ImageCaptchaService;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.component.word.wordgenerator.RandomWordGenerator;
import com.octo.captcha.component.image.color.RandomRangeColorGenerator;
import com.octo.captcha.component.image.textpaster.TextPaster;
import com.octo.captcha.component.image.textpaster.RandomTextPaster;
import com.octo.captcha.component.image.backgroundgenerator.BackgroundGenerator;
import com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator;
import com.octo.captcha.component.image.fontgenerator.FontGenerator;
import com.octo.captcha.component.image.fontgenerator.RandomFontGenerator;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.image.wordtoimage.ComposedWordToImage;
import com.octo.captcha.image.gimpy.GimpyFactory;
import java.awt.Font;
import com.octo.captcha.service.captchastore.FastHashMapCaptchaStore;
import java.awt.Color;
public class CaptchaServiceSingleton {
private static ImageCaptchaService instance =
new DefaultManageableImageCaptchaService(new FastHashMapCaptchaStore(),
new MyImageCaptchaEngine(),
180,
1000,
750);
public static ImageCaptchaService getInstance() {
return instance;
}
private static class MyImageCaptchaEngine extends ListImageCaptchaEngine {
protected void buildInitialFactories() {
WordGenerator wgen = new RandomWordGenerator("abcdefghijklmnopqrstuvwxyz0123456789");
RandomRangeColorGenerator cgen = new RandomRangeColorGenerator(
new int[] {0, 100},
new int[] {0, 100},
new int[] {0, 100});
TextPaster textPaster = new RandomTextPaster(new Integer(7), new Integer(7), cgen, true);
BackgroundGenerator backgroundGenerator = new UniColorBackgroundGenerator(new Integer(200), new Integer(100), new Color(204, 204, 204));
Font[] fontsList = new Font[] {
new Font("Arial", 0, 10),
new Font("Tahoma", 0, 10),
new Font("Verdana", 0, 10),
};
FontGenerator fontGenerator = new RandomFontGenerator(new Integer(20), new Integer(35), fontsList);
WordToImage wordToImage = new ComposedWordToImage(fontGenerator, backgroundGenerator, textPaster);
this.addFactory(new GimpyFactory(wgen, wordToImage));
}
}
}
- 新增
cas-server-3.4.3\cas-server-core\src\main\java\org\jasig\cas\captcha\ImageCaptchaServlet.java
檔案,以利CAS Server Login Page Request。
package org.jasig.cas.captcha;
import com.octo.captcha.service.CaptchaServiceException;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class ImageCaptchaServlet extends HttpServlet {
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
}
protected void doGet(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
byte[] captchaChallengeAsJpeg = null;
// the output stream to render the captcha image as jpeg into
ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
try {
// get the session id that will identify the generated captcha.
//the same id must be used to validate the response, the session id is a good candidate!
String captchaId = httpServletRequest.getSession().getId();
// call the ImageCaptchaService getChallenge method
BufferedImage challenge =
CaptchaServiceSingleton.getInstance().getImageChallengeForID(captchaId,
httpServletRequest.getLocale());
// a jpeg encoder
JPEGImageEncoder jpegEncoder =
JPEGCodec.createJPEGEncoder(jpegOutputStream);
jpegEncoder.encode(challenge);
} catch (IllegalArgumentException e) {
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
} catch (CaptchaServiceException e) {
httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
// flush it in the response
httpServletResponse.setHeader("Cache-Control", "no-store");
httpServletResponse.setHeader("Pragma", "no-cache");
httpServletResponse.setDateHeader("Expires", 0);
httpServletResponse.setContentType("image/jpeg");
ServletOutputStream responseOutputStream =
httpServletResponse.getOutputStream();
responseOutputStream.write(captchaChallengeAsJpeg);
responseOutputStream.flush();
responseOutputStream.close();
}
}
- 於 Login Page submit 時增加圖形驗證碼的判斷,接著找出 submit 時執行的程式,
cas\WEB-INF\login-webflow.xml
片段如下,登入成功與否的控制在authenticationViaFormAction.submit()
method。
<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
<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">
<set name="flowScope.credentials" value="credentials" />
<evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>
<action-state id="realSubmit">
<evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>
接著從cas\WEB-INF\cas-servlet.xml
(如下)可看到authenticationViaFormAction
的路徑,相對路徑為cas-server-3.4.3\cas-server-core\src\main\java\org\jasig\cas\web\flow
。
<bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction"
p:centralAuthenticationService-ref="centralAuthenticationService"
p:warnCookieGenerator-ref="warnCookieGenerator" />
修改cas-server-3.4.3\cas-server-core\src\main\java\org\jasig\cas\web\flow\AuthenticationViaFormAction.java
,加上圖形驗證碼的判斷,僅列出修改的submit() method,新增 line-8 ~ 20
取得 User 輸入的驗證碼並比對是否輸入正確,將 line-42 ~ 43
置換成 line-46 ~ 55
加上驗證碼輸入正確與否的流程判斷,。
import com.octo.captcha.service.CaptchaServiceException;
import org.jasig.cas.captcha.CaptchaServiceSingleton;
....
public final String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext) throws Exception {
final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
final Service service = WebUtils.getService(context);
// for captcha check input and expect value begin
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
Boolean isResponseCorrect = Boolean.FALSE;
String enterCaptchaId = request.getParameter( "j_captcha_response" );
String captchaId = request.getSession().getId();
try {
isResponseCorrect = CaptchaServiceSingleton.getInstance().validateResponseForID(captchaId, enterCaptchaId);
} catch (CaptchaServiceException e) {
//should not happen, may be thrown if the id is not valid
logger.error( "Captcha Code Validation Failed", e );
}
// for captcah input and expect value end
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 (e.getCause() != null && AuthenticationException.class.isAssignableFrom(e.getCause().getClass())) {
populateErrorsInstance(e, messageContext);
return "error";
}
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";
// for captcha
if( Boolean.TRUE.equals(isResponseCorrect) ) {
WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials));
putWarnCookieIfRequestParameterPresent(context);
return "success";
} else {
// for captcha, captcha validate failed
messageContext.addMessage( new MessageBuilder().error().code( "captcha.input.error" ).defaultText( "captcha.input.error" ).build() );
return "error";
}// for captcha
} catch (final TicketException e) {
populateErrorsInstance(e, messageContext);
return "error";
}
}
- build
cas-server-core-3.4.3.jar
,切換到 cas-server-3.4.3\cas-server-core
路徑執行下列指令,將產出的cas-server-core-3.4.3.jar
複製到cas\WEB-INF\lib
此路徑下。
mvn clean package -Dmaven.test.skip=true
- 下載jcaptcha-1.0-bin.zip,解壓縮後將
jcaptcha-1.0-all.jar
複製到cas\WEB-INF\lib
此路徑下。
- 修改
cas\WEB-INF\web.xml
加上 JCaptcha Servlet mapping。
<!-- jCaptcha Servlet -->
<servlet>
<servlet-name>jcaptcha</servlet-name>
<servlet-class>org.jasig.cas.captcha.ImageCaptchaServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jcaptcha</servlet-name>
<url-pattern>/jcaptcha</url-pattern>
</servlet-mapping>
- 修改登入頁
cas\WEB-INF\view\jsp\default\ui\casLoginView.jsp
加上圖形驗證碼的顯示。
<div class="row fl-controls-left">
<img src="jcaptcha" />
<input type="text" name="j_captcha_response" cssClass="required" cssErrorClass="error" size="25" />
</div>
- 啟動 Tomcat ,連結至CAS Login Page,可看到如下畫面,經測試須正確輸入圖形驗證碼才可登入成功。
下一節依需求修改驗證碼的干擾方式,JCaptcha 官網有提供很多種方式以下所述只是其中一種,單看需求而訂。
Modify JCaptcha Background
cas-server-3.4.3\cas-server-core\src\main\java\org\jasig\cas\captcha\CaptchaServiceSingleton.java
修改如下,照之前的步驟 build & copy cas-server-core-3.4.3.jar
。
package org.jasig.cas.captcha;
import com.octo.captcha.service.image.ImageCaptchaService;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
import com.octo.captcha.engine.image.ListImageCaptchaEngine;
import com.octo.captcha.component.word.wordgenerator.WordGenerator;
import com.octo.captcha.component.word.wordgenerator.RandomWordGenerator;
import com.octo.captcha.component.image.color.RandomRangeColorGenerator;
import com.octo.captcha.component.image.color.SingleColorGenerator;
import com.octo.captcha.component.image.textpaster.TextPaster;
import com.octo.captcha.component.image.textpaster.RandomTextPaster;
import com.octo.captcha.component.image.backgroundgenerator.FileReaderRandomBackgroundGenerator;
import com.octo.captcha.component.image.fontgenerator.FontGenerator;
import com.octo.captcha.component.image.fontgenerator.RandomFontGenerator;
import com.octo.captcha.component.image.wordtoimage.WordToImage;
import com.octo.captcha.component.image.wordtoimage.ComposedWordToImage;
import com.octo.captcha.image.gimpy.GimpyFactory;
import java.awt.Font;
import com.octo.captcha.service.captchastore.FastHashMapCaptchaStore;
import java.awt.Color;
public class CaptchaServiceSingleton {
private static ImageCaptchaService instance =
new DefaultManageableImageCaptchaService(new FastHashMapCaptchaStore(),
new MyImageCaptchaEngine(),
180,
1000,
750);
public static ImageCaptchaService getInstance() {
return instance;
}
private static class MyImageCaptchaEngine extends ListImageCaptchaEngine {
protected void buildInitialFactories() {
WordGenerator wgen = new RandomWordGenerator("abcdefghijklmnopqrstuvwxyz0123456789");
RandomRangeColorGenerator cgen = new RandomRangeColorGenerator(
new int[] {0, 100},
new int[] {0, 100},
new int[] {0, 100});
SingleColorGenerator wordColor = new SingleColorGenerator(Color.BLACK);
TextPaster textPaster = new RandomTextPaster(new Integer(6), new Integer(6), wordColor, true);
FileReaderRandomBackgroundGenerator background = new FileReaderRandomBackgroundGenerator(new Integer(180), new Integer(50),"../webapps/cas/images/backgrounds/");
Font[] fontsList = new Font[] {
new Font("Arial", 0, 10),
new Font("Tahoma", 0, 10),
new Font("Verdana", 0, 10),
};
FontGenerator fontGenerator = new RandomFontGenerator(new Integer(30), new Integer(30), fontsList);
WordToImage wordToImage = new ComposedWordToImage(fontGenerator, background, textPaster);
this.addFactory(new GimpyFactory(wgen, wordToImage));
}
}
}
產出圖形驗證碼的背景圖(jpg),放置於CaptchaServiceSingleton.java
所設定的路徑%TOMCAT_HOME%/webapps/cas/images/backgrounds/
,此 sample 只產出二張圖,一張黑色直條紋,一張紫色橫條紋
啟動 Tomcat ,連結至CAS Login Page,可看到如下畫面,背景圖可自行放置數量不限,會依亂數顯示,如有放置新的圖 Tomcat 須重啟才能顯示新的背景圖。
相關設定可參考:
JA-SIG CAS Single-Sign On - 1
SSO demonstration for a quick start with CAS
CAS on Windows Quick Setup Guide
JA-SIG CAS - 3-1: 增加Captcha功能
JCAPTCHA integration with CAS
沒有留言:
張貼留言