精华内容
下载资源
问答
  • Springboot Vue验证码校验

    千次阅读 2020-05-01 17:34:02
    项目中在登录注册环节肯定会用到验证码校验,Springboot整合Thymeleaf验证码校验之前已经做过了,那么现在SpringbootVue前后端分离项目中该如何使用呢。 实现思路: 后端利用工具类生成验证码code,将验证码code...

    项目中在登录注册环节肯定会用到验证码校验,Springboot整合Thymeleaf验证码校验之前已经做过了,那么现在Springboot和Vue前后端分离项目中该如何使用呢。

    实现思路:

    1. 后端利用工具类生成验证码code,将验证码code以key,value的形式利用域对象ServletContext存入session中,同时生成验证码图片将图片进行base64编码,编码过后的字符串和key一起返回给前端处理。

    2. vue生命周期创建是就获取验证码,并将后端传来的key和编码后的字符串绑定data,并解码赋值给src以显示验证码图片。注册时将用户输入的验证码code和后端传来的key作为参数请求后端注册api。

    3. 后端接受到用户输入的验证码code和key,利用key通过域对象ServletContext取出session中的对应的验证码code,不分大小写比较用户输入验证码和生成验证码是否相同,相同则注册,否则验证码错误!

    代码实现

    验证码工具类:

    import java.awt.Color;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.Random;
    
    import javax.imageio.ImageIO;
    
    
    public class CreateImageCode {
        // 图片的宽度。
        private int width = 160;
        // 图片的高度。
        private int height = 40;
        // 验证码字符个数
        private int codeCount = 4;
        // 验证码干扰线数
        private int lineCount = 20;
        // 验证码
        private String code = null;
        // 验证码图片Buffer
        private BufferedImage buffImg = null;
        Random random = new Random();
    
        public CreateImageCode() {
            creatImage();
        }
    
        public CreateImageCode(int width, int height) {
            this.width = width;
            this.height = height;
            creatImage();
        }
    
        public CreateImageCode(int width, int height, int codeCount) {
            this.width = width;
            this.height = height;
            this.codeCount = codeCount;
            creatImage();
        }
    
        public CreateImageCode(int width, int height, int codeCount, int lineCount) {
            this.width = width;
            this.height = height;
            this.codeCount = codeCount;
            this.lineCount = lineCount;
            creatImage();
        }
    
        // 生成图片
        private void creatImage() {
            int fontWidth = width / codeCount;// 字体的宽度
            int fontHeight = height - 5;// 字体的高度
            int codeY = height - 8;
    
            // 图像buffer
            buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = buffImg.getGraphics();
            //Graphics2D g = buffImg.createGraphics();
            // 设置背景色
            g.setColor(getRandColor(200, 250));
            g.fillRect(0, 0, width, height);
    
    
    
            // 设置字体
            //Font font1 = getFont(fontHeight);
            Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
            g.setFont(font);
    
            // 设置干扰线
            for (int i = 0; i < lineCount; i++) {
                int xs = random.nextInt(width);
                int ys = random.nextInt(height);
                int xe = xs + random.nextInt(width);
                int ye = ys + random.nextInt(height);
                g.setColor(getRandColor(1, 255));
                g.drawLine(xs, ys, xe, ye);
            }
    
            // 添加噪点
            float yawpRate = 0.01f;// 噪声率
            int area = (int) (yawpRate * width * height);
            for (int i = 0; i < area; i++) {
                int x = random.nextInt(width);
                int y = random.nextInt(height);
    
                buffImg.setRGB(x, y, random.nextInt(255));
            }
    
    
            String str1 = randomStr(codeCount);// 得到随机字符
            this.code = str1;
            for (int i = 0; i < codeCount; i++) {
                String strRand = str1.substring(i, i + 1);
                g.setColor(getRandColor(1, 255));
                // g.drawString(a,x,y);
                // a为要画出来的东西,x和y表示要画的东西最左侧字符的基线位于此图形上下文坐标系的 (x, y) 位置处
    
                g.drawString(strRand, i*fontWidth+3, codeY);
            }
    
    
        }
    
        // 得到随机字符
        private String randomStr(int n) {
            String str1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
            String str2 = "";
            int len = str1.length() - 1;
            double r;
            for (int i = 0; i < n; i++) {
                r = (Math.random()) * len;
                str2 = str2 + str1.charAt((int) r);
            }
            return str2;
        }
    
        // 得到随机颜色
        private Color getRandColor(int fc, int bc) {// 给定范围获得随机颜色
            if (fc > 255)
                fc = 255;
            if (bc > 255)
                bc = 255;
            int r = fc + random.nextInt(bc - fc);
            int g = fc + random.nextInt(bc - fc);
            int b = fc + random.nextInt(bc - fc);
            return new Color(r, g, b);
        }
    
        /**
         * 产生随机字体
         */
        private Font getFont(int size) {
            Random random = new Random();
            Font font[] = new Font[5];
            font[0] = new Font("Ravie", Font.PLAIN, size);
            font[1] = new Font("Antique Olive Compact", Font.PLAIN, size);
            font[2] = new Font("Fixedsys", Font.PLAIN, size);
            font[3] = new Font("Wide Latin", Font.PLAIN, size);
            font[4] = new Font("Gill Sans Ultra Bold", Font.PLAIN, size);
            return font[random.nextInt(5)];
        }
    
        // 扭曲方法
        private void shear(Graphics g, int w1, int h1, Color color) {
            shearX(g, w1, h1, color);
            shearY(g, w1, h1, color);
        }
    
        private void shearX(Graphics g, int w1, int h1, Color color) {
    
            int period = random.nextInt(2);
    
            boolean borderGap = true;
            int frames = 1;
            int phase = random.nextInt(2);
    
            for (int i = 0; i < h1; i++) {
                double d = (double) (period >> 1)
                        * Math.sin((double) i / (double) period
                        + (6.2831853071795862D * (double) phase)
                        / (double) frames);
                g.copyArea(0, i, w1, 1, (int) d, 0);
                if (borderGap) {
                    g.setColor(color);
                    g.drawLine((int) d, i, 0, i);
                    g.drawLine((int) d + w1, i, w1, i);
                }
            }
    
        }
    
        private void shearY(Graphics g, int w1, int h1, Color color) {
    
            int period = random.nextInt(40) + 10; // 50;
    
            boolean borderGap = true;
            int frames = 20;
            int phase = 7;
            for (int i = 0; i < w1; i++) {
                double d = (double) (period >> 1)
                        * Math.sin((double) i / (double) period
                        + (6.2831853071795862D * (double) phase)
                        / (double) frames);
                g.copyArea(i, 0, 1, h1, 0, (int) d);
                if (borderGap) {
                    g.setColor(color);
                    g.drawLine(i, (int) d, i, 0);
                    g.drawLine(i, (int) d + h1, i, h1);
                }
    
            }
    
        }
    
    
    
        public void write(OutputStream sos) throws IOException {
            ImageIO.write(buffImg, "png", sos);
            sos.close();
        }
    
        public BufferedImage getBuffImg() {
            return buffImg;
        }
    
        public String getCode() {
            return code.toLowerCase();
        }
    
        //使用方法
     /*public void getCode3(HttpServletRequest req, HttpServletResponse response,HttpSession session) throws IOException{
            // 设置响应的类型格式为图片格式
                response.setContentType("image/jpeg");
                //禁止图像缓存。
                response.setHeader("Pragma", "no-cache");
                response.setHeader("Cache-Control", "no-cache");
                response.setDateHeader("Expires", 0);
    
    
                CreateImageCode vCode = new CreateImageCode(100,30,5,10);
                session.setAttribute("code", vCode.getCode());
                vCode.write(response.getOutputStream());
         }*/
    
    }
    

    后端生成验证码

     /**
         * 生成验证码
         *
         * @throws IOException
         */
        @GetMapping("getImage")
        public Map<String, String> getImage(HttpServletRequest request) throws IOException {
            Map<String, String> result = new HashMap<>();
            CreateImageCode createImageCode = new CreateImageCode();
            //获取验证码
            String securityCode = createImageCode.getCode();
            //验证码存入session
            String key = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
            request.getServletContext().setAttribute(key, securityCode);
            //生成图片
            BufferedImage image = createImageCode.getBuffImg();
            //进行base64编码
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ImageIO.write(image, "png", bos);
            String string = Base64Utils.encodeToString(bos.toByteArray());
            result.put("key", key);
            result.put("image", string);
            return result;
        }
    

    后端注册

      /**
         * 用户注册
         *
         * @param code
         * @param user
         * @return
         */
        @PostMapping("register")
        public Result register(String code, String key, @RequestBody User user, HttpServletRequest request) {
            Result result = new Result();
            log.info("接收的验证码: " + code);
            log.info("接收的验证码的key: " + key);
            log.info("接收到user对象: " + user);
            //验证验证码
            String keyCode = (String) request.getServletContext().getAttribute(key);
            log.info(keyCode);
            try {
                //不考虑大小写比较生成的验证码和输入的验证码是否相同
                if (code.equalsIgnoreCase(keyCode)) {
                    //注册用户
                    userService.register(user);
                    result.setMsg("注册成功!!!");
                } else {
                    throw new RuntimeException("验证码错误!!!");
                }
            } catch (Exception e) {
                e.printStackTrace();
                result.setMsg(e.getMessage()).setState(false);
            }
            return result;
        }
    

    前端vue展示并调用后端api

     <div id="content" style="height: 360px">
            <img src="img/timg.jpg" style="float: right;height: 320px">
            <h2 style="margin-left: 10px">注册</h2>
            <form action="province/provincelist.html" method="post">
                <label>
                    <div class="label-text">账&emsp;号:</div>
                    <input type="text" v-model="user.username" name="username">
                </label>
                <label>
                    <div class="label-text">密&emsp;码:</div>
                    <input type="password" v-model="user.password" name="password">
                </label>
                <label>
                    <div class="label-text">邮&emsp;箱:</div>
                    <input type="text" v-model="user.email" name="email">
                </label>
                <img :src="src" id="img-vcode" @click="getImage" :key="key">
                <label>
                    <div class="label-text">验证码:</div>
                    <input type="text" v-model="code" name="vcode" style="width: 100px">
                </label>
                <button style="margin-top: 20px" type="button" class="registerbtnstyle" @click="saveUserInfo">注 册
                </button>&emsp;
                <a style="margin-top: 20px;margin-left: 30px" href="login.html">去登录</a>
            </form>
        </div>
    
    <script>
        const app = new Vue({
            el: "#app",
            data:{
                user:{},
                code:"",
                src:"",
                key:"",
            },
            methods:{
                saveUserInfo(){  //注册
                    console.log(this.user.username + this.user.password + this.user.email);
                    console.log(this.code);
                    if(!this.user.username){
                        alert('用户名不能为空!!!!');
                        return;
                    }
                    if(!this.user.password){
                        alert('密码不能为空!!!!');
                        return;
                    }
                    //发送axios
                    axios.post("http://localhost:321/user/register?code="+this.code+"&key="+this.key,this.user).then((res)=>{
                        console.log(res);
                        if(res.data.state){
                            alert(res.data.msg+",点击确定跳转到登录页面!!!");
                            location.href='./login.html';
                        }else{
                            alert(res.data.msg);
                        }
                    });
                },
                getImage(){   //获取验证码
                    _this  = this;
                    axios.get("http://localhost:321/user/getImage").then((res)=>{
                        console.log(res.data.key);
                        _this.src = "data:image/png;base64,"+res.data.image;//base64解码
                        _this.key = res.data.key;
                    });
                }
            },
            created(){
                this.getImage();//获取验证码
            }
    
        });
    </script>
    

    在这里插入图片描述

    展开全文
  • springboot-vue前后端分离session过期重新登录的实现简单回顾cookie和sessioncookie和session都是回话管理的方式Cookiecookie是浏览器端存储信息的一种方式服务端可以通过响应浏览器set-cookie标头(header,),...

    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

    展开全文
  • 这次整合了springboot+vue的登陆注册,实现了前后分离,可以作为入门的基础,重要的部分我都红框画出来,基本的代码都有了,如果没有安装vue的可以看下我上一篇文字vue的安装和基本语法。前端 visual studio code...

    这次整合了springboot+vue的登陆注册,实现了前后分离,可以作为入门的基础,重要的部分我都红框画出来,基本的代码都有了,如果没有安装vue的可以看下我上一篇文字vue的安装和基本语法。

    前端 visual studio code

    后端:ideal

    Vue:

    vue init webpack 包名

    安装axios在项目中

    在main.js配置内容

    import axios from 'axios'

    Vue.prototype.$axios = axios

    d6057cee8071a914f78efce85d6a0e23.png

    文件目录:

    270a33766239d44693b0f0a6c0411849.png

    Components包下创建三个文件(页面显示):

    1.Login.vue

    1869e8afa50c446fcf91babc6acef727.png
    dceb9829865206f46b0eef284daf0c4b.png

    2. success.vue

    505c7299368c2cbd0d649e29e1b5372e.png

    3. error.vue

    d97821c35a04d8ae4dce1fb923c9b41b.png

    配置路由(router的index.js):

    a2be5475f406eb91fea9721d2ba2a7f9.png
    6fe469ac6b398ef083500e9d79e3a858.png

    前后分离:在前端做代理:

    1267fd53f6d5d04cf95cc9c5ae1c8e71.png

    在config/index.js中加入

    39d6fa7a04b348544c6b1dc0d274f11f.png

    前端结束

    =============================================================================

    后端:

    创建model层;

    18d613f9c76f181bcf1d1cc75c237d1f.png

    创建Result封装返回code:

    bfb1235a39fef5ab266ee065b189a1a4.png

    创建dao层;

    0d464ef3040b8196c3a4d6e9bc0f3c0e.png

    Xml:

    cdd858fdad5ba1fced9ccc52438725d9.png

    创建service:

    41cc4b44d86813af647bb94add03a7ac.png

    创建impl层;

    56fdc62cec07ae8f09db7987c6be8c8a.png

    创建controller:

    d6b6bb92c4675872af5bed1af25750e4.png

    application.properties:

    aff1573ba3889257e321136403ce7725.png

    后端结束:

    ==================================================================================

    数据库:

    b4901da59e7f3c6c3119a252572bc5b0.png

    启动visual studio code: http://localhost:8080

    启动后台

    登陆页面:

    1784513c73e25b972b61e838e3c7b387.png

    正确登录跳转:

    a91f915185e23155375f7f0e52cfc091.png

    账号密码错误:

    d7107774246ac30fe91257d66baa46f1.png

    ====================================================

    记录学习,每天进步一点点的橘子大王。

    展开全文
  • springboot+vue实现细粒度的session处理 编写逻辑 1.使用自定义的过滤器去实现servlet的Filter接口,重写doFilter方法 2. 在springboot的入口类上使用@ServletComponentScan注解引入自定义的过滤器 3. 在实现...

    springboot+vue实现细粒度的session处理

    编写逻辑

    1.使用自定义的过滤器去实现servlet的Filter接口,重写doFilter方法
    2. 在springboot的入口类上使用@ServletComponentScan注解引入自定义的过滤器
    3. 在实现doFilter方法的时候,如果session没有过期,则放行,否则则返回session过期的提示信息给前端
    4. 前端接收到session过期(一般都会在请求响应时做统一处理),则提示用户session过期,然后重定向到登录页面即可;

    代码示例

    1. 以下代码参考了部分网友的写法:spring boot使用过滤器(以session校验为例)

    2. 后端代码:

      A.自定义的Filter:

    import com.alibaba.fastjson.JSONObject;
    import com.itsource.base.common.SdmConst;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @WebFilter(filterName = "sessionFilter",urlPatterns = {"/*"})
    public class SessionFilter implements Filter {
    
        //标示符:表示当前用户未登录(可根据自己项目需要改为json样式)
        String EXPIRE_MSG = "{\"data\":{\"code\": \"100003\"}}";
    
        //不需要登录就可以访问的路径(比如:注册登录等)
        String[] includeUrls = new String[]{"/tsyslogin/actionTSysLogin.do","/tsysuser/queryTSysMenuTreeByUser.do"};
    
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            HttpSession session = request.getSession(false);
            String uri = request.getRequestURI();
    
            System.out.println("filter url:"+uri);
            //是否需要过滤
            boolean needFilter = isNeedFilter(uri);
    
    
            if (!needFilter) { //不需要过滤直接传给下一个过滤器
                filterChain.doFilter(servletRequest, servletResponse);
            } else { //需要过滤器
                // session中包含user对象,则是登录状态
                if(session!=null&&session.getAttribute(SdmConst.LOGIN_SESSION_KEY) != null){
                    // System.out.println("user:"+session.getAttribute("user"));
                    filterChain.doFilter(request, response);
                }else{
                    String requestType = request.getHeader("X-Requested-With");
                    //判断是否是ajax请求
                    if(requestType!=null && "XMLHttpRequest".equals(requestType)){
                    }else{
                        response.setCharacterEncoding("UTF-8");
                        response.setContentType("application/json; charset=utf-8");
                        PrintWriter out = response.getWriter();
                        JSONObject res = new JSONObject();
                        res.put("code", "100003");
                        out.append(res.toString());
                    }
                }
            }
        }
    
        /**
         * @Author: xxxxx
         * @Description: 是否需要过滤
         * @Date: 2018-03-12 13:20:54
         * @param uri
         */
        public boolean isNeedFilter(String uri) {
    
            for (String includeUrl : includeUrls) {
                if(includeUrl.equals(uri)) {
                    return false;
                }
            }
    
            return true;
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void destroy() {
    
        }
    }
    
    B.入口程序:
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
    import org.springframework.boot.web.servlet.ServletComponentScan;
    
    /**
     *  注:为了避免扫描路径不一致,请将启动类放在Root Package 即 com.itsource
     */
    @SpringBootApplication(exclude = MongoAutoConfiguration.class)
    @MapperScan("**.itsource.**.mapper")
    @ServletComponentScan
    public class WebApplication {
        public static void main(String[] args) {
            SpringApplication.run(WebApplication.class, args);
        }
    }
    

    3.前端代码

    import axios from 'axios';
    // import qs from 'qs';
    import router from '@/router';
    import ErrorHandling from './error-handling';
    import {Modal} from 'ant-design-vue';
    import Loading from './loading-toast'
    import {crypto} from '../utils';
    import Vue from 'vue';
    import {merge} from 'lodash';
    
    const Error = new ErrorHandling();
    
    /**
     * Axios构造函数
     * @method CreatAxios
     * @param {Object} config
     * @constructor
     */
    function CreatAxios(config = {}) {
      merge({
        // baseURL: '/', // 因为我本地做了反向代理
        timeout: 60 * 1000, // 超时处理
        responseType: 'json', // 响应数据类型
        withCredentials: true, // 是否允许带cookie这些
        headers: {
          // json 或者 x-www-form-urlencoded 自行选择
          // 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
          'Content-Type': 'application/json;charset=utf-8'
        }
      }, config);
    
      const Axios = axios.create(config);
    
      //POST传参序列化(添加请求拦截器)
      Axios.interceptors.request.use(
        config => {
          Loading.open();
          // 在发送请求之前做某件事
          // 温馨提示,若提交能直接接受json 格式,可以不用 qs 来序列化的
          // if (config.method === 'post') {
          //   // 序列化
          //   config.data = qs.stringify(config.data);
          // }
    
          // 开启上送数据加密
          if ((config.hasOwnProperty('encrypt') ? config.encrypt : Vue.config.encrypt) && config.data) {
            config.data = crypto.Encrypt(config.data)
          }
    
          return config;
        },
        error => {
          Loading.close();
          // error 的回调信息
          Modal.error({
            title: '请求失败',
            cancelText: '确定',
            content: error && error.data.error.message
          });
          return Promise.reject(error.data.error.message);
        }
      );
    
      //返回状态判断(添加响应拦截器)
      Axios.interceptors.response.use(
        res => {
          Loading.close();
          //对响应数据做些事
          
          if (res.data) {
            //判断session是否过期
            if (res.data.code === '100003') {
              //
              Modal.error({
                title: '访问错误',
                cancelText: '确定',
                content: '用户登录过期,请重新登录!',
                onOk: () => {
                  router.push({
                    path: '/login'
                  })
                }})
                return
            }
            //请求错误
            if (res.data.code !== '000000') {
              const err_msg = Error.business(res.data);
              // 错误处理
              Modal.error({
                title: '请求错误',
                cancelText: '确定',
                content: err_msg,
                onOk: () => {
                  // router.push({
                  //   path: '/login'
                  // });
                }
              })
            }
            // 响应解密
            if ((res.config.hasOwnProperty('encrypt') ? res.config.encrypt : Vue.config.encrypt) && res.config.data) {
              res.data = crypto.Decrypt(res.data)
            }
          }
          if (res.hasOwnProperty('data')) {
            res = res.data
          }
          return res;
        },
        error => {
          Loading.close();
          const errMsg = Error.server(error.response);
    
          Modal.error({
            title: '服务器错误',
            okText: '确定',
            content: errMsg
          });
    
          return Promise.reject(error);
        }
      );
    
      return Axios;
    }
    
    // 对axios的实例重新封装成一个plugin ,方便 Vue.use(xxxx)
    export default CreatAxios;
    

    以上只是个人的用法,在此做一个记录,仅供参考!

    展开全文
  • RoleFrontendPermissionRelation 表存储角色拥有的前端路由信息 后端系统 后端系统是基于Springboot+shiro构建的,整个系统的核心就在shiro的配置上 @Configuration public class ShiroConfig { private static ...
  • 当前端需要携带cokie访问后端(axios.defaults.withCredentials = true),需要在服务端配置该类,主要目的:session不会再请求中刷新 public class CorsConfig extends WebMvcConfigurerAdapter { @Override public ...
  • 简单回顾cookie和sessioncookie和session都是回话管理的方式Cookiecookie是浏览器端存储信息的一种方式服务端可以通过响应浏览器set-cookie标头(header),浏览器接收到这个标头信息后,将以文件形式将cookie信息保存...
  • vue部署在本地的81端口,springboot部署在本地的80端口.设置了跨域访问来进行开发,但是在登陆成功之后设置session,其他接口来调用session的时候失效,显示null 解决方法 第一步 前端 因为我这里前端使用的axios来进行...
  • Springboot+vue 前后端分离出现后端校验验证码时出现获取session中的值为null的情况。 最近在写前后端分离的项目时,使用到后端验证码的情况。一般是在前端先get请求获取后端图片,并且将生成的验证存储到session中...
  • &nbsp;<a class="button button-little bg-blue" onclick="return confirm('你确定要退出吗')" v-on:click="loginout()"><span class="icon-power-off"></span>...2.vue方法 met...
  • 最近公司有新项目,我负责项目架构搭建,经过对业务需求和技术对比,选定springboot2.1.4+vue3.11前后端分离开发框架,本文就前后端分离遇到的问题进行总结. 1.跨域问题 相信这也是很多小伙伴们遇到的最多也最频繁的问题...
  • 记录一个很小但又搞得我蛋疼的问题:我在登陆之后将token和用户数据存到session当中,可当我在后面的接口取值时,为null 解决过程: 接下来在调试的时候发现每次请求sessionid不一致,期初我还是以为是跨域造成的...
  • vue axios中加上这段代码 axios.defaults.withCredentials = true; 完整代码 axios.interceptors.request.use( config => { config.headers.token = "aaa" config.baseURL = "http://localhost:8081/" axios....
  • SpringBoot+VUE跨域问题

    千次阅读 2019-03-19 14:57:06
    项目为前后端分离,后端项目使用Maven多模块项目+SpringBoot,前端使用 VUE。 后端在登录接口保存当前用户信息到session,前端请求后端接口,后端拦截器无法从session中获取当前登录的用户,前端出现以下跨域报错 ...
  • 1.前言当决定前端与后端代码分开部署时,发现shiro自带的session不起作用了。然后通过对请求head的分析,然后在网上查找一部分解决方案。最终就是,登录成功之后,前端接收到后端传回来的sessionId,存入cookie当中...
  • 1.前言当决定前端与后端代码分开部署时,发现shiro自带的session不起作用了。然后通过对请求head的分析,然后在网上查找一部分解决方案。最终就是,登录成功之后,前端接收到后端传回来的sessionId,存入cookie当中...
  • 简单回顾cookie和session cookie和session都是回话管理的方式 Cookie cookie是浏览器端存储信息的一种方式 服务端可以通过响应浏览器set-cookie标头(header),浏览器接收到这个标头信息后,将以文件形式将cookie...
  • 当决定前端与后端代码分开部署时,发现shiro自带的session不起作用了。 然后通过对请求head的分析,然后在网上查找一部分解决方案。 最终就是,登录成功之后,前端接收到后端传回来的sessionId,存入cookie当中。 ...
  • b5、如此就是前后端分离的SSO,后台是Springboot,前端是Vue,SSO服务是独立的CAS服务器 备注:关于前后端分离的文章,在网上翻了遍,也没有找到合适的,最终还是摸索中前进,找到了替代方案,这个方案感觉差点意思...

空空如也

空空如也

1 2 3 4 5 ... 10
收藏数 184
精华内容 73
关键字:

sessionspringbootvue

vue 订阅
spring 订阅