使用Captcha做验证码,存到数据库中,登录时候后端做判空,过期,错误提示,样式配置

实现思路:调用验证码接口传入uuid获取到验证码,把uuid和验证码传入登录接口,根据数据库中的验证码匹配,验证码登录后删除已使用的验证码,调用验证码接口时会删除小于当前时间的数据(因为存储时间的时候存的时间比系统多一分钟)

pom依赖

<!--验证码-->
<dependency>
     <groupId>com.github.axet</groupId>
     <artifactId>kaptcha</artifactId>
     <version>0.0.9</version>
 </dependency>
 <!--时间工具-->
  <dependency>
       <groupId>joda-time</groupId>
       <artifactId>joda-time</artifactId>
       <version>2.9.9</version>
   </dependency>

建表sql

/*
 Navicat Premium Data Transfer

 Source Server         : 本地人防
 Source Server Type    : PostgreSQL
 Source Server Version : 90507
 Source Host           : localhost:5432
 Source Catalog        : postgis_23_sample
 Source Schema         : public

 Target Server Type    : PostgreSQL
 Target Server Version : 90507
 File Encoding         : 65001

 Date: 24/03/2021 09:12:10
*/


-- ----------------------------
-- Table structure for xj_captcha
-- ----------------------------
DROP TABLE IF EXISTS "public"."xj_captcha";
CREATE TABLE "public"."xj_captcha" (
  "uuid" varchar COLLATE "pg_catalog"."default" NOT NULL,
  "code" varchar(255) COLLATE "pg_catalog"."default",
  "expire_time" timestamp(6)
)
;

-- ----------------------------
-- Primary Key structure for table xj_captcha
-- ----------------------------
ALTER TABLE "public"."xj_captcha" ADD CONSTRAINT "xj_captcha_pkey" PRIMARY KEY ("uuid");

config 配置

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.ph.rfwg.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;


/**
 * 生成验证码配置
 *
 * @author Mark sunlightcs@gmail.com
 */
@Configuration
public class KaptchaConfig {

