springboot集成shiro+前端vue,前后端分离项目遇到跨域以及sessionid拿不到等问题

news/2024/7/10 1:43:57 标签: spring boot, vue

近期在写前后端分离的项目,由于前后端分离导致原来使用的shiro配置无法满足现有系统要求。同时在前后端分离项目中存在的一些问题。例如,一些用户信息需要存储在后端方便进行安全性判断,但这些存储在后端的session前端却获取不到(特别奇怪),以及浏览器访问后端接口出现的跨域问题。

1、跨域问题

由于前后端分离导致前端和后端分别占用不同的端口,所以浏览器在访问不同接口的时候就会存在跨域问题。

解决方法

我是在springboot后端项目中添加CorsConfig配置类,用于解决跨域问题,当然前端也可以解决这个跨域问题。

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class corsConfig {

    @Bean
    public WebMvcConfigurer CORSConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("*")
                        .allowedMethods("*")
                        .allowedHeaders("*")
                        //设置是否允许跨域传cookie
                        .allowCredentials(true)
                        //设置缓存时间,减少重复响应
                        .maxAge(3600);
            }
        };
    }


    @Bean
    public FilterRegistrationBean corsFilter() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        final CorsConfiguration config = new CorsConfiguration();
        // 允许cookies跨域
        config.setAllowCredentials(true);
        // #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
        config.addAllowedOrigin("*");
        // #允许访问的头信息,*表示全部
        config.addAllowedHeader("*");
        // 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
        config.setMaxAge(3600L);
        // 允许提交请求的方法,*表示全部允许
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        source.registerCorsConfiguration("/**", config);

        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        // 设置监听器的优先级
        bean.setOrder(0);

        return bean;
    }
}

这样就能解决前后端跨域问题。

2、后端存储的session,前端获取不到

起因是我在做登录的时候,用户信息被我用session存储在了后端,但是我在调用其他接口的时候,我却无法在后端获取到我保存在后端的用户信息,无法判断前端是否登录过。

这个问题一开始困扰了我很久,搜了半天发现了一个不错的解释:这是因为服务器判断前端的请求是同一个 session 的依据是通过网页默认的一个sessionid的cookie判断的,如果存在跨域,cookie值传不过来,也就当下一个请求过来时服务端无法识别为同一个会话,会被当做一个新的会话处理,故找不到session原保存的值。
在这里插入图片描述
所以我就去测试两个不同请求的sessionId是否是一样的,发现果然不一样,这也是为什么我在后端无法获取到我保存在后端的session。

解决方法

其实解决办法有很多种,例如说不用session存储数据而是利用Redis,这当然是最轻松的方式。但是本着我的session是利用shiro自带的Util存储的,所以我的解决方式是将sessionId回传给前端,然后前端保存后每次访问后端接口的时候携带保存的sessionId,这样就能我就能获取到原来的session了。

1、在登录接口中将sessionId回传给前端

public ResponseResult doLogin(String username, String password) {
    UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    Subject subject = SecurityUtils.getSubject();
    try{
        subject.login(token);
        UserInfo userInfo = baseMapper.selectById(username);
        //利用shiro的Util获取sessionId
        String sessionId = (String) subject.getSession().getId();
        //存储用户信息
        ShiroUtils.setSessionAttribute(SessionConst.USER_INFO_SESSION,userInfo);

        System.out.println((String)ShiroUtils.getSession().getId());
        
        userInfo.setSalt("");
        userInfo.setPassword("");
        Map<String,Object> data = new HashMap<>();
        //保存key-value类型数据AUTHORIZATION,值为sessionId
        data.put("AUTHORIZATION", sessionId);
        data.put("userInfo",userInfo);
        //回传给前端
        return ResponseResult.success(data);
    }catch(UnknownAccountException e){
        return ResponseResult.error(ResponseResultEnum.SESSION_ERROR);
    }catch (IncorrectCredentialsException e){
        return ResponseResult.error(ResponseResultEnum.PASSWORD_ERROR);
    }
}

前端拿到sesionId后进行保存,并每次调用接口的时候携带Id即可

//举例:登录method
login() {
    this.$axios.get("/login", {
        params: {
            username: this.form1Data.username,
            password: this.form1Data.password
        }
    }).then((res) => {
        if (res.data.code === 200) {
            console.log(res)
            var userInfo = res.data.data.userInfo
            var auth = res.data.data.AUTHORIZATION
            ElementUI.Message.success("登录成功");
            this.$store.commit("SET_USERINFO", userInfo);
            
            //将信息保存在auth中
            this.$store.commit("SET_AUTH", auth)
            
            let url_name = this.$route.params.redirect
            console.log(url_name)
            if (url_name !== undefined && url_name !== null && url_name != '') {
                this.$router.replace({
                    name: url_name
                })
            } else
                this.$router.push('/')
        } else { // 有问题
            ElementUI.Message.error(res.data.message);
        }
    }
           )
}


