Achieve springboot-vue separate front and rear end of the session expired log in again

Achieve springboot-vue separate front and rear end of the session expired log in again

A brief review of cookie and session

  1. cookie and session management are the answer mode
  2. Cookie
    • A cookie is a way of storing information in the browser
    • The server can respond to the browser set-cookie header (header,), after the browser receives the header information, will form the cookie file information stored on the browser client computers. After the request, cookie information browser the domain and then be sent to the server.
    • The default cookie survival period after closing the browser fails, that clears the browser cookie file information when closed. We can respond when the server cookie, set its survival period, such as one week or so after you close the browser cookie also still not been cleared within the time limit, the next time the browser requests will be sent to the server.
  3. Session
    • session and cookie use is closely related
    • cookie stored on the client (browser responsible for memory), session is stored on the server (a web container object, the server responsible for memory in Java).
    • Each object has a session the sessionID, the ID value is in the browser, the browser sends the cookie with a cookie stored, the container web server to obtain the corresponding session object cookie in accordance with the sessionID, so that each can be obtained browser's "session "information.
    • It is because of the actual use sessionID cookie stored on the client, and the default cookie term survival is the browser is closed, so the session of the "Term" is the browser is closed

Development environment

  • JDK8、Maven3.5.3、springboot2.1.6、STS4
  • node10.16, npm6.9, vue2.9, element-ui, axios

springboot backend provides an interface

  • demo has been placed Gitee
  • The demo requires only starter-web pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • Backend interface provides only interface services, port 8080 application.properties
server.port=8080
  • Only one controller, which has three handle, respectively, login, logout and normal request 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);
        }
    }
}
  • Global cross-domain configuration and landed interceptor Register 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/**");
    }
}
  • Login interceptor 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<Object> 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<String> 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;
    }
}
  • Global exception handler 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());
        }
    }
}
  • Back-end project package structure

    Back-end project package structure

vue-cli (2.x) front end

  • demo has been placed Gitee
  • Item distal packet structure - standard vue-cli

    Front-end project package structure
  • Routing settings, log ( '/') and Home 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
        }
  ]
})
  • Set the port 8081 (the rear end is 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 
    //...
  • Simple login and home assembly (complete code - see demo-Gitte chain)
    • log in

      Login effect components
    • After login Home

      Home Components effects
  • axios ajax global settings request, response and exception handling 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 routing Jump interception ( sessionStorageStarter Edition)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()
    }
})
  • Test

    front-end that is to enter the login page, Session, front-end login user name and password are correct then the back end of a successful jump to save login home page, click on the 'functional test' is normal json response (Session valid). How the initiative will fail Session on this page, it will be blocked again functional testing, jump the login page.

Problems encountered

  • In response to global exception handler returned no cross-domain required header

    Description: cross-domain itself is provided at the rear, so all requests are front-end cross-domain, but when I took the initiative to Session fail, then click the trigger function test login interception, interception Session failure throws an exception, capturing the global exception handler in response to normal and jsonat this time the response header less consoleprompt items:
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的,但是前端不能拿到响应

Then I was forced into a specified response header to an end (see back-end interceptor), this is not elegant, I want to know why a small partner can feel free under XD ..

Expand topics (links pit to be filled)

  • cookie been cleaned, how sessionID corresponding session object recovery?
    Violent temper ban users browser cookie?
  • Front and rear ends separated cross-domain request related
  • axios auxiliary configuration
  • Filter and the interceptor

    filter is () request interceptor in front servlet.service, springmvc interceptor front handle is intercepted method, the particle size is not the same.
  • Jump to intercept URL Routing
  • We can continue the theme: vuex state management, redis the session.

Contact & Communication

Guess you like

Origin www.cnblogs.com/noodlerkun/p/11094564.html