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

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

1、跨域问题

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

解决方法

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

java 复制代码
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回传给前端

java 复制代码
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即可

js 复制代码
//举例:登录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。

java 复制代码
@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缓存管理器

java 复制代码
@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注入到安全管理中

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

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

相关推荐
zl9798999 小时前
SpringBoot-常用注解
java·spring boot·spring
舒克日记11 小时前
基于springboot的民谣网站的设计与实现
java·spring boot·后端
lang2015092813 小时前
Spring Boot配置属性:类型安全的最佳实践
spring boot
Jabes.yang19 小时前
Java面试场景:从Spring Web到Kafka的音视频应用挑战
大数据·spring boot·kafka·spring security·java面试·spring webflux
程序员小凯1 天前
Spring Boot性能优化详解
spring boot·后端·性能优化
tuine1 天前
SpringBoot使用LocalDate接收参数解析问题
java·spring boot·后端
番茄Salad1 天前
Spring Boot项目中Maven引入依赖常见报错问题解决
spring boot·后端·maven
摇滚侠1 天前
Spring Boot 3零基础教程,yml配置文件,笔记13
spring boot·redis·笔记
HECHEN****1 天前
Composition API 与 React Hook 很像,区别是什么?
vue·面试题
!if1 天前
springboot mybatisplus 配置SQL日志,但是没有日志输出
spring boot·sql·mybatis