当前位置: 代码网 > it编程>编程语言>Java > SpringBoot解决跨域导致sessionId不一致的实现方式

SpringBoot解决跨域导致sessionId不一致的实现方式

2026年04月10日 Java 我要评论
springboot解决跨域导致sessionid不一致在用谷歌的kaptcha做验证码登录校验,将后端发布到阿里云,前端是本地启动,用谷歌浏览器(版本85)访问验证码遇到了如下问题(360浏览器、m

springboot解决跨域导致sessionid不一致

在用谷歌的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);
        }

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com