1、前端
form使用了element-ui的组件,主要还是看img标签,src绑定了form.imgCodeUrl数据,点击图片时触发refreshCode更新图片验证码。
<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>
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中
//验证码地址
Vue.prototype.$verificationCodeUrl="http://127.0.0.1/api/createImageCode";
2、后端实现生成createImageCode验证码图片
使用kaptcha生成图片验证码,添加如下依赖
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
配置kaptcha图片生成规则
...
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域中,等待用户登录表单提交时进行校验匹对
...
@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"最后判断用户名和密码是否正确,三层逻辑都通过才保存用户登录信息。
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域中的匹对。
//匹对图片验证码
@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配置
//携带证书 session id
axios.defaults.withCredentials = true
后端的跨域配置也要允许证书通行
//配置跨域过滤器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:
#获取同一个session id的cookie设置
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=none
再运行起来,set-cookie和感动号消失,调试通过。
参考文章:vue+springboot实现登录验证码