    @Bean
    public DefaultKaptcha producer() {
        Properties properties = new Properties();
        //图片边框,合法值yes,no,默认值yes
        properties.put("kaptcha.border", "no");
        // 字体颜色
        properties.put("kaptcha.textproducer.font.color", "black");
        //文字间隔
        properties.put("kaptcha.textproducer.char.space", "5");
        // 图片样式
        properties.put("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
        properties.put("kaptcha.textproducer.font.names", "Arial,Courier,cmr10,宋体,楷体,微软雅黑");
        // 验证码长度默认是5,下面是设为4,备用
//        properties.put("kaptcha.textproducer.char.length", "4");
        // 验证码内容,从0123456789数字里随机生成,不写的话默认是数字加字母
        properties.put("kaptcha.textproducer.char.string", "0123456789");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

captcha配置参数

controller

package com.ph.rfwg.controller;

import java.awt.image.BufferedImage;
import java.io.IOException;

import java.util.List;
import java.util.Map;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ph.rfwg.entity.*;
import com.ph.rfwg.mapper.CaptchaMapper;
import com.ph.rfwg.service.CaptchaService;
import com.ph.rfwg.service.UserService;
import com.ph.rfwg.util.CommUtils;
import com.ph.rfwg.util.HttpCilentUtil;
import com.ph.rfwg.value.AjaxObj;
import com.ph.rfwg.value.DataProperty;
import com.ph.rfwg.value.ReturnValCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.web.bind.annotation.*;

import com.alibaba.fastjson.JSON;


@CrossOrigin(allowCredentials = "true", origins = "*", maxAge = 3600)
@RestController
@Api(tags = "登录")
public class UserController {

	@Value("${urlLogin}")
	private String urlLogin;

	@Autowired
	private CaptchaService captchaService;

	@Value("${urlListUserdomsNolayers}")
	private String urlListUserdomsNolayers;

	@Autowired
	private CaptchaMapper captchaMapper;

	private final static Logger logger = LoggerFactory.getLogger(UserController.class);

	/**
	 * 登录
	 * @param userName
	 * @param password
	 * @param sysId
	 * @param request
	 * @return
	 * @throws Exception
	 */
	@ApiOperation(value = "登录")
	@RequestMapping(value={"", "/", "/login", "/login.do"}, method={RequestMethod.POST, RequestMethod.GET})
	public AjaxObj login(@ApiParam(name = "userName",value = "用户名")String userName,
						 @ApiParam(name = "password",value = "密码")String password,
						 @ApiParam(name = "sysId",value = "系统id")String sysId,
						 @ApiParam(name = "uuid",value = "验证码id")@RequestParam(value = "uuid",required=true) String uuid,
						 @ApiParam(name = "yzmCode",value = "验证码字符串")@RequestParam(value = "yzmCode",required=true) String yzmCode,
//						 @ApiParam(name = "type",value = "区分web和app")@RequestParam(value = "type",required=false) String type,

						 HttpServletRequest request) throws Exception {
		HttpSession session = request.getSession();

//		if(!CommUtils.isEmpty(type)){
//			if(type.equals("1") && CommUtils.isEmpty(uuid) || CommUtils.isEmpty(yzmCode)){
//				return new AjaxObj(ReturnValCode.RETURN_VALUE_CODES_FAIL, "请输入验证码","error");
//			}
//		}

		if(CommUtils.isEmpty(uuid) || CommUtils.isEmpty(yzmCode)){
			return new AjaxObj(ReturnValCode.RETURN_VALUE_CODES_FAIL, "请输入验证码","error");
		}

		boolean validateOverTime = captchaService.validateOverTime(uuid, yzmCode);
		if(validateOverTime && !CommUtils.isEmpty(uuid) && !CommUtils.isEmpty(yzmCode)){
			return new AjaxObj(ReturnValCode.RETURN_VALUE_CODES_FAIL, "验证码已过期","error");
		}


		boolean validate = captchaService.validate(uuid, yzmCode);
		if(!validate && !CommUtils.isEmpty(uuid) && !CommUtils.isEmpty(yzmCode)){
			return new AjaxObj(ReturnValCode.RETURN_VALUE_CODES_FAIL, "验证码错误","error");
		}

		// 根据输入的验证码和数据库中的验证码相比较,如果输入正确则把数据库中的删掉
		if(!CommUtils.isEmpty(uuid)){
			CaptchaEntity captchaEntity = captchaMapper.selectOne(new QueryWrapper<CaptchaEntity>().eq("uuid", uuid));
			String pgCode = captchaEntity.getCode();
			if(!CommUtils.isEmpty(pgCode) && yzmCode.equals(pgCode)){
				captchaMapper.deleteById(uuid);
			}
		}


		String result = HttpCilentUtil.getRemoteDatas(urlLogin, "userName", userName, "password", password, "sysId", sysId);
		AjaxObj ajaxObj = JSON.parseObject(result, AjaxObj.class);
		int code = ajaxObj.getCode();

		logger.info("result = " + result);
		
		if(code>=0) {
			// 调用方法存储session
			User user = result2Session(result);
			session.setAttribute(DataProperty.LOGIN_USER, user);
			User attribute = (User) session.getAttribute(DataProperty.LOGIN_USER);
			/*System.err.println(session.getAttribute(DataProperty.LOGIN_USER)+"111111111111111111111");
			System.err.println(attribute+"22222222222222222");*/
			session.setAttribute(DataProperty.LOGIN_USER_ROLE, user.getRoles().get(0).getName());
			return new AjaxObj(ReturnValCode.RTN_VAL_CODE_SUCCESS, "登录成功", user);
		} else {
			return new AjaxObj(ReturnValCode.RETURN_VALUE_CODES_FAIL, "用户名或密码错误");	
		}
	}

	@ApiOperation(value = "登陆成功")
	@RequestMapping(value="/loginSuccess", method={RequestMethod.POST, RequestMethod.GET})
	public AjaxObj loginSuccess(@ApiParam(name = "token",value = "使用login.do返回的token值调用该接口")String token,
								@ApiParam(name = "sysId",value = "验证码")String sysId, HttpServletRequest request, HttpSession session) throws IOException, Exception {



	@GetMapping("captcha.jpg")
	public void captcha(HttpServletResponse response, @RequestParam(value = "uuid",required = true) String uuid)throws IOException {
		response.setHeader("Cache-Control", "no-store, no-cache");
		response.setContentType("image/jpeg");

		//获取图片验证码
		BufferedImage image = captchaService.getCaptcha(uuid);

		ServletOutputStream out = response.getOutputStream();
		ImageIO.write(image, "jpg", out);
		IOUtils.closeQuietly(out);
	}
}

service

package com.ph.rfwg.service;

import java.awt.image.BufferedImage;

public interface CaptchaService {

    BufferedImage getCaptcha(String uuid);

    // 验证验证码是否正确
    boolean validate(String uuid, String code);

    // 验证验证码是否过期
    boolean validateOverTime(String uuid, String code);
}

impl

package com.ph.rfwg.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.code.kaptcha.Producer;

import com.ph.rfwg.entity.CaptchaEntity;
import com.ph.rfwg.mapper.CaptchaMapper;
import com.ph.rfwg.service.CaptchaService;
import com.ph.rfwg.util.CommUtils;
import com.ph.rfwg.util.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


import java.awt.image.BufferedImage;
import java.util.Date;
import java.util.List;

@Service
public class CaptchaServiceImpl implements CaptchaService {

    @Autowired
    private Producer producer;
    @Autowired
    private CaptchaMapper captchaMapper;

    @Override
    public BufferedImage getCaptcha(String uuid) {

        List<CaptchaEntity> captchaEntities = captchaMapper.selectList(new QueryWrapper<CaptchaEntity>());
        if (captchaEntities.size()>0) {
            captchaEntities.forEach(captchaEntity1 -> {
                if (captchaEntity1.getExpireTime().getTime() < System.currentTimeMillis()) {
                    captchaMapper.deleteById(captchaEntity1.getUuid());
                }
            });
        }

        //生成文字验证码
        String code = producer.createText();

        CaptchaEntity captchaEntity = new CaptchaEntity();
        captchaEntity.setUuid(uuid);
        captchaEntity.setCode(code);
        // 1分钟后过期
        captchaEntity.setExpireTime(DateUtils.addDateMinutes(new Date(), 1));
        captchaMapper.insert(captchaEntity);

        return producer.createImage(code);


    }

    //验证码校验 在登录的时候判断
    @Override
    public boolean validate(String uuid, String code) {
        // 验证码错误
        CaptchaEntity captchaEntity = captchaMapper.selectOne(new QueryWrapper<CaptchaEntity>().eq("uuid", uuid));
        if(captchaEntity == null){
            return false;
        }

        //删除验证码
//        captchaMapper.deleteById(uuid);
//        List<CaptchaEntity> captchaEntities = captchaMapper.selectList(new QueryWrapper<CaptchaEntity>());
//        if (captchaEntities.size()>0) {
//            captchaEntities.forEach(captchaEntity1 -> {
//                if (captchaEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
//                    captchaMapper.deleteById(captchaEntity1.getUuid());
//                }
//            });
//        }

        if(captchaEntity.getCode().equalsIgnoreCase(code) && captchaEntity.getExpireTime().getTime() >= System.currentTimeMillis()){
            return true;
        }


        return false;
    }

    //验证码校验 在登录的时候判断
    @Override
    public boolean validateOverTime(String uuid, String code) {
        // 验证码过期
        CaptchaEntity captchaEntity = captchaMapper.selectOne(new QueryWrapper<CaptchaEntity>().eq("uuid", uuid));
        if(!CommUtils.isEmpty(captchaEntity)){
            if(!CommUtils.isEmpty(captchaEntity.getCode()) && captchaEntity.getExpireTime().getTime() < System.currentTimeMillis()){
                return true;
            }
        }else{
            return true;
        }
        return false;
    }
}

util

package com.ph.rfwg.value;

import java.io.Serializable;

/**
 * 消息对象
 *
 * @author fiver
 *
 */
public class AjaxObj implements Serializable {

	/**
	 *
	 */
	private static final long serialVersionUID = 4806448810229890854L;

	private int code;
	private String msg;
	private String token;
	private Object data;
	private Boolean flag;

	public AjaxObj() {

	}

	public Boolean getFlag() {
		return flag;
	}

	public void setFlag(Boolean flag) {
		this.flag = flag;
	}

	public AjaxObj(int code, String msg) {
		this.code = code;
		this.msg = msg;
	}

	public AjaxObj(int code, String msg, Object data) {
		this.code = code;
		this.msg = msg;
		this.data = data;
	}

	public AjaxObj(int code, String msg, Object data, Boolean flag) {
		this.code = code;
		this.msg = msg;
		this.data = data;
		this.flag = flag;
	}

	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public String getToken() {
		return token;
	}

	public void setToken(String token) {
		this.token = token;
	}

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}
}

/**
 * Copyright (c) 2016-2019 人人开源 All rights reserved.
 *
 * https://www.renren.io
 *
 * 版权所有,侵权必究!
 */

package com.ph.rfwg.util;

import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 日期处理
 *
 * @author Mark sunlightcs@gmail.com
 */
public class DateUtils {
	/** 时间格式(yyyy-MM-dd) */
	public final static String DATE_PATTERN = "yyyy-MM-dd";
	/** 时间格式(yyyy-MM-dd HH:mm:ss) */
	public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";

    /**
     * 日期格式化 日期格式为:yyyy-MM-dd
     * @param date  日期
     * @return  返回yyyy-MM-dd格式日期
     */
	public static String format(Date date) {
        return format(date, DATE_PATTERN);
    }

    /**
     * 日期格式化 日期格式为:yyyy-MM-dd
     * @param date  日期
     * @param pattern  格式,如:DateUtils.DATE_TIME_PATTERN
     * @return  返回yyyy-MM-dd格式日期
     */
    public static String format(Date date, String pattern) {
        if(date != null){
            SimpleDateFormat df = new SimpleDateFormat(pattern);
            return df.format(date);
        }
        return null;
    }

    /**
     * 字符串转换成日期
     * @param strDate 日期字符串
     * @param pattern 日期的格式,如:DateUtils.DATE_TIME_PATTERN
     */
    public static Date stringToDate(String strDate, String pattern) {
        if (StringUtils.isBlank(strDate)){
            return null;
        }

        DateTimeFormatter fmt = DateTimeFormat.forPattern(pattern);
        return fmt.parseLocalDateTime(strDate).toDate();
    }

    /**
     * 根据周数,获取开始日期、结束日期
     * @param week  周期  0本周,-1上周,-2上上周,1下周,2下下周
     * @return  返回date[0]开始日期、date[1]结束日期
     */
    public static Date[] getWeekStartAndEnd(int week) {
        DateTime dateTime = new DateTime();
        LocalDate date = new LocalDate(dateTime.plusWeeks(week));

        date = date.dayOfWeek().withMinimumValue();
        Date beginDate = date.toDate();
        Date endDate = date.plusDays(6).toDate();
        return new Date[]{beginDate, endDate};
    }

    /**
     * 对日期的【秒】进行加/减
     *
     * @param date 日期
     * @param seconds 秒数,负数为减
     * @return 加/减几秒后的日期
     */
    public static Date addDateSeconds(Date date, int seconds) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusSeconds(seconds).toDate();
    }

    /**
     * 对日期的【分钟】进行加/减
     *
     * @param date 日期
     * @param minutes 分钟数,负数为减
     * @return 加/减几分钟后的日期
     */
    public static Date addDateMinutes(Date date, int minutes) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusMinutes(minutes).toDate();
    }

    /**
     * 对日期的【小时】进行加/减
     *
     * @param date 日期
     * @param hours 小时数,负数为减
     * @return 加/减几小时后的日期
     */
    public static Date addDateHours(Date date, int hours) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusHours(hours).toDate();
    }

    /**
     * 对日期的【天】进行加/减
     *
     * @param date 日期
     * @param days 天数,负数为减
     * @return 加/减几天后的日期
     */
    public static Date addDateDays(Date date, int days) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusDays(days).toDate();
    }

    /**
     * 对日期的【周】进行加/减
     *
     * @param date 日期
     * @param weeks 周数,负数为减
     * @return 加/减几周后的日期
     */
    public static Date addDateWeeks(Date date, int weeks) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusWeeks(weeks).toDate();
    }

    /**
     * 对日期的【月】进行加/减
     *
     * @param date 日期
     * @param months 月数,负数为减
     * @return 加/减几月后的日期
     */
    public static Date addDateMonths(Date date, int months) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusMonths(months).toDate();
    }

    /**
     * 对日期的【年】进行加/减
     *
     * @param date 日期
     * @param years 年数,负数为减
     * @return 加/减几年后的日期
     */
    public static Date addDateYears(Date date, int years) {
        DateTime dateTime = new DateTime(date);
        return dateTime.plusYears(years).toDate();
    }
}