shiro学习分享(二)——登陆次数限制,RemberMe功能以及验证码功能实现

实现登陆次数限制,RemberMe功能以及验证码功能的整合


登陆次数限制

实现:通过重写HashedCredentialsMatcher这个类实现,并且使用Ehcache管理内存
实现类:

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
    private Cache<String, AtomicInteger> passwordRetryCache;
    /**
     * @param cacheManager 传进一个缓存池
     */
    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        AtomicInteger retryCount = passwordRetryCache.get(username);
        if (retryCount == null) {
            // 高并发下使用的线程安全的int类
            retryCount = new AtomicInteger(0);
            passwordRetryCache.put(username, retryCount);
        }
        // 自定义一个验证过程:当用户连续输入密码错误2次以上禁止用户登录一段时间
        if (retryCount.incrementAndGet() > 2) {
            throw new ExcessiveAttemptsException();
        }
        boolean match = super.doCredentialsMatch(token, info);
        log.info(String.valueOf(match));
        if (match) {
            passwordRetryCache.remove(username);
        }
        return match;
    }

Ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://www.ehcache.org/ehcache.xsd">
    <diskStore path="java.io.tmpdir/ehcache" />
    <!-- 默认缓存 -->
    <defaultCache 
        maxElementsInMemory="1000" 
        eternal="false"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        overflowToDisk="false" />
    <!-- 
        配置自定义缓存
        maxElementsInMemory:缓存中允许创建的最大对象数
        eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
        timeToIdleSeconds:缓存数据的钝化时间,也就是在一个元素消亡之前,
        两次访问时间的最大时间间隔值,这只能在元素不是永久驻留时有效,
        如果该值是 0 就意味着元素可以停顿无穷长的时间。
        timeToLiveSeconds:缓存数据的生存时间,也就是一个元素从构建到消亡的最大时间间隔值,
        这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
        overflowToDisk:内存不足时,是否启用磁盘缓存。
        memoryStoreEvictionPolicy:缓存满了之后的淘汰算法。
    -->

        <!-- 密码尝试次数的缓存 -->
    <cache name="passwordRetryCache"
        maxElementsInMemory="2000"
        eternal="false"
        timeToIdleSeconds="60"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        statistics="true">
    </cache>        
</ehcache>

spring-shiro.xml

<!-- 凭证匹配器 自定义实现了密码次数限制 MD5算法实现 -->
    <bean id="credentialsMatcher"
        class="com.mdy.student.shiro.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager" />
        <property name="hashAlgorithmName" value="MD5" />
        <property name="hashIterations" value="2" />
    </bean>

(也可以自己使用注解注入)


RemberMe功能实现

直接在xml配置就可以了
spring-shiro.xml

<!-- 会话Cookie模板 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid" />
        <property name="httpOnly" value="true" />
        <property name="maxAge" value="-1" />
    </bean>
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe" />
        <property name="httpOnly" value="true" />
        <property name="maxAge" value="604800" /><!-- 7天 -->
    </bean>
    <!-- rememberMe管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cipherKey"
            value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" />
        <property name="cookie" ref="rememberMeCookie" />
    </bean>

验证码功能实现

实现:在后台使用Graphic类生成随机验证码图像,再使用response传回前端,使用sess,保存验证码,接受前端传回来的验证码进行比较
图像生成类的实现:

public final class GraphicsUtil {

    /**
     * @param width 验证码图片宽
     * @param height 验证码图片长
     * @param imgType 验证码图片类型
     * @param sb 验证码
     * @return 生成的验证码图片对象
     */
    public static BufferedImage create(final int width, final int height, final String imgType,StringBuffer sb) {
        Random random = new Random();

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics graphic = image.getGraphics();

        graphic.setColor(Color.getColor("F8F8F8"));
        graphic.fillRect(0, 0, width, height);

        Color[] colors = new Color[] { Color.BLUE, Color.GRAY, Color.GREEN, Color.RED, Color.BLACK, Color.ORANGE,
                Color.CYAN };
        // 在 "画板"上生成干扰线条 ( 50 是线条个数)
        for (int i = 0; i < 50; i++) {
            graphic.setColor(colors[random.nextInt(colors.length)]);
            final int x = random.nextInt(width);
            final int y = random.nextInt(height);
            final int w = random.nextInt(20);
            final int h = random.nextInt(20);
            final int signA = random.nextBoolean() ? 1 : -1;
            final int signB = random.nextBoolean() ? 1 : -1;
            graphic.drawLine(x, y, x + w * signA, y + h * signB);
        }

        // 在 "画板"上绘制字母
        graphic.setFont(new Font("Comic Sans MS", Font.BOLD, 35));
        for (int i = 0; i < 4; i++) {
            final int temp = random.nextInt(26) + 97;
            String s = String.valueOf((char) temp);
            sb.append(s);
            graphic.setColor(colors[random.nextInt(colors.length)]);
            graphic.drawString(s, i * (width / 4), height - (height / 3));
        }
        graphic.dispose();
        return image;
    }
}

controller传回验证码的实现:

@RequestMapping(value = "/setImage", method = RequestMethod.GET)
    public void setImage(HttpServletResponse response, HttpServletRequest request) {
        StringBuffer code = new StringBuffer();
        BufferedImage image = GraphicsUtil.create(WIDTH, HEIGHT, "jpg", code);
        request.getSession().setAttribute("code", code);
        // 禁止图像缓存。
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        // 将图像输出到Servlet输出流中。
        try {
            ServletOutputStream sos = response.getOutputStream();
            ImageIO.write(image, "jpeg", sos);
            sos.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

前端的显示:(js在地址后加时间是为了确保img的src的地址不同,不然图片不会更换)

<script type="text/javascript">
    function changeImage(img) {
        img.src = img.src + '?' +new Date() ;
    }
</script>

<label class="form-label">验证码</label>
<input type="text" name="code" required="required" maxlength="6" style="width: 50px"> 
<img src="/setImage" onclick="changeImage(this)" id="image" alt="换一张"/>

判断代码则是直接去session值进行比较,虽然整合在shiro里面,但是要重写表单验证类FormAuthenticationFilter,博主尝试后发现验证成功后会自动跳转到之前的页面,导致验证验证码两次,并没有找到有效的解决方法。。。还是直接在控制层判断方便。。。


猜你喜欢

转载自blog.csdn.net/madonghyu/article/details/79611170