原 Spring boot 集成 Kaptcha 实现前后端分离验证码功能

https://blog.csdn.net/a78270528/article/details/78603199

简述:
在web开发中验证码是一个常见的功能。不论是防止机器人还是爬虫都有一定的作用,我们可以自己编写验证码的工具类,也可以使用比较方便的验证码工具。

本文使用Spring boot 集成 Kaptcha 实现前后端分离验证码功能,这里为什么强调前后端分离,拿登陆功能为例,在登陆我们要请求后台返回一张验证码图片,然后输入用户名密码加上验证码,再次提交给后台,如果不是前后端分离,可轻松的从session中获取用户信息;现在前后端分离了,session失效了,第二次请求认为是一个新的用户,这问题就产生了,我们不能确定当前传过的这个验证码是否是第一次给这个用户的,本文就是要解决这个问题,在前后端分离项目中实现验证码校验功能。

解决思路:
1、前端请求后端,后端返回验证码图片和TOKEN。
2、后端将TOKEN和验证码记录在数据库中。
3、前端请求登陆,传入TOKEN和验证码及相关登陆信息,让后端匹配验证码。
4、后端按TOKEN查出验证码,对刚传入的验证码进行匹配。
5、匹配成功,登陆成功;匹配失败,返回异常信息。

具体实现:
一、首先,搭建一个Spring boot 工程,在我博客中可以找到具体的搭建文章。
二、导入kaptcha的依赖

<dependency>  
    <groupId>com.github.penggle</groupId>  
    <artifactId>kaptcha</artifactId>  
    <version>2.3.2</version>  
</dependency>
三、配置kaptcha
/**
 * 生成验证码配置
 *
 * @author shanming.yang
 * @email a78270528@126.com
 * @date 2017-04-20 19:22
 */
@Configuration
public class KaptchaConfig {

    @Bean
    public DefaultKaptcha producer() {
        Properties properties = new Properties();
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.textproducer.font.color", "black");
        properties.put("kaptcha.textproducer.char.space", "10");
        properties.put("kaptcha.textproducer.char.length","4");
        properties.put("kaptcha.image.height","34");
        properties.put("kaptcha.textproducer.font.size","25");
        
        properties.put("kaptcha.noise.impl","com.google.code.kaptcha.impl.NoNoise");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}
四、后端创建请求验证码接口
1、请求验证码接口

    @ResponseBody
    @RequestMapping(value = "/captcha", method = RequestMethod.POST)
    public Map<String, Object> captcha(HttpServletResponse response) throws ServletException, IOException {

        // 生成文字验证码

        String text = producer.createText();
        // 生成图片验证码
        ByteArrayOutputStream outputStream = null; 
        BufferedImage image = producer.createImage(text);
        
        outputStream = new ByteArrayOutputStream();  
        ImageIO.write(image, "jpg", outputStream);  
        
        // 对字节数组Base64编码  
        BASE64Encoder encoder = new BASE64Encoder();  
        
        // 生成captcha的token
        Map<String, Object> map = captchaService.createToken(text);
        map.put("img", encoder.encode(outputStream.toByteArray()));

        return map;
    }
2、登陆接口
   /**
     * 登录
     */
    @IgnoreAuth
    @PostMapping("login")
    @ApiOperation(value = "登录",notes = "登录")
    @ApiImplicitParams({
        @ApiImplicitParam(paramType = "query", dataType="string", name = "account", value = "账号", required = true),
        @ApiImplicitParam(paramType = "query", dataType="string", name = "password", value = "密码", required = true),
        @ApiImplicitParam(paramType = "query", dataType="string", name = "captcha", value = "验证码", required = true)
    })
    public R login(HttpServletRequest request,String account, String password,String captcha,String ctoken){
        Assert.isBlank(account, "账号不能为空");
        Assert.isBlank(password, "密码不能为空");
        Assert.isBlank(password, "验证码不能为空");
        String token = request.getHeader("token");
        CaptchaEntity captchaEntity  = captchaService.queryByToken(ctoken);

    if(!captcha.equalsIgnoreCase(captchaEntity.getCaptcha())){
        return R.error("验证码不正确");
    }
        
        //用户登录
        String userId = userService.login(account, password);

        //生成token
        Map<String, Object> map = tokenService.createToken(userId);

        return R.ok(map);
    }
3、具体实现
@Service("captchaService")
public class CaptchaServiceImpl implements CaptchaService {
    @Autowired
    private CaptchaDao captchaDao;
    //1小时后过期
    private final static int EXPIRE = 3600 * 1;

    @Override
    public CaptchaEntity queryByCaptcha(String captcha) {
        return captchaDao.queryByCaptcha(captcha);
    }

    @Override
    public CaptchaEntity queryByToken(String token) {
        return captchaDao.queryByToken(token);
    }

    @Override
    public void save(CaptchaEntity token){
        captchaDao.save(token);
    }
    
