获取登录验证码失败及前后端不同域导致session丢失问题分析记录
时间:2021-12-22 作者:blayn
前言
前两周在把兄弟公司的几个服务部署到我们公司测试环境服务器的时候又遇到了不少问题,因为是前后端分离的项目,所以这次也同样遇到了跨域问题,解决方式也跟上一回的不一样,这里就再来分析记录一下。
登录验证码在本地获取正常但部署到服务器报空指针异常问题
问题描述:
登录页面的验证码,在本地运行项目的时候可以正常获取和显示,但部署到测试环境服务器上验证码图片却无法显示出来,如下图所示: 追了一下后端服务器日志,发现后端报空指针异常,报错信息如下所示:域名PointerException: null at 域名域名ersion(域名:1264) at 域名域名FontConfigFile(域名:219) at 域名域名(域名:107) at 域名域名teFontConfiguration(域名:774) at 域名ontManager$域名(域名:431) at 域名域名ivileged(Native Method) at 域名ontManager.(域名:376) at 域名ntManager.(域名:35) at 域名ontManager.(域名:57) at 域名域名nstance0(Native Method) at 域名域名nstance(域名:62) at 域名域名nstance(域名:45) at 域名域名nstance(域名:423) at 域名域名nstance(域名:442) at 域名ManagerFactory$域名(域名:83) at 域名域名ivileged(Native Method) at 域名域名nstance(域名:74) at 域名.getFont2D(域名:491) at 域名.access$000(域名:224) at 域名$域名ont2D(域名:228) at 域名域名ont2D(域名:180) at 域名域名kFontInfo(域名:669) at 域名域名ontInfo(域名:830) at 域名.域名String(域名:50) at 域名域名String(域名:2928) at 域名域名teimage(域名:77) ......跟踪代码后发现,在绘制验证码图片时使用了Graphics画布,Graphics在绘制验证码字符时,需要先设定字体,如下所示:
域名ont(new Font("DejaVu Sans", 域名ER_BASELINE, 18));这就是问题点所在,在windows系统上,运行这行代码是完全没问题的,因为windows系统自带这种字体,可是我们测试环境服务器上运行的linux镜像系统并没有这种字体,所以就报空指针异常了。
解决办法:构建一个带有指定字体的基础镜像
构建了一个自带“DejaVu Sans”字体的openjdk镜像,使用该镜像来运行我们的程序,就能正常获取和显示登录验证码了,如下图所示: 注意:关于构建这个镜像,有两种方式可以选择,一种是创建一个跟原来一样没有“DejaVu Sans”字体的基础openjdk镜像,然后再执行添加相应字体的命令,对应的Dockerfile编写方式如下:FROM openjdk:8-jdk-alpine ARG JAR_FILE ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY ${JAR_FILE} 域名 RUN apk add --update ttf-dejavu fontconfig EXPOSE 8086 ENTRYPOINT ["java","-jar","/域名"]使用这种方式的缺点在于每次构建镜像的时候都要花很多时间去下载我们所需要的字体,效率很低。 另一种方式则是先在我们自己公司用于存放镜像的服务器上专门构建一个带有“DejaVu Sans”字体的openjdk镜像,然后用它来运行我们的项目,这样一来能够满足功能需求,二来是直接从我们自己的镜像服务器上拉取镜像、速度也会快很多。
前后端不同域导致session丢失问题
问题描述:
输入的登录验证码没有错,可是却一直报:验证码错误,登录失败! 在后端打了一些日志后,发现如下信息: 这里我们登录验证码的完整校验逻辑是这样的:用户请求登录页面时,调用后端生成验证码的接口,后端生成一个验证码返回给前端并将验证码字符串缓存在浏览器session中,用户输入登录信息后,后端根据用户输入的验证码与用户缓存在浏览器session中的验证码字符串进行比对,若一致则校验通过,否则校验失败。 根据日志可以判断,这里session中缓存的验证码丢失了,导致后端获取不到。 按F12键使用开发者工具查看获取验证码的请求响应,可以看到如下信息: 根据上图可以得知,后端尝试将验证码缓存到浏览器session中时,由于前后端不同域导致该操作被拦截了。解决办法:通过前端nginx设置同域代理转发请求
在nginx配置文件中添加如下配置:location /hello-sso-service { proxy_pass http://域名.xxx:8080/hello-sso-service; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }将与hello-sso-service相关的接口请求代理转发到对应的后端服务接口,这样缓存验证码到session的操作就不会被拦截了。 更新代码配置后,重新进行登录,后端服务就能从session中获取到相应的验证码了,如下图所示: 关于前后端不同域,这次我其实还遇到了另外一个小问题:在退出登录时,需要跳转到前端登录页面并且在url中携带一些参数请求后端一个接口,也就是说,跳转的地址组成结构为“前端ip:端口+对应的后端接口”。 针对这个问题,我也是使用nginx进行了如下代理:
location /v1 { proxy_pass http://域名.xxx:8081/v1; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }
总结
这段时间在部署这些新服务的过程中,学习了不少前后端服务部署的知识,尤其对于前后端跨域问题有了很深刻的体会。所谓“同域”,就是“协议+主机+端口”都相同,反之则称之为“跨域”。
解决“跨域”问题较常见的办法就是使用nginx进行反向代理设置,如上文中提到的两个例子,通过代理使前后端“同域”,就可以解决相应的session设置失败及接口请求不通等问题。