Shiro 入门教程 - Shiro 集成验证码

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

集成验证码

在做用户登录功能时,很多时候都需要验证码支持,验证码的目的是为了防止机器人模拟真实用户登录而恶意访问,如暴力破解用户密码 / 恶意评论等。目前也有一些验证码比较简单,通过一些 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.zte.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 获取相应的验证码图片;另外需要设置响应内容不进行浏览器端缓存。

    <filter>
      <filter-name>JCaptchaFilter</filter-name>
      <filter-class> 
        com.github.zte.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

    <bean id="authcFilter" class="com.github.zte.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.zte.shiro.chapter22.jcaptcha.JCaptchaValidateFilter">
        <property name="jcaptchaEbabled" value="true"/>
        <property name="jcaptchaParam" value="jcaptchaCode"/>
        <property name="failureKeyAttribute" value="shiroLoginFailure"/>
    </bean>
    <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="https://atts.w3cschool.cn/attachments/image/wk/shiro/jcaptcha.jpg" title="点击更换验证码">
        <a class="jcaptcha-btn" href="javascript:;">换一张</a>
        <br/>
    </c:if>

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

十、测试

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

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值