    @Override
    public void update(CaptchaEntity token){
        captchaDao.update(token);
    }
    
    @Override
    public boolean isExpired(Date expireTime){
        Date d=new Date();
        return d.getTime()>expireTime.getTime()?true:false;
    }

    @Override
    public Map<String, Object> createToken(String captcha) {
        //生成一个token
        String token = UUID.randomUUID().toString();
        //当前时间
        Date now = new Date();

        //过期时间
        Date expireTime = new Date(now.getTime() + EXPIRE * 1000);

        //判断是否生成过token
        CaptchaEntity tokenEntity = queryByCaptcha(captcha);
        if(tokenEntity == null){
            tokenEntity = new CaptchaEntity();
            tokenEntity.setCaptcha(captcha);
            tokenEntity.setToken(token);
            tokenEntity.setUpdateTime(now);
            tokenEntity.setExpireTime(expireTime);

            //保存token
            save(tokenEntity);
        }else{
            tokenEntity.setToken(token);
            tokenEntity.setUpdateTime(now);
            tokenEntity.setExpireTime(expireTime);

            //更新token
            update(tokenEntity);
        }

        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("expire", EXPIRE);

        return map;
    }

    @Override
    public void deleteByToken(String token) {
        captchaDao.deleteByToken(token);
    }
}
4、DAO
/**
 * 验证码
 * 
 * @author shanming.yang
 * @email a78270528@126.com
 * @date 2017-11-22 15:22:07
 */
public interface CaptchaDao extends BaseDao<CaptchaEntity> {
    
    CaptchaEntity queryByCaptcha(String captcha);

    CaptchaEntity queryByToken(String token);

    void deleteByToken(String token);
    
}
5、Mapper
<mapper namespace="com.fingerprint.dao.TokenDao">

    <select id="queryByUserId" resultType="com.fingerprint.entity.TokenEntity">
        select * from u_token where user_id = #{value}
    </select>

    <select id="queryByToken" resultType="com.fingerprint.entity.TokenEntity">
        select * from u_token where token = #{value}
    </select>
     
    <insert id="save" parameterType="com.fingerprint.entity.TokenEntity">
        insert into u_token
        (
            `user_id`, 
            `token`, 
            `expire_time`, 
            `update_time`
        )
        values
        (
            #{userId}, 
            #{token}, 
            #{expireTime}, 
            #{updateTime}
        )
    </insert>
     
    <update id="update" parameterType="com.fingerprint.entity.TokenEntity">
        update u_token 
        <set>
            <if test="token != null">`token` = #{token}, </if>
            <if test="expireTime != null">`expire_time` = #{expireTime}, </if>
            <if test="updateTime != null">`update_time` = #{updateTime}</if>
        </set>
        where user_id = #{userId}
    </update>
    
     <delete id="deleteByToken">
        delete from u_token where token = #{value}
    </delete>

</mapper>
五、前端AJAX(VUE)
login: function (event) {
//alert(localStorage.getItem("ctoken"));
var data = "account="+vm.username+"&password="+vm.password+"&captcha="+vm.captcha+"&ctoken="+localStorage.getItem("ctoken");
$.ajax({
    type: "POST",
    url: basePath+"api/login",
    headers:{'token':localStorage.getItem("token")},
    data: data,
    dataType: "json",
    success: function(result){
        //alert(result.code);
        if(result.code == 0){//登录成功
            var token=result.token;
            var expire=result.expire;
            localStorage.setItem("token",token);
            parent.location.href = 'sysindex.html';
        }else{
            vm.error = true;
            vm.errorMsg = result.msg;
            
            vm.refreshCode();
        }
    }
});

function ajaxcaptcha(){
    $.ajax({
        type: "POST",
        url: basePath+"captcha",
        dataType: "json",
        success: function(result){
        //JQUERY
            //$("#captchaimg").prop('src', 'data:image/jpeg;base64,'+result.img);
            localStorage.setItem("ctoken",result.token);
            vm.src = 'data:image/jpeg;base64,'+result.img;
        }
    });
}
六、前端HTML页面
<div class="form-group has-feedback">
        <img id="captchaimg" alt="如果看不清楚,请单击图片刷新!" class="pointer" :src="src"  @click="refreshCode">
            <a href="javascript:;" @click="refreshCode">{{$t("refresh")}}</a>
         
</div>
七、注意

生成的Base64编码前要加入data:image/jpeg;base64,可通过http://imgbase64.duoshitong.com/来判断生成的Base64图片是否正确,粘入代码,如果正确,会显示对应图片,否则生成Base64错误。(感谢同事小岳岳的帮助)


到此,使用Spring boot 集成 Kaptcha 实现前后端分离验证码功能已完成。
--------------------- 
作者:二一点 
来源:CSDN 
原文:https://blog.csdn.net/a78270528/article/details/78603199 
版权声明:本文为博主原创文章,转载请附上博文链接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值