vue+springboot实现图形验证码Kaptcha

1、前端

form使用了element-ui的组件,主要还是看img标签,src绑定了form.imgCodeUrl数据,点击图片时触发refreshCode更新图片验证码。

html 复制代码
	  <el-form-item prop="verificationCode" label="验证码" style="text-align: left;">
		  <el-input v-model="form.verificationCode" placeholder="请输入验证码" style="width: 120px;vertical-align: top;"></el-input>
		  <img id="img" alt="验证码" @click="refreshCode" :src="form.imgCodeUrl" style="padding-left: 12px;"/>
	  </el-form-item>
js 复制代码
    data() {
      return {
        form: {
          username: '',
          password: '',
		  verificationCode:'',
		  imgCodeUrl:this.$verificationCodeUrl,
        },
    ...
	...
  refreshCode(){
	  this.form.imgCodeUrl=this.$verificationCodeUrl+"?d="+new Date().getTime();
  }	

上面refreshCode更新图片验证码的原理,其实就是在原网址后添加随机的参数来改变form.imgCodeUrl网址,实现img标签自动刷新请求。

  • this.$verificationCodeUrl是请求的网址,这里设置成全局常量写在了main.js中
js 复制代码
//验证码地址
Vue.prototype.$verificationCodeUrl="http://127.0.0.1/api/createImageCode";

2、后端实现生成createImageCode验证码图片

使用kaptcha生成图片验证码,添加如下依赖

html 复制代码
<dependency>
     <groupId>com.github.penggle</groupId>
     <artifactId>kaptcha</artifactId>
     <version>2.3.2</version>
</dependency>

配置kaptcha图片生成规则

java 复制代码
...
import java.util.Properties;
 
@Configuration
public class KaptchaConfig {
    @Bean
    public DefaultKaptcha getDefaultKaptcha() {
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        Properties properties = new Properties();
        // 图片宽
        properties.setProperty("kaptcha.image.width", "130");
        // 图片高
        properties.setProperty("kaptcha.image.height", "50");
        // 图片边框
        properties.setProperty("kaptcha.border", "yes");
        // 边框颜色
        properties.setProperty("kaptcha.border.color", "105,179,90");
        // 字体颜色
        properties.setProperty("kaptcha.textproducer.font.color", "blue");
        // 字体大小
        properties.setProperty("kaptcha.textproducer.font.size", "40");
        // session key
        properties.setProperty("kaptcha.session.key", "imageCode");
        // 验证码长度
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        // 字体
        properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
        Config config = new Config(properties);
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

生成验证码的图片通过response.getOutputStream流发送到前端,而验证码保存到session域中,等待用户登录表单提交时进行校验匹对

java 复制代码
	...
	@Autowired
	private DefaultKaptcha defaultKaptcha;
	...
	//生成图片验证码
	@GetMapping("/createImageCode")
	public void createImageCode(HttpServletRequest request,HttpServletResponse response) {
		response.setHeader("Cache-Control", "no-store, no-cache");
		response.setContentType("image/jpeg");
		// 生成文字验证码
		String text = defaultKaptcha.createText();
		// 生成图片验证码
		BufferedImage image = defaultKaptcha.createImage(text);
		//保存到session域
		HttpSession session = request.getSession();
		session.setAttribute("imageCode",text);
		ServletOutputStream out = null;
		try {
			//响应输出图片流
			out = response.getOutputStream();
			ImageIO.write(image, "jpg", out);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				out.flush();
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

3、前端提交表单

代码有点长,其实是嵌套了三层,第一层if (valid)判断表单校验(填入的数据是否符合规则),第二层this. h t t p . g e t ( " / v e r i f y I m a g e C o d e " 提交验证码判断验证码是否正确,第三层 t h i s . http.get("/verifyImageCode"提交验证码判断验证码是否正确,第三层this. http.get("/verifyImageCode"提交验证码判断验证码是否正确,第三层this.http.post("/user/login"最后判断用户名和密码是否正确,三层逻辑都通过才保存用户登录信息。

js 复制代码
      submitForm() {
        this.$refs.form.validate(valid => {
		  //表单校验通过
          if (valid) {
			//先判断图形验证码
			this.$http.get("/verifyImageCode",{params:{verificationCode:this.form.verificationCode}})
			.then((response)=>{
				//图形验证码成功
				if(response.data.code === 0){
					//判断用户名和密码
					this.$http.post("/user/login",{
						username:this.form.username,
						password:this.form.password
					})
					.then((response)=>{
						if(response.data.code === 0){//登录成功
							this.$message({
							  message: '登录成功',
							  type: 'success',
							});
							localStorage.setItem("user",JSON.stringify(response.data.data));//保存用户信息
							this.$router.push("/");
						}
						else{
							this.$message.error("用户名或密码错误,请重试!!!");
						}
					})
					.catch((error)=>{
						//未接受到response的网络传输等错误
						console.log(error);
					});
				}else{//图形验证码错误
					this.$message.error(response.data.data.message);
					return;
				}
			})
			.catch((error)=>{
				//未接受到response的网络传输等错误
				console.log(error);
				return;
			});	
          } else {
            this.$message.error('表单填写错误,请检查');
            return false;
          }
        });
      },

4、后端/verifyImageCode验证码匹对

CommonResult是自定义的返回数据结果类,可以搜我以前文章。前端传来的验证码与session域中的匹对。

java 复制代码
	//匹对图片验证码
	@GetMapping("/verifyImageCode")
	public CommonResult<Object> verifyImageCode(String verificationCode,HttpServletRequest request) {
		HttpSession session = request.getSession();
		if(session.getAttribute("imageCode").equals(verificationCode)) {
			return CommonResult.success();
		}else {
			return CommonResult.failed(ErrorCode.IMG_CODE_VERIFY_ERROR.getCode(), Message.createMessage(ErrorCode.IMG_CODE_VERIFY_ERROR.getMessage()));
		}
	}

5、bug

测试时,验证码图片能够生成显示,并能刷新。但提交表单时,对/verifyImageCode的请求返回错误码500。

首先打印发现session域的值为null。了解到原来session会话是有唯一的session id。打印session id,发现每次请求session id的不一样。session id其实是存在在cookie里的。

所以前端发送请求时要携带cookie证书,vue配置

js 复制代码
//携带证书 session id
axios.defaults.withCredentials = true

后端的跨域配置也要允许证书通行

java 复制代码
//配置跨域过滤器CorsFilter
@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        //允许证书
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        List<String> list = Arrays.asList("*");
        corsConfiguration.setAllowedHeaders(list);
        corsConfiguration.setAllowedMethods(list);
        corsConfiguration.setAllowedOriginPatterns(list);
        source.registerCorsConfiguration("/**", corsConfiguration);
        CorsFilter corsFilter = new CorsFilter(source);
        return corsFilter;
    }
}

还没完,请求还是返回500。浏览器调试器显示如下

请求头中已经带cookie,然而响应头又set-cookie了,也就是cookie里的session id又被重置,为什么这样子?可能是感叹号报错的原因:set-cookie默认属性是"SameSite=Lax,"。参考网上文章,我们修改后端配置application.properties:

sh 复制代码
#获取同一个session id的cookie设置
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=none

再运行起来,set-cookie和感动号消失,调试通过。

参考文章:vue+springboot实现登录验证码

相关推荐
zzz_236815 分钟前
【Spring】面试突击系列(三):Spring Web MVC 深度解析
前端·spring·面试
colofullove17 分钟前
小说上传中心与异步处理进度展示设计
前端
Marst Code32 分钟前
⚙️ 2026 年推荐技术方案
前端
qq_3660862240 分钟前
测试接口传参数时,放在Header和Body中后台接收参数的区别
java·开发语言·前端
biubiubiu070643 分钟前
SpringBoot 3.5.4 整合Quartz 定时任务
java·spring boot·spring
whatever who cares1 小时前
Vue3中vue文件和composables的分工
前端·javascript·vue.js
袋鼠云数栈UED团队1 小时前
基于 superpowers 实现复杂前端改造
前端
袋鼠云数栈前端1 小时前
基于 superpowers 实现复杂前端改造
前端·ai
sugar__salt1 小时前
LLM服务HTTP接口实战:前端HTTP请求全解与项目落地
前端·网络协议·http
薛先生_0991 小时前
vue-编程式跳转-基本跳转
前端·javascript·vue.js