1. Basic structure

Captcha is used to generate the verification code, and Redis is used to store the verification code

In Redis, Key is a 32-bit UUID and Value is a collection of random letters and numbers of Captcha

If the Redis expiration time is set to 1 minute, the expiration verification code automatically becomes invalid

2. Kaptcha’s dependence

The basic dependencies are not described here, but the dependencies to import Captcha

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

All dependencies are as follows

<? The 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 > < the parent > < groupId > org. Springframework. Boot < / groupId > The < artifactId > spring - the 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 - the 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 - the 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> The < version > 1.2.2 < / version > < / dependency > <! Alibaba </groupId> <artifactId> FastJSON </artifactId> <version>1.2.74</version> </dependency> <! Log4j --> <dependency> <groupId>log4j</ artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <! --Swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-boot-starter</artifactId> The < 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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>Copy the code

3. The configuration SpringBoot

Configure the SpringBoot configuration file, focusing on the expiration time of a 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: 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: trueCopy the code

The rest of the configuration, such as Log4j, Druid, SpringSecurity, and RedisTemplate, will not be covered here

4. Configuration Captcha

We can configure some rules for generating Captcha through JAVA configuration classes

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; @configuration Public class KaptchaConfig {@bean public DefaultKaptcha Producer () {//Properties class Properties properties = new Properties(); SetProperty ("kaptcha. Border ", "yes"); SetProperty ("kaptcha.border. Color ", "105,179,90"); / / font color properties. SetProperty (" kaptcha. Textproducer. The font, color, "" blue"); SetProperty ("kaptcha.image.width", "110"); SetProperty ("kaptcha.image.height", "40"); / / font size properties. SetProperty (" kaptcha. Textproducer. The font. The size ", "30"); // session key properties.setProperty("kaptcha.session.key", "code"); / / properties. The verification code length setProperty (" kaptcha. Textproducer. Char. Length ", "4"); / / font properties. SetProperty (" kaptcha. Textproducer. The font. The names, "" song typeface, regular script, Microsoft elegant black"); / / picture interference properties. The setProperty (" kaptcha. Noise. Impl ", "com. Google. Code. Kaptcha. Impl. DefaultNoise"); Config Config = new Config(properties); // The DefaultKaptcha object uses the above configuration and returns the Bean DefaultKaptcha DefaultKaptcha = new DefaultKaptcha(); defaultKaptcha.setConfig(config); return defaultKaptcha; }}Copy the code

5. Utility class

Using the UUID as the key, and considering that there may be different requirements for the output of the captcha, two utility classes are written to handle them

  • 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 {/** * Generate a 32-bit random UUID * @return lowercase 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 producer Public class CaptchaUtil {@autoWired private DefaultKaptcha producer; @Autowired private CaptchaService captchaService; Public map <String, Object> catchaImgCreator() throws IOException {String text = producer.createText(); BufferedImage image = producer.createImage(text); // Generate the image verification code corresponding to the text. OutputStream = new ByteArrayOutputStream(); ImageIO.write(image, "jpg", outputStream); BASE64Encoder encoder = new BASE64Encoder(); / / generated token Map < String, Object > token. = captchaService createToken (text); token.put("img", encoder.encode(outputStream.toByteArray())); return token; }}Copy the code

Interfaces and implementation classes

1. The interface

package com.wang.spring_security_framework.service; import org.springframework.stereotype.Service; import java.io.IOException; import java.util.Map; Public interface CaptchaService {// Generate token Map<String, Object> createToken(String captcha); // Generate capTCHA verification code Map<String, Object> captchaCreator() throws IOException; String versifyCaptcha (String token, String inputCode); // Verify that the entered verification code is correct. }Copy the code

2. The implementation class

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; / / from a configuration file to retrieve SpringBoot expiration time @ Value (" ${server. The servlet. The session. The timeout} ") private Integer timeout; @override public Map<String, Object> createToken(String captcha) {// Generate a token String key = uuidutil.getuuid32 (); The token is the key of the token. The verification code is value. 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; } @override public Map<String, Object> captchaCreator() throws IOException { return captchaUtil.catchaImgCreator(); } @override public String versifyCaptcha(String token, String inputCode) {// Find the corresponding value ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue(); If (redistemplate.haskey (token)) {redistemplate.haskey (token); Delete the corresponding key if (valueoperations.get (token).equals(inputCode)) {redistemplate.delete (token); return "true"; } else { return "false"; } } else { return "false"; }}}Copy the code
  • The validation here simply verifies that the input matches Redis and returns the string
  • For real authentication, we also add username and password considerations to the logic

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); }}Copy the code
  • Captcha Is used to obtain a verification code
  • Login1 is used to verify and return the result after receiving the request from the front end
  • Login1 uses the GET method for easy testing, but in practice it is better to use the POST method for higher security

8. Front-end page implementation

The front-end structure is shown in figure, realizing a simple verification code

<! DOCTYPE HTML > < HTML lang="en" XMLNS :th="http://www.thymeleaf.org"> <head> <meta charset=" utF-8 "> <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=" userName"> <br> <input Name ="password"> <br> <input type="text" ID ="inputCode" Placeholder =" maxLength ="4" name="inputCode"> <! Pass the value through the hidden field, bind the value in the captcha click event below, and get the latest captcha value! -- > < input id = "token" value = "" type =" hidden "name =" token "> < input type =" submit "value =" login "> < / form > < / div > < div > <! <a href="javascript:void(0);" Title =" click to replace the verification code "> <! - this parameter, Return the current DOM element --> <img SRC ="" Alt =" replace the verification code id="imgVerify" onclick="getVerify(this)"> </a> </div> </div> <script> // get the IMG object let imgVerify = $("#imgVerify").get(0); $(function() {getVerify(imgVerify); //$(document).ready(function()) ==> }); 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>Copy the code
  • Surround the IMG tag with an A tag so that if the image doesn’t load it will have a hyperlink, but it won’t work when clicked
  • (function()) = (function()) = (document).ready(function()) ==> Otherwise, the onclick method will not be called the first time it is loaded, and no captcha will be generated!
  • We use the hidden field to pass the key of the verification code into the form. We can use jQuery to manipulate DOM in the Ajax callback function corresponding to the IMG click event, and take out the key value and put it into our hidden field, so that the key and the value entered by the user will be submitted when submitting

The sample

Verification by

Done!