//在main.js文件中进行前置拦截
axios.interceptors.request.use(config => {
    let auth = store.getters.get_auth
    //从auth中获取后端传给前端的sessionId,以后调用任何接口就可以携带sessionId了
    if (auth != null) {
        config.headers['AUTHORIZATION'] = auth;
    }
    console.log(config.headers)
    return config;

}, error => {
    return Promise.reject(error)
});

2、在配置类中写一个自定义的sessionManager并重写getSessionId方法,便于对前端传递过来的sessionId进行判断,检测是否是原来的sessionId。

@Configuration
@Slf4j
public class MySessionManager extends DefaultWebSessionManager {

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public MySessionManager() {

        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader("AUTHORIZATION");
        //如果请求头中有 Authorization (前端请求头中设置的名字)则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

3、在shiro配置类中注入自定义的session缓存管理器

@Bean("sessionManager")
public SessionManager sessionManager(){
    MySessionManager sessionManager = new MySessionManager();
    sessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
    sessionManager.setGlobalSessionTimeout(1000 * 60 * 30);
    sessionManager.setSessionValidationSchedulerEnabled(true);
    sessionManager.setSessionIdUrlRewritingEnabled(false);
    return sessionManager;
}

关键: 因为getSessionId()方法是通过DefaultWebSecurityManager类进行实现的,所以我们需要将SessionManager注入到安全管理中

/**
     * 安全管理
     *
     * @return
     */
@Bean
public DefaultWebSecurityManager securityManager(){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //注入
    securityManager.setSessionManager(sessionManager());
    securityManager.setRealm(getShiroRealm());
    return securityManager;
}

至此可解决前后端跨域后的sessionId的问题。


http://www.niftyadmin.cn/n/5332671.html

相关文章

【Linux】shell 脚本入门详解

一、shell入门简介 1.1什么是shell # 什么是shell网上有很多shell的概念介绍&#xff0c;其实都很官方化&#xff0c;如果你对linux命令很熟悉&#xff0c;那么编写shell就不是一个难事&#xff0c;shell本质上是linux命令&#xff0c;一条一条命令组合在一起&#xff0c;实现…

C#,字符串匹配(模式搜索)RK(Rabin Karp)算法的源代码

M.O.Rabin Rabin-Karp算法&#xff0c;是由M.O.Rabin和R.A.Karp设计实现的一种基于移动散列值的字符串匹配算法。 通常基于散列值的字符串匹配方法&#xff1a;&#xff08;1&#xff09;首先计算模式字符串的散列函数&#xff1b;&#xff08;2&#xff09;然后利用相同的散…

港科夜闻|香港科大团队研发多功能,可重构和抗破坏单线感测器阵列

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大团队研发多功能、可重构和抗破坏单线感测器阵列。研究人员开发出一种受人类听觉系统启发的感测器阵列设计技术。透过模仿人耳根据音位分布来区分声音的能力&#xff0c;这种新型感测器阵列方法可能优化感测器阵列…

【INTEL(ALTERA)】F-Tile 25G 以太网 FPGA IP RX MAC IP 报告 FCS 错误?

说明 当接收来自链路伙伴的外部流量时&#xff0c;F-Tile 25G 以太网 英特尔 FPGA IP RX MAC IP 使用恢复时钟 &#xff08;o_clk_rec_div64&#xff09; 计时&#xff0c;观察到 FCS 错误&#xff0c;并且恢复的时钟可能超过 ppm 差异。在流量为 0ppm 的内部和外部环回中可能…

10个常用的正则表达式

1 电话号码 let r1 /^1[3-9]\d{9}$/g console.log(r1.exec(18596932371)) 2 qq号 let r2 /^[1-9][0-9]{4,9}$/g console.log(r2.exec(123456)) 3 十六进制的方式表示颜色 let r3 /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/g // # 可能可有可无&#xff0c;如果不需要#&a…

进阶Docker4:网桥模式、主机模式与自定义网络

目录 网络相关 子网掩码 网关 规则 docke网络配置 bridge模式 host模式 创建自定义网络(自定义IP) 网络相关 IP 子网掩码 网关 DNS 端口号 子网掩码 互联网是由许多小型网络构成的&#xff0c;每个网络上都有许多主机&#xff0c;这样便构成了一个有层次的结构。 IP 地…

AT24C02读写操作 一

//AT24C02初始化 void AT24C02_Init(void) { IIC_Init(); } //AT24C02的字节写入 写一个字节 void AT24C02_WordWrite(uint8_Address,uint8_t Data) { //1。主机发送开始信号 IIC_StartSignal(); //2.主机发送器件地址 写操作 IIC_SentBytes(0xA0); //3.主机等侍从机应…

腾讯云 腾讯云服务器 - 腾讯云 产业智变·云启未来

腾讯云服务器CVM提供安全可靠的弹性计算服务&#xff0c;腾讯云明星级云服务器&#xff0c;弹性计算实时扩展或缩减计算资源&#xff0c;支持包年包月、按量计费和竞价实例计费模式&#xff0c;CVM提供多种CPU、内存、硬盘和带宽可以灵活调整的实例规格&#xff0c;提供9个9的数…