springboot-vue前后端分离session过期重新登录的实现

简单回顾cookie和session

cookie和session都是回话管理的方式

Cookie

cookie是浏览器端存储信息的一种方式

服务端可以通过响应浏览器set-cookie标头(header,),浏览器接收到这个标头信息后,将以文件形式将cookie信息保存在浏览器客户端的计算机上。之后的请求,浏览器将该域的cookie信息再一并发送给服务端。

cookie默认的存活期限关闭浏览器后失效,即浏览器在关闭时清除cookie文件信息。我们可以在服务端响应cookie时,设置其存活期限,比如设为一周,这样关闭浏览器后也cookie还在期限内没有被清除,下次请求浏览器就会将其发送给服务端了。

Session

session的使用是和cookie紧密关联的

cookie存储在客户端(浏览器负责记忆),session存储在服务端(在Java中是web容器对象,服务端负责记忆)。

每个session对象有一个sessionID,这个ID值还是用cookie方式存储在浏览器,浏览器发送cookie,服务端web容器根据cookie中的sessionID得到对应的session对象,这样就能得到各个浏览器的“会话”信息。

正是因为sessionID实际使用的cookie方式存储在客户端,而cookie默认的存活期限是浏览器关闭,所以session的“有效期”即是浏览器关闭

开发环境

JDK8、Maven3.5.3、springboot2.1.6、STS4

node10.16、npm6.9、vue2.9、element-ui、axios

springboot后端提供接口

demo 已放置 Gitee

本次 demo 只需要 starter-web pom.xml

org.springframework.boot

spring-boot-starter-web

后台接口只提供接口服务,端口8080 application.properties

server.port=8080

只有一个controller,里面有3个handle,分别是登录、注销和正常请求 TestCtrller.java

@RestController

public class TestCtrller extends BaseCtrller{

//session失效化-for功能测试

@GetMapping("/invalidateSession")

public BaseResult invalidateSession(HttpServletRequest request) {

HttpSession session = request.getSession(false);

if(session != null &&

session.getAttribute(SysConsts.Session_Login_Key)!=null) {

request.getSession().invalidate();

getServletContext().log("Session已注销!");

}

return new BaseResult(true);

}

//模拟普通ajax数据请求(待登录拦截的)

@GetMapping("/hello")

public BaseResult hello(HttpServletRequest request) {

getServletContext().log("登录session未失效,继续正常流程!");

return new BaseResult(true, "登录session未失效,继续正常流程!");

}

//登录接口

@PostMapping("/login")

public BaseResult login(@RequestBody SysUser dto, HttpServletRequest request) {

//cookie信息

Cookie[] cookies = request.getCookies();

if(null!=cookies && cookies.length>0) {

for(Cookie c:cookies) {

System.out.printf("cookieName-%s, cookieValue-%s, cookieAge-%d%n", c.getName(), c.getValue(), c.getMaxAge());

}

}

/**

* session处理

*/

//模拟库存数据

SysUser entity = new SysUser();

entity.setId(1);

entity.setPassword("123456");

entity.setUsername("Richard");

entity.setNickname("Richard-管理员");

//验密

if(entity.getUsername().equals(dto.getUsername()) && entity.getPassword().equals(dto.getPassword())) {

if(request.getSession(false) != null) {

System.out.println("每次登录成功改变SessionID!");

request.changeSessionId(); //安全考量,每次登陆成功改变 Session ID,原理:原来的session注销,拷贝其属性建立新的session对象

}

//新建/刷新session对象

HttpSession session = request.getSession();

System.out.printf("sessionId: %s%n", session.getId());

session.setAttribute(SysConsts.Session_Login_Key, entity);

session.setAttribute(SysConsts.Session_UserId, entity.getId());

session.setAttribute(SysConsts.Session_Username, entity.getUsername());

session.setAttribute(SysConsts.Session_Nickname, entity.getNickname());

entity.setId(null); //敏感数据不返回前端

entity.setPassword(null);

return new BaseResult(entity);

}

else {

return new BaseResult(ErrorEnum.Login_Incorrect);

}

}

}

全局跨域配置和登陆拦截器注册 MyWebMvcConfig.java

@Configuration

