问题
最近在给项目开发登录注册功能,在登录的图片验证码上出现了问题,报了空指针异常。
逻辑是这样的:在登录页面,前端通过验证码接口先请求验证码,然后输入用户名密码和验证码之后,请求登录接口。在验证码接口中我用session保存验证码,在登录接口中我从session取出验证码进行校验。 postman测试没有问题,但是前端在测试时报了空指针异常。
于是我查看后端日志,发现请求验证码时返回给前端的sessionId和前端发送登录请求时所携带的sessionId不一致,所以后端拿到的sessionId是没有对应的验证码的,于是报了空指针异常。
通过查阅网上的各种信息,我们了解到这是因为跨域问题导致的,跨域我是配置过的,按照网上的办法前后端都配置了相应的跨域设置,但是依然没有解决问题。
具体可以参考链接试试,也是有人成功的:https://www.cnblogs.com/jpfss/p/9081570.html
解决办法
这个问题困扰了我们好几天,期间也是尝试过很多方法,无论是前端的跨域配置,还是后端的跨域配置都试过了,但是问题还是没有解决,不知道是不是因为我将shiro整合JWT的时候关闭了shiro的session导致的,并不清楚,我把shiro的session打开后也没有解决问题。
既然跨域这条路走不通,我们就换条路走,下面给出两种解决方法
方法一:不使用session
不使用session来实现验证码的验证,将验证码转存到redis或者数据库中,前端在通过验证码接口获取验证码时除了返回验证码本身,还要返回验证码的id,那么前端在登录的时候也要把验证码id和用户输入的验证码传给后端进行验证。这个方法并没有使用session,所以并不重点展开,我主要介绍第二种方法。
方法二:取消验证码接口,只保留登录接口来实现获取图片验证码和登录功能
使用session时对同一个页面多次刷新是不会改变sessionid的
既然sessionid不会改变,也就解决了我们之前的问题,那么新的问题是如何只通过一个接口实现即能返回验证码,又能实现登录功能呢。
我在开发登录接口时,会在登录失败时返回code为400的错误信息,在登录成功时返回code为200,也就是说只要前端发送的请求中用户名,密码,验证码任意一个为空,后端都会返回错误信息,那么只要在返回错误信息的同时将验证码的base64格式传给前端就可以了。
@PostMapping("login")
public Result login(@RequestBody User loginUser , HttpServletResponse response, HttpServletRequest request) throws IOException {
//检验loginUser元素是否完整,不完整则返回错误信息
String message = new Judge().judgeUser(loginUser);
if(message != "none"){
//这是我封装的判断方法,如果message不为none的话,说明登录信息不完整,此时就可以返回验证码了
//生成验证码
String code = VerifyCodeUtils.generateVerifyCode(4);
//验证码放入session
request.getSession().setAttribute("code",code);
System.out.println("getImageSession:"+request.getSession().getId());
//验证码存入图片
ByteArrayOutputStream bs = new ByteArrayOutputStream();
VerifyCodeUtils.outputImage(110,30,bs,code);
//将图片转成二进制并进行Base64编码
String imgsrc = Base64.byteArrayToBase64(bs.toByteArray());
System.out.println(imgsrc);
return Result.fail(message,"data:image/png;base64,"+imgsrc);
}
//验证图片验证码
String codes = (String)request.getSession().getAttribute("code");
System.out.println("loginSession:"+request.getSession().getId());
System.out.println("codes:"+codes);
System.out.println("loginUser.getCode():"+loginUser.getCode());
try {
if (!codes.equalsIgnoreCase(loginUser.getCode())) {
//equalsIgnoreCase忽略大小写
return Result.fail("验证码错误");
}
}catch (NullPointerException e){
e.printStackTrace();
return Result.fail("空指针异常");
}
//后面就是基本的登录逻辑了,和本问题无关,所以就不贴出来了。
}
那么前端要做的事也就很简单了,在用户访问登录界面时发送一个空请求给后端,从而拿到验证码图片的base64格式,再加上img src标签后就可以展示图片了,用户填写完整的登录信息后再给后端发送请求就能实现登录了。
附上前端代码:
一、在点击用户名输入框的时候 发送一个请求获取验证码。
$('.inputName').one("focus", function () {
$.ajax({
url: 'http://*.*.*.*/user/login',
type: 'POST',
contentType: "application/json",
dataType: 'json',//json 返回值类型
data: JSON.stringify({
username: "" + rt.value,
password: "" + uu.value,
code: "" + op.value
}),//转化为json字符串
xhrFields: {
withCredentials: true
},
success: function (datas) {
$('.pp').attr("src", datas.data);
}
})
})
二、点击登录的时候 将信息提交给服务的接口
tt[3].onclick = function () {
ru.style.display = 'block';
$.ajax({
url: 'http://*.*.*.*/user/login',
type: 'POST',
contentType: "application/json",
dataType: 'json',//json 返回值类型
data: JSON.stringify({
username: "" + rt.value,
password: "" + uu.value,
code: "" + op.value
}),//转化为json字符串
xhrFields: {
withCredentials: true
},
success: function (datas) {
console.log(datas);
if (datas.code == '200') {
ru.style.display = 'none';
ty.innerHTML = datas.msg;
ty.style.display = 'block';
tt[3].setAttribute("class", "sub");
alert('好兄弟即将起航');
var seconds = 3;
setInterval(function () {
seconds--;
if (seconds == 0) {
window.location.href = "../main_page.html";
}
}, 1000);
}
else {
ru.style.display = 'none';
ty.innerHTML = datas.msg;
ty.style.display = 'block';
}
}
});
}
最后自然是成功解决了验证码报空指针的问题了