`
jinnianshilongnian
  • 浏览: 21434746 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2405078
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:2997748
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5631488
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:257577
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1593183
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:248974
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5847568
Group-logo
跟我学Nginx+Lua开...
浏览量:698167
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:780468
社区版块
存档分类
最新评论

第二十二章 集成验证码——《跟我学Shiro》

阅读更多

 

目录贴: 跟我学Shiro目录贴

 

在做用户登录功能时,很多时候都需要验证码支持,验证码的目的是为了防止机器人模拟真实用户登录而恶意访问,如暴力破解用户密码/恶意评论等。目前也有一些验证码比较简单,通过一些OCR工具就可以解析出来;另外还有一些验证码比较复杂(一般通过如扭曲、加线条/噪点等干扰)防止OCR工具识别;但是在中国就是人多,机器干不了的可以交给人来完成,所以在中国就有很多打码平台,人工识别验证码;因此即使比较复杂的如填字、算数等类型的验证码还是能识别的。所以验证码也不是绝对可靠的,目前比较可靠还是手机验证码,但是对于用户来说相对于验证码还是比较麻烦的。

 

对于验证码图片的生成,可以自己通过如Java提供的图像API自己去生成,也可以借助如JCaptcha这种开源Java类库生成验证码图片;JCaptcha提供了常见的如扭曲、加噪点等干扰支持。本章代码基于《第十六章 综合实例》。

 

一、添加JCaptcha依赖 

<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> 

com.octo.captcha . jcaptcha 提供了jcaptcha 核心;而jcaptcha-integration-simple-servlet提供了与Servlet集成。

 

二、GMailEngine

来自https://code.google.com/p/musicvalley/source/browse/trunk/musicvalley/doc/springSecurity/springSecurityIII/src/main/java/com/spring/security/jcaptcha/GMailEngine.java?spec=svn447&r=447(目前无法访问了),仿照JCaptcha2.0编写类似GMail验证码的样式;具体请参考com.github.zhangkaitao.shiro.chapter22.jcaptcha.GMailEngine。

 

三、MyManageableImageCaptchaService

提供了判断仓库中是否有相应的验证码存在。 

public class MyManageableImageCaptchaService extends 
  DefaultManageableImageCaptchaService { 
    public MyManageableImageCaptchaService(
      com.octo.captcha.service.captchastore.CaptchaStore captchaStore,      
      com.octo.captcha.engine.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);
    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;
    } 
    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;
    }
} 

validateResponse():验证当前请求输入的验证码否正确;并从CaptchaService中删除已经生成的验证码;

hasCaptcha():验证当前请求输入的验证码是否正确;但不从CaptchaService中删除已经生成的验证码(比如Ajax验证时可以使用,防止多次生成验证码);

 

五、JCaptchaFilter

用于生成验证码图片的过滤器。  

public class JCaptchaFilter extends OncePerRequestFilter {
    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();
        }
    }
} 

CaptchaService使用当前会话ID当作key获取相应的验证码图片;另外需要设置响应内容不进行浏览器端缓存。 

 

<!-- 验证码过滤器需要放到Shiro之后 因为Shiro将包装HttpSession 如果不,可能造成两次的sesison id 不一样 -->
<filter>
  <filter-name>JCaptchaFilter</filter-name>
  <filter-class> 
    com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaFilter
  </filter-class>
  </filter>
  <filter-mapping>
    <filter-name>JCaptchaFilter</filter-name>
    <url-pattern>/jcaptcha.jpg</url-pattern>
</filter-mapping> 

这样就可以在页面使用/jcaptcha.jpg地址显示验证码图片。

 

六、JCaptchaValidateFilter

用于验证码验证的Shiro过滤器。  

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;
    }
    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));
    }
    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 {
    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>
    <br/>
</c:if> 

根据jcaptchaEbabled来显示验证码图片。

 

十、测试

输入http://localhost:8080/chapter22将重定向到登录页面;输入正确的用户名/密码/验证码即可成功登录,如果输入错误的验证码,将显示验证码错误页面: 


  

 

 

示例源代码:https://github.com/zhangkaitao/shiro-example;可加群 231889722 探讨Spring/Shiro技术。

        

  

9
6
分享到:
评论
16 楼 lvyuan1234 2018-03-07  
lvyuan1234 写道
找不到验证码接口的,统统在pom文件中加载这个依赖:
<dependency>
    <groupId>com.octo.captcha</groupId>
    <artifactId>jcaptcha-all</artifactId>
    <version>1.0-RC6</version>
</dependency>
说错了,抱歉误导大家了,引入这个包启动时会报Attribute 'expose-proxy' is not allowed to appear in element 'aop:aspectj-autoproxy,应该是某些依赖冲突,实际上我们只需要jcaptcha-api这个依赖就够了。就按照6楼说的来
15 楼 lvyuan1234 2018-03-07  
zhaoaij 写道
请问:jcaptcha我能找到,就是找不到jcaptcha-integration-simple-servlet,这个包在哪个中央库,为什么我找不到。你提供的maven.oschina.com已经关闭。

在网上下载,自己安装到本地仓库吧
14 楼 lvyuan1234 2018-03-07  
找不到验证码接口的,统统在pom文件中加载这个依赖:
<dependency>
    <groupId>com.octo.captcha</groupId>
    <artifactId>jcaptcha-all</artifactId>
    <version>1.0-RC6</version>
</dependency>
13 楼 zhaoaij 2017-01-15  
请问:jcaptcha我能找到,就是找不到jcaptcha-integration-simple-servlet,这个包在哪个中央库,为什么我找不到。你提供的maven.oschina.com已经关闭。
12 楼 ruiliang1988 2016-06-03  
  String id = request.getRequestedSessionId();

String id = request.getSession().getId();

不带这么坑人啊,getRequestedSessionId是从cookie取值啊,第一次没cookie,就是null.XX额,调半天
11 楼 ruiliang1988 2016-06-03  
一个简单操作,干吗要封装偶合那么多代码? 简单问题搞这么复杂,
10 楼 asgab 2015-12-03  
博主,这个群:231889722为啥不加人了啊
9 楼 linyuliang 2014-10-25  
linyuliang 写道
shiro使用默认的sessionmanager没啥问题,当我自定义sessiondao,使用redis来保存session的时候,发现有如下问题:
1. 页面重定向到登录页面的时候,JSESSION_ID是显示到URL参数中的,网上的,修改web.xml的session trace-mode,增加filter等方式都不好使。
2. 验证码验证不成功,看session,每次验证码刷新都会新建一个session,然后验证码放入新session中,但是输入的验证码校验时,又是从最开始的那个session中去取,取出来就是null,所以比较失败。
开涛有遇到么?
我用的是spring mvc+shiro+redis

突然想到是为什么了。。。在sessionmanager下启用了sessionIdCookie导致的。。。
8 楼 linyuliang 2014-10-24  
shiro使用默认的sessionmanager没啥问题,当我自定义sessiondao,使用redis来保存session的时候,发现有如下问题:
1. 页面重定向到登录页面的时候,JSESSION_ID是显示到URL参数中的,网上的,修改web.xml的session trace-mode,增加filter等方式都不好使。
2. 验证码验证不成功,看session,每次验证码刷新都会新建一个session,然后验证码放入新session中,但是输入的验证码校验时,又是从最开始的那个session中去取,取出来就是null,所以比较失败。
开涛有遇到么?
我用的是spring mvc+shiro+redis
7 楼 xiaosheng12345 2014-10-14  
njbble 写道
<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>&nbsp;

帅哥,这个好像maven仓库没有吧,是从官网上下的jar包本地安装的?

加上6楼回复的1.0的api。使用maven.oschina.com的仓库
6 楼 s_p_t110 2014-08-14  
<dependency>
            <groupId>com.octo.captcha</groupId>
            <artifactId>jcaptcha-api</artifactId>
            <version>1.0</version>
        </dependency>

少了这段依赖 我的spring4.0项目加上这个依赖编译就通过了
5 楼 njbble 2014-05-31  
<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>&nbsp;

帅哥,这个好像maven仓库没有吧,是从官网上下的jar包本地安装的?
4 楼 luodaxu 2014-05-20  
上面那个缺少接口的问题解决了吗?我也是这样的问题

3 楼 武林第一帅哥 2014-05-13  
Crazyliuxj 写道
可以搞成输入三次错误用户名密码,再出来验证码吗?
弄个变量控制一下,,当输入三次的时候,,再把验证码的DIV展示出来,,不就可以了
2 楼 leobluewing 2014-04-29  
为什么jcaptcha的jar包里会缺少类?

比如 com.octo.captcha.engine.CaptchaEngine  这个接口为什么会没有?
1 楼 Crazyliuxj 2014-04-15  
可以搞成输入三次错误用户名密码,再出来验证码吗?

相关推荐

Global site tag (gtag.js) - Google Analytics