zoukankan      html  css  js  c++  java
  • 权限控制之验证码

    SpringBoot整合Captcha验证码

    1. 基本结构

    使用Captcha生成验证码, 利用Redis存储验证码

    Redis中的结构为, Key是32位的UUID, Value为Captcha的4位随机字母以及数字的集合

    设定Redis过期时间为1min, 即可实现过期验证码的自动失效

    2. 额外的依赖

    基本的依赖这里不再叙述, 主要说一下要导入Captcha的依赖

    <!--Kaptcha-->
    <dependency>
        <groupId>com.github.penggle</groupId>
        <artifactId>kaptcha</artifactId>
        <version>2.3.2</version>
    </dependency>
    

    所有的依赖如下

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.4.0</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.wang</groupId>
        <artifactId>spring_security_framework</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>spring_security_framework</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <!--Redis-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
            <!--JDBC-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
    
            <!--SpringSecurity-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
    
            <!--Thymeleaf-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
    
            <!--Validation-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-validation</artifactId>
            </dependency>
    
            <!--SpringBoot Web-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <!--Mybatis-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.1.4</version>
            </dependency>
    
            <!--SpringSecurity with thymeleaf-->
            <dependency>
                <groupId>org.thymeleaf.extras</groupId>
                <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            </dependency>
    
            <!--MySQL connector-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
    
            <!--Lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    
            <!--Test-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!--Druid-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.2</version>
            </dependency>
    
            <!--FastJSON-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>1.2.74</version>
            </dependency>
    
            <!--log4j-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
    
            <!--Swagger2-->
            <dependency>
                <groupId>io.springfox</groupId>
                <artifactId>springfox-boot-starter</artifactId>
                <version>3.0.0</version>
            </dependency>
    
            <!--HuTool-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>5.4.7</version>
            </dependency>
    
            <!--Kaptcha-->
            <dependency>
                <groupId>com.github.penggle</groupId>
                <artifactId>kaptcha</artifactId>
                <version>2.3.2</version>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    3. 配置SpringBoot

    配置SpringBoot的配置文件, 这里主要关注一个session的过期时间

    #Port
    server:
      port: 80
      servlet:
        session:
          timeout: 1
    
    spring:
      application:
        name: SpringSecurityFramework
    
      #dataBase Setting
      datasource:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/security?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
    
        #Druid Setting
        druid:
          initial-size: 5
          min-idle: 5
          max-active: 20
          max-wait: 60000
          time-between-eviction-runs-millis: 60000
          min-evictable-idle-time-millis: 30000
          validation-query: SELECT 1 FROM DUAL
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          pool-prepared-statements: true
          #Setting For Druid StatView and Filter
          filters: stat,wall,log4j
          max-pool-prepared-statement-per-connection-size: 20
          use-global-data-source-stat: true
          connection-properties: druid.stat.mergeSql=true;druid.stat.slowSql
    
      #Redis Setting
      redis:
        host: 127.0.0.1
        port: 6379
    
      #Thymeleaf
      thymeleaf:
        cache: false
    
    #Mybatis
    mybatis:
      type-aliases-package: com.wang.entity
      mapper-locations: classpath:Mybatis/mapper/*.xml
      configuration:
        map-underscore-to-camel-case: true
    

    其余的配置, 如log4j, druid, SpringSecurity, RedisTemplate,这里就不再赘述

    4. 配置Captcha

    我们可以通过JAVA的配置类来配置Captcha生成验证码的一些规则

    package com.wang.spring_security_framework.config;
    
    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import com.google.code.kaptcha.util.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.Properties;
    
    //Kaptcha配置
    @Configuration
    public class KaptchaConfig {
    
        @Bean
        public DefaultKaptcha producer() {
            //Properties类
            Properties properties = new Properties();
            // 图片边框
            properties.setProperty("kaptcha.border", "yes");
            // 边框颜色
            properties.setProperty("kaptcha.border.color", "105,179,90");
            // 字体颜色
            properties.setProperty("kaptcha.textproducer.font.color", "blue");
            // 图片宽
            properties.setProperty("kaptcha.image.width", "110");
            // 图片高
            properties.setProperty("kaptcha.image.height", "40");
            // 字体大小
            properties.setProperty("kaptcha.textproducer.font.size", "30");
            // session key
            properties.setProperty("kaptcha.session.key", "code");
            // 验证码长度
            properties.setProperty("kaptcha.textproducer.char.length", "4");
            // 字体
            properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
            //图片干扰
            properties.setProperty("kaptcha.noise.impl","com.google.code.kaptcha.impl.DefaultNoise");
    
            //Kaptcha 使用上述配置
            Config config = new Config(properties);
    
            //DefaultKaptcha对象使用上述配置, 并返回这个Bean
            DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
            defaultKaptcha.setConfig(config);
            return defaultKaptcha;
        }
    }
    

    5. 工具类

    使用UUID作为key, 同时考虑到对验证码的输出结果可能有不同的要求, 这里写两个工具类来处理它们

    • UUIDUtil
    package com.wang.spring_security_framework.util;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.stereotype.Component;
    
    import java.util.UUID;
    
    @Component
    public class UUIDUtil {
    
        /**
         * 生成32位的随机UUID
         * @return 字符形式的小写UUID
         */
        @Bean
        public String getUUID32() {
            return UUID.randomUUID().toString()
                    .replace("-", "").toLowerCase();
        }
    }
    
    • CaptchaUtil
    package com.wang.spring_security_framework.util;
    
    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import com.wang.spring_security_framework.service.CaptchaService;
    import io.netty.handler.codec.base64.Base64Encoder;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import sun.misc.BASE64Encoder;
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Map;
    
    @Component
    //Captcha 生成工具
    public class CaptchaUtil {
        @Autowired
        private DefaultKaptcha producer;
        @Autowired
        private CaptchaService captchaService;
    
        //生成catchCreator的map
        public Map<String, Object> catchaImgCreator() throws IOException {
            //生成文字验证码
            String text = producer.createText();
            //生成文字对应的图片验证码
            BufferedImage image = producer.createImage(text);
    
            //将图片写出
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ImageIO.write(image, "jpg", outputStream);
    
            //对写出的字节数组进行Base64编码 ==> 用于传递8比特字节码
            BASE64Encoder encoder = new BASE64Encoder();
    
            //生成token
            Map<String, Object> token = captchaService.createToken(text);
            token.put("img", encoder.encode(outputStream.toByteArray()));
            return token;
        }
    
    }
    

    6. 接口以及实现类

    1. 接口

    package com.wang.spring_security_framework.service;
    
    import org.springframework.stereotype.Service;
    
    import java.io.IOException;
    import java.util.Map;
    
    public interface CaptchaService {
        //生成token
        Map<String, Object> createToken(String captcha);
        //生成captcha验证码
        Map<String, Object> captchaCreator() throws IOException;
        //验证输入的验证码是否正确
        String versifyCaptcha (String token, String inputCode);
    }
    

    2. 实现类

    package com.wang.spring_security_framework.service.serviceImpl;
    
    import com.google.code.kaptcha.impl.DefaultKaptcha;
    import com.wang.spring_security_framework.service.CaptchaService;
    import com.wang.spring_security_framework.util.CaptchaUtil;
    import com.wang.spring_security_framework.util.UUIDUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    import org.springframework.stereotype.Service;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class CaptchaServiceImpl implements CaptchaService {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Autowired
        private UUIDUtil uuidUtil;
        @Autowired
        private CaptchaUtil captchaUtil;
    
        //从SpringBoot的配置文件中取出过期时间
        @Value("${server.servlet.session.timeout}")
        private Integer timeout;
    
        //UUID为key, 验证码为Value放在Redis中
        @Override
        public Map<String, Object> createToken(String captcha) {
            //生成一个token
            String key = uuidUtil.getUUID32();
    
            //生成验证码对应的token  以token为key  验证码为value存在redis中
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            valueOperations.set(key, captcha);
            //设置验证码过期时间
            redisTemplate.expire(key, timeout, TimeUnit.MINUTES);
    
            Map<String, Object> map = new HashMap<>();
            map.put("token", key);
            map.put("expire", timeout);
            return map;
        }
    
        //生成captcha验证码
        @Override
        public Map<String, Object> captchaCreator() throws IOException {
            return captchaUtil.catchaImgCreator();
        }
    
        //验证输入的验证码是否正确
        @Override
        public String versifyCaptcha(String token, String inputCode) {
            //根据前端传回的token在redis中找对应的value
            ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
            if (redisTemplate.hasKey(token)) {
                //验证通过, 删除对应的key
                if (valueOperations.get(token).equals(inputCode)) {
                    redisTemplate.delete(token);
                    return "true";
                } else {
                    return "false";
                }
            } else {
                return "false";
            }
        }
    }
    
    • 这里的验证, 只是简单的验证了输入是否能从Redis中匹配, 返回了字符串
    • 真实的验证中, 我们还要再逻辑中添加用户名和密码的考虑

    7. Controller

    package com.wang.spring_security_framework.controller;
    
    import com.wang.spring_security_framework.service.CaptchaService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.IOException;
    import java.util.Map;
    
    @RestController
    public class LoginController {
        @Autowired
        CaptchaService captchaService;
    
        @GetMapping("/captcha")
        public Map<String, Object> captcha() throws IOException {
            return captchaService.captchaCreator();
        }
    
        @GetMapping("/login1")
        public String login(@RequestParam("token") String token,
                                  @RequestParam("inputCode") String inputCode) {
            return captchaService.versifyCaptcha(token, inputCode);
        }
    }
    
    • captcha 用于获取一个验证码
    • login1 用于接收到前端的请求后验证并返回结果
    • login1 这里为了测试简便实用了GET方法, 而实际中最好使用POST方法, 这样安全性更高

    8. 前端页面的实现

    前端结构如图, 实现了一个简单的验证码

    image-20201116164019011

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
        <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
    </head>
    <body>
    
    <div>
        <div>
            <form th:action="@{/login1}" method="get">
                <input type="text" id="userName" placeholder="请输入用户名" name="userName">
                <br>
                <input type="password" id="password" placeholder="请输入密码" name="password">
                <br>
                <input type="text" id="inputCode" placeholder="请输入验证码" maxlength="4" name="inputCode">
                <!--通过隐藏域传递值, 在下面的验证码点击事件中, 将值绑定过来, 这样就可以获得最新的验证码对应的值了!-->
                <input id="token" value="" type="hidden" name="token">
                <input type="submit" value="登录">
            </form>
    
        </div>
        <div>
            <!-- 当用户链接时,void(0)计算为0,用户点击不会发生任何效果 -->
            <a href="javascript:void(0);" title="点击更换验证码">
                <!--this参数, 返回当前的DOM元素-->
                <img src="" alt="更换验证码" id="imgVerify" onclick="getVerify(this)">
            </a>
        </div>
    </div>
    
    <script>
        //获得img对象
        let imgVerify = $("#imgVerify").get(0);
        //$(function())等同于$(document).ready(function()) ==> 页面加载完毕之后, 才执行函数
        $(function () {
            getVerify(imgVerify);
        });
        //onclick时间绑定的getVerify函数
        function getVerify(obj) {
            $.ajax({
                type: "GET",
                url: "/captcha",
                success: function (result) {
                    obj.src = "data:image/jpeg;base64," + result.img;
                    $("#token").val(result.token);
                }
            });
        }
    </script>
    
    </body>
    </html>
    
    • 用一个 a 标签包围 img 标签, 这样如果图片没有加载出来也有一个超链接, 不过点了以后没有效果
    • ((function())等同于)(document).ready(function()) ==> 页面加载完毕之后, 才执行函数, 这里必须要写这个函数, 否则第一次加载不会调用 onclick 方法, 也就不会生成验证码!
    • 我们利用隐藏域将验证码的key传递到表单中, 我们在 img 的点击事件对应的函数的ajax回调函数中可以利用jQuery操作DOM, 顺带取出key值放到我们的隐藏域中, 这样提交的时候就会提交 key 和用户输入的 value 了
  • 相关阅读:
    移动端web页面使用position:fixed问题
    登录的一些心得
    响应式网页设计
    xss(跨站脚本攻击),crsf(跨站请求伪造),xssf
    HTML5 离线功能介绍
    webapp开发经验和资料
    学习Java,值得你留意的问题(1)更名为《学习Java,容易被你忽略的小细节(1)》
    Python下搜索文件
    从百度地图API接口批量获取地点的经纬度
    获取代理IP地址(BeautifulSoup)
  • 原文地址:https://www.cnblogs.com/wang-sky/p/13985846.html
Copyright © 2011-2022 走看看