《跟我学Shiro》笔记22-集成验证码
原文地址:第二十二章 集成验证码——《跟我学Shiro》
目录贴: 跟我学Shiro目录贴
添加JCaptcha依赖
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha</artifactId>
<version>2.0-alpha-1</version>
</dependency>
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-integration-simple-servlet</artifactId>
<version>2.0-alpha-1</version>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
GMailEngine
/**
* JCaptcha验证码图片生成引擎, 仿照JCaptcha2.0编写类似GMail验证码的样式.
*/
public class GMailEngine extends ListImageCaptchaEngine {
@Override
protected void buildInitialFactories() {
// 图片和字体大小设置
int minWordLength = 6;
int maxWordLength = 6;
int fontSize = 20;
int imageWidth = 100;
int imageHeight = 36;
WordGenerator dictionnaryWords = new ComposeDictionaryWordGenerator(
new FileDictionary("toddlist"));
// word2image components
TextPaster randomPaster = new DecoratedRandomTextPaster(minWordLength,
maxWordLength, new RandomListColorGenerator(new Color[]{
new Color(23, 170, 27), new Color(220, 34, 11),
new Color(23, 67, 172)}), new TextDecorator[]{});
BackgroundGenerator background = new UniColorBackgroundGenerator(
imageWidth, imageHeight, Color.white);
FontGenerator font = new RandomFontGenerator(fontSize, fontSize,
new Font[]{new Font("nyala", Font.BOLD, fontSize),
new Font("Bell MT", Font.PLAIN, fontSize),
new Font("Credit valley", Font.BOLD, fontSize)});
ImageDeformation postDef = new ImageDeformationByFilters(
new ImageFilter[]{});
ImageDeformation backDef = new ImageDeformationByFilters(
new ImageFilter[]{});
ImageDeformation textDef = new ImageDeformationByFilters(
new ImageFilter[]{});
WordToImage word2image = new DeformedComposedWordToImage(font,
background, randomPaster, backDef, textDef, postDef);
addFactory(new GimpyFactory(dictionnaryWords, word2image));
}
}
MyManageableImageCaptchaService 判断仓库中是否有相应的验证码存在
public class MyManageableImageCaptchaService extends DefaultManageableImageCaptchaService {
public MyManageableImageCaptchaService(CaptchaStore captchaStore, CaptchaEngine captchaEngine, int minGuarantedStorageDelayInSeconds,
int maxCaptchaStoreSize, int captchaStoreLoadBeforeGarbageCollection) {
super(captchaStore, captchaEngine, minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize, captchaStoreLoadBeforeGarbageCollection);
}
public boolean hasCapcha(String id, String userCaptchaResponse) {
return store.getCaptcha(id).validateResponse(userCaptchaResponse);
}
}
JCaptcha 工具类,提供相应的API来验证当前请求输入的验证码是否正确
public class JCaptcha {
public static final MyManageableImageCaptchaService captchaService
= new MyManageableImageCaptchaService(new FastHashMapCaptchaStore(), new GMailEngine(), 180, 100000, 75000);
// 验证当前请求输入的验证码否正确;并从 CaptchaService 中删除已经生成的验证码;
public static boolean validateResponse(HttpServletRequest request, String userCaptchaResponse) {
if (request.getSession(false) == null) return false;
boolean validated = false;
try {
String id = request.getSession().getId();
validated = captchaService.validateResponseForID(id, userCaptchaResponse).booleanValue();
} catch (CaptchaServiceException e) {
e.printStackTrace();
}
return validated;
}
// 验证当前请求输入的验证码是否正确;但不从 CaptchaService 中删除已经生成的验证码
// 比如 Ajax 验证时可以使用,防止多次生成验证码
public static boolean hasCaptcha(HttpServletRequest request, String userCaptchaResponse) {
if (request.getSession(false) == null) return false;
boolean validated = false;
try {
String id = request.getSession().getId();
validated = captchaService.hasCapcha(id, userCaptchaResponse);
} catch (CaptchaServiceException e) {
e.printStackTrace();
}
return validated;
}
}
JCaptchaFilter 用于生成验证码图片的过滤器
CaptchaService 使用当前会话 ID 当作 key 获取相应的验证码图片;另外需要设置响应内容不进行浏览器端缓存。
public class JCaptchaFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setDateHeader("Expires", 0L);
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
String id = request.getRequestedSessionId();
BufferedImage bi = JCaptcha.captchaService.getImageChallengeForID(id);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bi, "jpg", out);
try {
out.flush();
} finally {
out.close();
}
}
}
web.xml
<!-- 验证码过滤器需要放到Shiro之后 因为Shiro将包装HttpSession 如果不 可能造成两次的sesison id 不一样 -->
<filter>
<filter-name>JCaptchaFilter</filter-name>
<filter-class>com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>JCaptchaFilter</filter-name>
<url-pattern>/jcaptcha.jpg</url-pattern>
</filter-mapping>
JCaptchaValidateFilter 验证码验证
public class JCaptchaValidateFilter extends AccessControlFilter {
private boolean jcaptchaEbabled = true;//是否开启验证码支持
private String jcaptchaParam = "jcaptchaCode";//前台提交的验证码参数名
private String failureKeyAttribute = "shiroLoginFailure"; //验证码验证失败后存储到的属性名
public void setJcaptchaEbabled(boolean jcaptchaEbabled) {
this.jcaptchaEbabled = jcaptchaEbabled;
}
public void setJcaptchaParam(String jcaptchaParam) {
this.jcaptchaParam = jcaptchaParam;
}
public void setFailureKeyAttribute(String failureKeyAttribute) {
this.failureKeyAttribute = failureKeyAttribute;
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
//1、设置验证码是否开启属性,页面可以根据该属性来决定是否显示验证码
request.setAttribute("jcaptchaEbabled", jcaptchaEbabled);
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
//2、判断验证码是否禁用 或不是表单提交(允许访问)
if (jcaptchaEbabled == false || !"post".equalsIgnoreCase(httpServletRequest.getMethod())) {
return true;
}
//3、此时是表单提交,验证验证码是否正确
return JCaptcha.validateResponse(httpServletRequest, httpServletRequest.getParameter(jcaptchaParam));
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//如果验证码失败了,存储失败key属性
request.setAttribute(failureKeyAttribute, "jCaptcha.error");
return true;
}
}
MyFormAuthenticationFilter
用于验证码验证的 Shiro 拦截器在用于身份认证的拦截器之前运行;但是如果验证码验证拦截器失败了,就不需要进行身份认证拦截器流程了;所以需要修改下如 FormAuthenticationFilter 身份认证拦截器,当验证码验证失败时不再走身份认证拦截器。即如果之前已经错了,那直接跳过即可。
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
if(request.getAttribute(getFailureKeyAttribute()) != null) {
return true;
}
return super.onAccessDenied(request, response, mappedValue);
}
}
spring-config-shiro.xml
<!-- 基于Form表单的身份验证过滤器 -->
<bean id="authcFilter" class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.MyFormAuthenticationFilter">
<property name="usernameParam" value="username"/>
<property name="passwordParam" value="password"/>
<property name="rememberMeParam" value="rememberMe"/>
<property name="failureKeyAttribute" value="shiroLoginFailure"/>
</bean>
<bean id="jCaptchaValidateFilter" class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaValidateFilter">
<property name="jcaptchaEbabled" value="true"/>
<property name="jcaptchaParam" value="jcaptchaCode"/>
<property name="failureKeyAttribute" value="shiroLoginFailure"/>
</bean>
<!-- Shiro的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="authcFilter"/>
<entry key="sysUser" value-ref="sysUserFilter"/>
<entry key="jCaptchaValidate" value-ref="jCaptchaValidateFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/static/** = anon
/jcaptcha* = anon
/login = jCaptchaValidate,authc
/logout = logout
/authenticated = authc
/** = user,sysUser
</value>
</property>
</bean>
login.jsp 登录页面
<c:if test="${jcaptchaEbabled}">
验证码:
<input type="text" name="jcaptchaCode">
<img class="jcaptcha-btn jcaptcha-img" src="${pageContext.request.contextPath}/jcaptcha.jpg" title="点击更换验证码">
<a class="jcaptcha-btn" href="javascript:;">换一张</a>
</c:if>
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《跟我学Shiro》笔记22-集成验证码
文章字数:1.5k
本文作者:Bin
发布时间:2018-04-25, 20:12:22
最后更新:2019-08-06, 00:42:14
原始链接:http://coolview.github.io/2018/04/25/%E8%B7%9F%E6%88%91%E5%AD%A6Shiro/%E3%80%8A%E8%B7%9F%E6%88%91%E5%AD%A6Shiro%E3%80%8B%E7%AC%94%E8%AE%B022-%E9%9B%86%E6%88%90%E9%AA%8C%E8%AF%81%E7%A0%81/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。