public class MyWebMvcConfig implements WebMvcConfigurer{

//全局跨域配置

@Override

public void addCorsMappings(CorsRegistry registry) {

registry.addMapping("/**") //添加映射路径

.allowedOrigins("http://localhost:8081") //放行哪些原始域

.allowedMethods("*") //放行哪些原始域(请求方式) //"GET","POST", "PUT", "DELETE", "OPTIONS"

.allowedHeaders("*") //放行哪些原始域(头部信息)

.allowCredentials(true) //是否发送Cookie信息

// .exposedHeaders("access-control-allow-headers",

// "access-control-allow-methods",

// "access-control-allow-origin",

// "access-control-max-age",

// "X-Frame-Options") //暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)

.maxAge(1800);

}

//注册拦截器

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(new MyLoginInterceptor())

.addPathPatterns("/**")

.excludePathPatterns("/login")

.excludePathPatterns("/invalidateSession");

//.excludePathPatterns("/static/**");

}

}

登录拦截器 MyLoginInterceptor.java

public class MyLoginInterceptor implements HandlerInterceptor{

@Override

public boolean preHandle(HttpServletRequest request,

HttpServletResponse response, Object handler) throws Exception {

request.getServletContext().log("MyLoginInterceptor preHandle");

HttpSession session = request.getSession();

request.getServletContext().log("sessionID: " + session.getId());

Optional token = Optional.ofNullable(session.getAttribute(SysConsts.Session_Login_Key));

if(token.isPresent()) { //not null

request.getServletContext().log("登录session未失效,继续正常流程!");

} else {

request.getServletContext().log(ErrorEnum.Login_Session_Out.msg());

// Enumeration enumHeader = request.getHeaderNames();

// while(enumHeader.hasMoreElements()) {

// String name = enumHeader.nextElement();

// String value = request.getHeader(name);

// request.getServletContext().log("headerName: " + name + " headerValue: " + value);

// }

//尚未弄清楚为啥全局异常处理返回的响应中没有跨域需要的header,于是乎强行设置响应header达到目的 XD..

//希望有答案的伙伴可以留言赐教

response.setHeader("Access-Control-Allow-Origin", request.getHeader("origin"));

response.setHeader("Access-Control-Allow-Credentials", "true");

response.setCharacterEncoding("UTF-8");

response.setContentType("text/html; charset=utf-8");

// PrintWriter writer = response.getWriter();

// writer.print(new BaseResult(ErrorEnum.Login_Session_Out));

// return false;

throw new BusinessException(ErrorEnum.Login_Session_Out);

}

return true;

}

}

全局异常处理 MyCtrllerAdvice.java

@ControllerAdvice(

basePackages = {"com.**.web.*"},

annotations = {Controller.class, RestController.class})

public class MyCtrllerAdvice {

//全局异常处理-ajax-json

@ExceptionHandler(value=Exception.class)

@ResponseBody

public BaseResult exceptionForAjax(Exception ex) {

if(ex instanceof BusinessException) {

return new BaseResult((BusinessException)ex);

}else {

return new BaseResult(ex.getCause()==null?ex.getMessage():ex.getCause().getMessage());

}

}

}

后端项目包结构

vue-cli(2.x)前端

demo 已放置 Gitee

前端项目包结构-标准的 vue-cli

路由设置,登录(‘/‘)和首页 router/index.js

import Vue from 'vue'

import Router from 'vue-router'

import Home from '@/components/Home'

import Login from '@/components/Login'

Vue.use(Router)

export default new Router({

routes: [

{

path: '/',

name: 'Login',

component: Login

},

{

path: '/home',

name: 'Home',

component: Home

}

]

})

设置端口为8081(后端则是8080)config/index.js

