SpringBoot解决关于跨域导致sessionId不一致问题

您所在的位置:网站首页 不跨域的图片怎么做 SpringBoot解决关于跨域导致sessionId不一致问题

SpringBoot解决关于跨域导致sessionId不一致问题

2024-07-16 17:14:25| 来源: 网络整理| 查看: 265

在用谷歌的kaptcha做验证码登录校验,将后端发布到阿里云,前端是本地启动,用谷歌浏览器(版本85)访问验证码遇到了如下问题(360浏览器、microsoft edge未重现)

                                                                     

可以定位到是浏览器兼容问题。 代码是这样的:后端先用HttpServletRequest request的getSession().setAttribute将验证码存进session,请求登录的时候再用request.getSession().getAttribute来判断,然后发现请求验证码的sessionId跟请求登录的sessionId不一致,导致提示验证码一直失效。

如下为获取验证码的接口

@ApiOperation(value = "获取验证码", notes = "此接口用于获取验证码") @GetMapping("captcha.jpg") public void captcha(HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException { response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); // 生成文字验证码 String text = producer.createText(); // 生成图片验证码 BufferedImage image = producer.createImage(text); // 保存到验证码到 session System.out.println("============================="); request.getSession().setAttribute(Constants.KAPTCHA_SESSION_KEY, text); System.out.println("生成文字验证码:" + text); System.out.println("获取验证码 session:" + request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY)); System.out.println("获取验证码 request.getSession().getId():" + request.getSession().getId()); System.out.println("============================="); ServletOutputStream out = response.getOutputStream(); ImageIO.write(image, "jpg", out); IOUtils.closeQuietly(out); }

   登录的部分接口

@ApiOperation(value = "系统登录", notes = "此接口用于系统登录") @PostMapping(value = "/login") public ApiResponses login(@RequestBody LoginParam loginPARAM, HttpServletRequest request) { String username = loginPARAM.getUsername(); String password = loginPARAM.getPassword(); String captcha = loginPARAM.getCaptcha(); System.out.println("============================="); System.out.println("系统登录时 request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY):" + request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY)); System.out.println("系统登录时 request.getSession().getId():" + request.getSession().getId()); 。 。 . }

这种方式请求验证码的时候会带cookie给前端,如下所示,JSESSIONID就是后端的request.getSession().getId(),登录的时候如果设置了跨域,前端会将JSESSIONID返回给后端,后端会进行判断。但是现在的问题就是两次的sessionId不一致。所以还是要检查是否设置对了跨域

检查后端设置的跨域:

这是我的跨域配置类,需要注意的是

当allowCredentials为true时,allowedOrigins尽量不要设置为 * @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // 允许跨域访问的路径 registry.addMapping("/**") // 允许跨域访问的源 .allowedOrigins("http://服务器Ip:9528","http://服务器Ip:9001") // 允许请求方法 .allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") // 预检间隔时间 .maxAge(168000) // 允许头部设置 .allowedHeaders("*") // 是否发送cookie .allowCredentials(true); } } 前端设置的跨域

前端设置跨域主要为:axios.defaults.withCredentials = true,然后此项目前端如下

const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url withCredentials: true, // send cookies when cross-domain requests timeout: 5000 // request timeout })

寻思着,这样配置也没问题吧。

经过度娘的助攻,终于找出了问题的源头

新版的chrome,加强了防止CSRF攻击,需要设置Cookie的SameSite属性

SameSite的值可以填3个:Strict,Lax,None. 

缺省的值为Lax,而且当你设置其为空时,在新的Chrome中还是会给予默认值Lax.

3个模式的介绍 Strict

严格模式

Lax

宽松模式

None

可以在第三方环境中发送cookie 在这种模式下,必须同时启用Secure才行