module.exports = {

dev: {

// Paths

assetsSubDirectory: 'static',

assetsPublicPath: '/',

proxyTable: {},

// Various Dev Server settings

host: 'localhost', // can be overwritten by process.env.HOST

port: 8081, // can be overwritten by

//...

简单的登录和首页组件(完整代码-见demo-Gitte链)

登录

登录后首页

axios ajax请求全局设置、响应和异常处理 src/main.js

import axios from 'axios'

axios.defaults.baseURL = 'http://localhost:8080'

//axios.defaults.timeout = 3000

axios.defaults.withCredentials = true //请求发送cookie

// 添加请求拦截器

axios.interceptors.request.use(function (config) {

// 在发送请求之前做些什么

console.log('in interceptor, request config: ', config);

return config;

}, function (error) {

// 对请求错误做些什么

return Promise.reject(error);

});

// 添加响应拦截器

axios.interceptors.response.use(function (response) {

// 对响应数据做点什么

console.log('in interceptor, response: ', response);

if(!response.data.success){

console.log('errCode:', response.data.errCode, 'errMsg:', response.data.errMsg);

Message({type:'error',message:response.data.errMsg});

let code = response.data.errCode;

if('login02'==code){ //登录session失效

//window.location.href = '/';

console.log('before to login, current route path:', router.currentRoute.path);

router.push({path:'/', query:{redirect:router.currentRoute.path}});

}

}

return response;

}, function (error) {

// 对响应错误做点什么

console.log('in interceptor, error: ', error);

Message({showClose: true, message: error, type: 'error'});

return Promise.reject(error);

});

路由URL跳转拦截(sessionStorage初级版)src/main.js

//URL跳转(变化)拦截

router.beforeEach((to, from, next) => {

//console.log(to, from, next) //

if(to.name=='Login'){ //本身就是登录页,就不用验证登录session了

next()

return

}

if(!sessionStorage.getItem('username')){ //没有登录/登录过期

next({path:'/', query:{redirect:to.path}})

}else{

next()

}

})

测试过程

前端进入即是login页,用户名和密码正确则后端保存登录的Session,前端登录成功跳转home页,点击‘功能测试‘则是正常json响应(Session有效)。如何在本页中主动将Session失效,再次功能测试则会被拦截,跳转登录页。

碰到的问题

全局异常处理返回的响应中没有跨域需要的 header

描述:本身跨域设置在后端,所以前端所有的请求都是跨域的,但是当我主动将Session失效,然后点击功能测试触发登录拦截,拦截器抛出Session失效异常,由全局异常处理捕捉并正常地响应json,此时响应头中就少了console中提示的项:

XMLHttpRequest cannot load http://localhost:8080/hello. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8081' is therefore not allowed access.

//PS:查看network可以看到请求是200的,但是前端不能拿到响应

而后我是强行塞入指定响应头达到目的的(见后端拦截器),这样做不优雅,希望知道原因的小伙伴可以不吝指教下 XD..

拓展话题(链接坑待填)

cookie被清理,sessionID对应的session对象怎么回收?

暴脾气用户禁掉浏览器cookie?

前后端分离跨域请求相关

axios 辅助配置

过滤器与拦截器

过滤器是在servlet.service()请求前后拦截,springmvc拦截器则是在handle方法前后拦截,粒度不一样。

URL跳转路由拦截

可以继续的主题:vuex状态管理,redis与session。

联系&交流

原文:https://www.cnblogs.com/noodlerkun/p/11094564.html

vue前后分离session实现_springboot-vue前后端分离session过期重新登录的实现相关推荐

  1. java计算机毕业设计基于springboot+vue+elementUI的口腔管理平台管理系统(前后端分离)

    项目介绍 口腔卫生是关系民生的一个重要问题.口腔健康会直接影响全身的健康,口腔基本常见的有龋齿,牙周炎等问题,而且人类的牙齿只有2次更换周期,一旦牙齿彻底完成更换终生将不再更换,所以越来越多的人开始关 ...

  2. 基于nodejs+vue+elementUI的电影交流网站(前后端分离)电影网站

    该系统基于nodejs+vue+elementUI整合,mysql数据库,前后端分离,具有完整的业务逻辑. 电影交流网站,在网站首页可以查看首页.电影信息.交流论坛.电影资讯.个人中心.后台管理等内容 ...

  3. 前后端分离架构:Web 实现前后端分离,前后端解耦

    你知道的越多,不知道的就越多,业余的像一棵小草! 你来,我们一起精进!你不来,我和你的竞争对手一起精进! 编辑:业余草 blog.csdn.net/fuzhongmin05 推荐:https://ww ...

  4. springboot+jwt+shiro+vue+elementUI+axios+redis+mysql完成一个前后端分离的博客项目(笔记,帮填坑)

    根据B站up主MarkerHub视频制作的一个笔记 我的博客 B站博主链接: https://www.bilibili.com/video/BV1PQ4y1P7hZ?p=1 博主的开发文档: http ...

  5. vue调用接口修改密码_vue开发前后端分离前端如何调用后端接口?

    对前后端分离如何调用接口这块感觉一直没怎么弄明白,但又不知如何说明,下面我模拟一个项目说明我的问题. 现在我们有个项目,前端用vue开发,后端php开发,后端测试地址为:localhost:8181, ...

  6. vue+element模仿电商商城,前后端分离实现,下单微信扫码支付

    1.前言 接上一篇<vue+element+SpringBoot+OAuth2+Spring Security+Redis+mybatis-plus+mysql+swagger模仿商城,前后端分 ...

  7. 基于springboot+vue的商城系统(电商平台)(前后端分离)

    博主主页:猫头鹰源码 博主简介:Java领域优质创作者.CSDN博客专家.公司架构师.全网粉丝5万+.专注Java技术领域和毕业设计项目实战 主要内容:毕业设计(Javaweb项目|小程序等).简历模 ...

  8. 【JAVA程序设计】基于SpringBoot+VUE的高校疫情打卡系统-前后端分离

    基于SpringBoot+VUE的高校疫情打卡系统 零.项目获取 一.项目简介 二.开发环境 三.项目技术 四.系统架构 五.运行截图 六.数据库设计 零.项目获取 获取方式(点击下载):是云猿实战 ...

  9. Springboot+vue在线考试系统源码,前后端分离

    Springboot+vue在线考试系统源码 开发语言:Java 数据库:Mysql 开发工具:Eclipse 使用技术: 后端:SpringBoot 前端:VUE 和 Element-UI 源码免费 ...

  10. vue返回上级并且携带数据_前后端分离之后端返回用户角色信息(vueelementadmin+laravel)...

    通过之前对vue-element-admin路由的解析,可以得知后端需向前端返回用户端的角色信息,前端在拿到用户的角色信息之后才能去拼装最终生成的路由信息. 我们先看前端需要什么样的数据格式,我们打开 ...

最新文章

  1. dojo从asp.net中获取json数据
  2. c#扩展方法奇思妙用高级篇四:对扩展进行分组管理
  3. Caffe 增加自定义 Layer 及其 ProtoBuffer 参数
  4. Unknown system variable 'tx_isolation'报错
  5. 话说模式匹配(1) 什么是模式?
  6. 清华山维eps软件_6款倾斜摄影裸眼3D采集软件 | 推荐给大家
  7. 手机坏点如何测试软件,怎么检测手机屏幕坏点
  8. Android 图片虚化,虚化图片,模糊图片
  9. 支付宝提现至个人账户接口开发
  10. 蓝牙耳机什么牌子的好?口碑、销量双高的十大蓝牙耳机品牌!
  11. 基于单片机的模拟风扇控制系统
  12. java编程惯用法_java惯用法转载
  13. Tello无人机的使用笔记之dji-sdk/Tello-Python
  14. 恒生电子发布2023金融科技趋势研究报告,探索数智金融时代技术创新
  15. java毕业设计校园内推系统mybatis+源码+调试部署+系统+数据库+lw
  16. [转载]软件测试从零开始
  17. Python_Nine
  18. c语言程序设计数学电压表,单片机课程设计-数字电压表的设计.doc
  19. 猿创征文|Python3,10分钟写了一个WIFI 万(破) 能 (解) 钥 (神) 匙 (器),YYDS。
  20. jvm深入理解:内存分配与回收策略(优先在Eden分配、大对象直接进入老年代、长期存活的对象将进入老年代、动态对象年龄判定、空间分配担保)

热门文章

  1. C4D建模宝典R20笔记
  2. js几种加密/解密方法
  3. ISO27001标准
  4. Java开源项目Hibernate获得成功的十大理由
  5. 能打开2D、3D图文件的小工具abviewer
  6. 蓝桥杯题目 abcde/fghij=n
  7. ztree 后台异步加载_ztree 异步加载示例
  8. 大地坐标系是不是经纬度_批量导入经纬度点到奥维地图中
  9. 实训-利用HTML+CSS做响应式项目网页
  10. RTSP视频流直播实现(海康)