似乎看到了黎明的曙光,上后端代码 @Configuration public class SpringSessionConfig { // 最新的chrome,设置null会默认成lax 但是如果设置samesite为NONE,又需要设置secure。https支持secure,http不行 @Bean public CookieSerializer httpSessionIdResolver() { DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); cookieSerializer.setUseHttpOnlyCookie(false); cookieSerializer.setSameSite("None"); cookieSerializer.setCookiePath("/"); cookieSerializer.setUseSecureCookie(true); return cookieSerializer; } }

 然后将后端继续发布到阿里云,然而的然而 还是翻车了。。。

再次寻求百度,然后发现要满足https + SameSite("None") + SecureCookie(true)

三者条件才能在高版本的谷歌浏览器访问

但是阿里云是http,那怎么办呢

还有一种解决方法,弃用通过session校验,可以引入redis来做判断

上代码:

@Autowired private RedisTemplate redisTemplate; @ApiOperation(value = "获取验证码", notes = "此接口用于获取验证码") @GetMapping("captcha.jpg") public void captcha(HttpServletResponse response, HttpServletRequest request) throws ServletException, IOException { response.setHeader("Cache-Control", "no-store, no-cache"); response.setContentType("image/jpeg"); // 生成文字验证码 String text = producer.createText(); // 生成图片验证码 BufferedImage image = producer.createImage(text); // 保存到验证码到 redis 设置1分钟过期 redisTemplate.opsForValue().set(Constants.KAPTCHA_SESSION_KEY,text,1, TimeUnit.MINUTES); ServletOutputStream out = response.getOutputStream(); ImageIO.write(image, "jpg", out); IOUtils.closeQuietly(out); } @ApiOperation(value = "系统登录", notes = "此接口用于系统登录") @PostMapping(value = "/login") public ApiResponses login(@RequestBody LoginParam loginPARAM, HttpServletRequest request) { String username = loginPARAM.getUsername(); String password = loginPARAM.getPassword(); String captcha = loginPARAM.getCaptcha(); Object kaptcha = redisTemplate.opsForValue().get(Constants.KAPTCHA_SESSION_KEY); 。 。 。 }

这样就可以解决啦

问题调整:

用如上方法写验证码会有一种问题,就是当多个用户同时请求获取验证码,其中先获取验证码的人就会失效。然后做了如下改进

我弃用了谷歌的kaptcha,重写了验证码。给redis set值的时候同时加上一个token,登录的时候需要返回token来验证

续上部分代码

/** * 生成验证码 * * @return */ public CaptchaDTO getCaptcha() { //1.在内存中创建一张图片 BufferedImage bi = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); // 画布颜色数组 Color[] colors = new Color[]{Color.BLUE, Color.CYAN, Color.GRAY, Color.GREEN, Color.ORANGE, Color.RED, Color.BLACK}; //2.得到图片 Graphics g = bi.getGraphics(); //3.设置图片的背影色 setBackGround(g, WIDTH, HEIGHT); //4.设置图片的边框 //setBorder(g,width,height); //5.在图片上画干扰线 drawRandomLine(g, colors, WIDTH, HEIGHT); String random = drawRandomNum((Graphics2D) g, colors); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); try { ImageIO.write(bi, "jpg", outputStream); } catch (IOException e) { e.printStackTrace(); } // 对字节数组Base64编码 BASE64Encoder encoder = new BASE64Encoder(); String imageCode = encoder.encode(outputStream.toByteArray()).replaceAll("\r|\n", ""); String token = JwtTokenUtils.generateCheckCode(random); CaptchaDTO captchaDTO = new CaptchaDTO(); captchaDTO.setCodeToken(token); captchaDTO.setImageCode(imageCode); // 保存到验证码到 redis 设置1分钟过期 redisTemplate.opsForValue().set(Constants.KAPTCHA_SESSION_KEY + token, random, 1, TimeUnit.MINUTES); return captchaDTO; } // 登录部分代码 String username = loginPARAM.getUsername(); String password = loginPARAM.getPassword(); String imageCode = loginPARAM.getImageCode(); String codeToken = loginPARAM.getCodeToken(); // 校验验证码 String code = (String) redisTemplate.opsForValue().get(Constants.KAPTCHA_SESSION_KEY + codeToken); if (StringUtils.isBlank(code)) { ApiAssert.failure(ErrorCodeEnum.KAPTCHA_NOT_FOUND); } // 清除token,防止重用 redisTemplate.delete(Constants.KAPTCHA_SESSION_KEY + codeToken); if (!imageCode.equalsIgnoreCase(code)) { ApiAssert.failure(ErrorCodeEnum.KAPTCHA_ERROR); }

希望对您有帮助!当然有更好的方法烦请大神指出



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