精华内容
下载资源
问答
  • JS异常处理:Uncaught SyntaxError: Unexpected token ) , VM242:1 今天在做项目时,在页面上添加了一个点击事件,结果报错了,chrome浏览器控制台查看 发现错误,但是你又找不到错误具体在哪一行,只显示了VM...

    JS异常处理:Uncaught SyntaxError: Unexpected token ) , VM242:1


    今天在做项目时,在页面上添加了一个点击事件,结果报错了,chrome浏览器控制台查看
    发现错误,但是你又找不到错误具体在哪一行,只显示了VM242:1,无法定位在哪一行报错:

    我刚才仅仅在<a>标签里添加了一个点击事件。那么,是不是刚才页面上添加事件出错了?

    <a href="javascript:void();" class="indbtn_search" onclick="search();">搜索</a>
    仔细看,好像没什么问题? 实际上是有问题的!

    引发此错误的原因:
    href属性值“javascript:void()”,括号中没有加“0”,表达式“0”不能少。

    void 操作符用法格式如下:
    ①. javascript:void(expression);
    ②. javascript:void expression

    expression 是一个要计算的 Javascript 标准的表达式。表达式外侧的圆括号和分号是可选的,但是写上去是一个好习惯。 (实现版本  Navigator 3.0  )
    你以使用 void 操作符指定超级链接。表达式会被计算但是不会当前文档处装入任何内容。

    正确写法是
    <a href="javascript:void(0);" class="indbtn_search" onclick="search();">搜索</a>
    
    //或者,去掉void(0)
    <a href="javascript:;" class="indbtn_search" onclick="search();">搜索</a>


    参考资料:

    【1】<a>标签中 href 和 onclick 的区别,以及 onclick(this) 传递this参数详解 - chunlynn的小屋 - CSDN博客


    展开全文
  • 文章内容输出来源:拉勾教育大前端高薪训练营; 本文实例代码使用的是vue+axiosÏ 什么是Token Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个... refresh_token:.

    本文实例代码使用的是vue+axiosÏ

    什么是Token

    Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。

    请求后台返回的登录数据一般情况如下

    {
        access_token:"加密的字符串",
        expires_in:"7200",
        refresh_token:"加密的字符串",
    }
    
    • access_token (访问令牌,用于资源访问)
    • refresh_token ( 当访问令牌失效,使用这个令牌重新获取访问令牌)
    • expire_in( access_tokenÏ过期时间)

    基本使用

    因为业务模式多种多样所以使用方法也是有很多的(ps:主要是后端想要什么,我们就给什么),比较常见的是Header中携带Token。

    Token获取及使用

    • 接口封装
      /**
       * 用户请求模块
       */
      export const login = (dataÏ) => request({
        url: '/front/user/login',
        method: 'POST',
        data: qs.stringify(data)
      })
      
    • VueX基本使用
      export default new Vuex.Store({
        state: {
          // 初始化
          user: null
        },
        mutations: {
          // 设置用户登录信息
          setUser(state, payload) {
            //因目前后端返回的是json字符串,所以我转义了一下
            payload = JSON.parse(payload)
            //如果pyload中没有过期时间并且存在过期时间长度
            if (!payload.expires_at && payload.expires_in) {
              //设置过期时间
              payload.expires_at = new Date().getTime() + payload.expires_in * 1000
            }
            //赋值
            state.user = payload
          }
        },
        actions: {
        },
        modules: {
        }
      })
      
    • 登录页面使用(伪代码)
      import Vue from 'vue'
      import { login } from '@/services/user'
      export default Vue.extend({
        name: 'LoginIndex',
        data() {
          return {
            formData: {
              phone: '18201288771',
              password: '111111'
            }
          }
        },
        methods: {
          async submit() {
            try {
              const { data } = await login(this.formData)
              // 处理请求结果
              if (data.state !== 1) {
                this.$message.error(data.message)
              } else {
                // 使用vuex中的setUser共享登录信息
                this.$store.commit('setUser', data.content)
                this.$message.success('登录成功')
              }
            } catch (error) {}
            this.isLoading = false
          }
        }
      })
      
    • axios请求拦截器
      // 请求拦截器,每一个请求都会经过此拦截器。
      request.interceptors.request.use((config) => {
        // 在请求的header中设置token
        config.headers.Authorization = store.state?.user?.access_token
        return config
      }, (error) => {
        return Promise.reject(error)
      })
      

    优化——授权过期登录重新返回页面

    在这里插入图片描述

    request.js中

    // 跳转至首页封装
    const redirectLogin = () => {
      router.push({
        name: 'login',
        query: {
          // 通过参数传 登录成功后的跳转地址
          redirect: router.currentRoute.fullPath
        }
      })
    }
    

    登录页面

     methods: {
        // 登录请求方法
        async submit() {
          try {
            const { data } = await login(this.formData)
            // 处理请求结果
            if (data.state !== 1) {
              //..... 登录失败处理逻辑
            } else {
              //..... 登录成功处理逻辑
              // 登录成功后进行路由跳转
              this.$router.push((this.$route.query.redirect as string) || '/')
            }
          } catch (error) {}
        }
    }
    

    优化——页面刷新Token丢失

    export default new Vuex.Store({
      state: {
        // 初始化时从本地存储中获取
        user: JSON.parse(window.localStorage.getItem('user') || 'null')
      },
      mutations: {
        //设置用户登录信息
        setUser(state, payload) {
         //因目前后端返回的是json字符串,所以我转义了一下
          payload = JSON.parse(payload)
          //如果pyload中没有过期时间并且存在过期时间长度
          if (!payload.expires_at && payload.expires_in) {
            //设置过期时间
            payload.expires_at = new Date().getTime() + payload.expires_in * 1000
          }
          //赋值
          state.user = payload
          //每次设置用户登录信息都存储值本地存储
          window.localStorage.setItem('user', JSON.stringify(payload))
             }
      },
      actions: {
      },
      modules: {
      }
    })
    

    过期维护

    过期维护存前端存在两种方式

    • 在请求发起前拦截每个请求,判断token的有效时间是否已经过期。若已过期,则将请求挂起,先刷新token后在继续请求。
      • 优点:请求前拦截,节省请求及流量
      • 缺点:需要后端额外提供过期时间字段,若本地时间与服务器时间不一致可能存在拦截失败。
    • 不在请求前拦截,而是拦截返回后的数据。先放弃请求,接口返回过期后,先刷新token,在进行一次重试。
      • 优点:不需要额外的token过期字段及判断时间
      • 缺点:会消耗多一次请求,耗流量

    请求发起前拦截

    // 跳转首页逻辑
    const redirectLogin = () => {
      router.push({
        name: 'login',
        query: {
          redirect: router.currentRoute.fullPath
        }
      })
    }
    
    // 刷新token后的任务队列
    let refreshTokenArray = []
    
    /**
     * 刷新token,重新请求
     */
    const refreshTokenFn = async () => {
      // 判断是否有刷新token
      const refreshToken = store.state?.user?.refresh_token || ''
      // 如果刷新token存在
      if (refreshToken) {
        // 使用重新创建的axios请求,防止递归调用
        const { data } = await axios.create()({
          method: 'POST',
          url: '/front/user/refresh_token',
          data: qs.stringify({
            refreshtoken: refreshToken
          })
        })
        //如果获取token失败 抛出异常
        if (!data.content) throw new Error('refreshToken is faild')
        // 重新设置token
        store.commit('setUser', data.content)
        return true
      }
      throw new Error('refreshToken not find')
    }
    
    // 请求拦截器
    request.interceptors.request.use(async (config: Config) => {
      // 获取用户登录信息
      const user = store.state?.user
      // 判断access_token 是否过期且接口是否需要token
      if (config.isAuthToken && user.expires_at < new Date().getTime()) {
        // 是否正在执行刷新token
        if (!refreshTokenLoding) {
          try {
            //刷新token锁为true
            refreshTokenLoding = true
            await refreshTokenFn()
            // 执行获取token后的任务队列
            refreshTokenArray.forEach(item => item())
            //清空任务队列
            refreshTokenArray = []
            return config
          } catch (error) {
            // 如果刷新失败跳转登录页面
            redirectLogin()
          } finally {
            // 无论成功失败消除
            refreshTokenLoding = false
          }
        } else {
          // 如果这正在刷新,返回一个 Promise ,并向刷新token成功后执行队列push 函数.
          return new Promise(resolve => {
            refreshTokenArray.push(() => {
              // 返回config请求对象
              resolve(config)
            })
          })
        }
      }
      return config
    })
    

    请求发起后拦截

    //相应拦截器
    request.interceptors.response.use((response) => {
      // 2xx 会进入这里
      return response
    }, async (error) => {
      // 判断是否是授权错误
      if (error.response === 401) {
        // 是否正在刷新
        if (!refreshTokenLoding) {
          refreshTokenLoding = true
          // 尝试使用 refresh_token 获取新的 access_token
          try {
            // 执行刷新token
            await refreshTokenFn()
            // 执行刷新后任务队列
            refreshTokenArray.forEach(item => item())
            //清除任务队列
            refreshTokenArray = []
            // 重发当前请求
            return request(error.config)
            // 如果成功 则重发上次请求
          } catch (error) {
            // 如果失败 跳转至登录
            redirectLogin()
          } finally {
            refreshTokenLoding = false
          }
        } else {
          // 如果当前正在请求
          return new Promise(resolve => {
            // 当前请求的config投递至刷新后的任务队列中
            refreshTokenArray.push(() => {
              resolve(request(error.config))
            })
          })
        }
      }
      //... 其他异常捕获
    })
    

    结束语

    虽然token大家平常工作中都会使用,但是我见过太多的项目token使用上存在误区。例如为了避免token过期问题,让token的有效期为一周,还有些人甚至设置了一年(手动滑稽)。还有一些人只设置了拦截器,例如请求发现token过期或者后端返回了401,直接让用户跳转至登录页面,这样的用户体验真的很不优化。

    代码地址: https://gitee.com/a20070322/edu-boss-fed

    展开全文
  • 后台不用保存token,只需要验证是否是自己签发的token 支持多种前段,如移动端和浏览器 缺点和解决方法 每次都要去数据库查询权限信息验证token 解决: 将查询到的权限数据保存到session中,之后可以直接从session...

    @TOC

    1、基本概念

    为什么要使用token

    优点

    1. 后台不用保存token,只需要验证是否是自己签发的token
    2. 支持多种前段,如移动端和浏览器

    缺点和解决方法

    每次都要去数据库查询权限信息验证token
    解决:
    将查询到的权限数据保存到session中,之后可以直接从session中获取
    也可以使用redisJ解决

    2、代码实操

    tokenVo

    过期时间:方便前端人员判断是否置换,在快过期但用户又有新操作时需要置换

    package com.bean.vo;
    import java.io.Serializable;
    /**
     * 返回前端-Token相关VO
     */
    public class TokenVO implements Serializable {
    
    	/** 用户认证凭据 */
    	private String token;
    	/** 过期时间 */
    	//通常是总毫秒数: token生成时间+有效时长
    	private long expTime;
    	/** 生成时间 */
    	//通常是总毫秒数
    	private long genTime;
    
    	public String getToken() {
    		return token;
    	}
    	public void setToken(String token) {
    		this.token = token;
    	}
    	public long getExpTime() {
    		return expTime;
    	}
    	public void setExpTime(long expTime) {
    		this.expTime = expTime;
    	}
    	public long getGenTime() {
    		return genTime;
    	}
    	public void setGenTime(long genTime) {
    		this.genTime = genTime;
    	}
    	
    	public TokenVO() {
    		super();
    	}
    	public TokenVO(String token, long expTime, long genTime) {
    		super();
    		this.token = token;
    		this.expTime = expTime;
    		this.genTime = genTime;
    	}
    }
    

    2.1、前端

    localStorage(可以将token保存到这里)
    res.data是dto的data属性

     <script type="text/javascript">
    	  $(".submit").bind("click", function () {
    		  $.post(
    		  		"/auth/login",
    				$("#actionForm").serialize(),
    			    function (res) {
    					//判断
    					if (res.success == "true"){ //登录成功
    						//将token保存到localStorage
    						localStorage.token = res.data.token;
    						//保存token过期时间
    						localStorage.tokenExpire = res.data.expTime;
    						//跳转页面
    						location = "/page/main.html";
    					}else {
    						//显示错误提示
    						$(".info").html(res.msg);
    					}
    				}
    		  );
    	  })
      </script>
    
    package com.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class ViewController {
    
        @RequestMapping("/")
        public String base(){
            System.out.println(">>> base");
            return "/page/login.html";
        }
    }
    
    

    TokenUtil.java

    session时自动刷新,而token要前端来请求,所以token的置换剩余时间少于1个小时
    UserAgentInfo类来自于辅助工具依赖 的插件

    package com.util;
    
    import com.alibaba.fastjson.JSON;
    import cz.mallat.uasparser.UserAgentInfo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Arrays;
    import java.util.Calendar;
    import java.util.Date;
    
    @Component
    public class TokenUtil {
    
        @Autowired
        private RedisUtil redisUtil;
    
        private int session_timeout = 60 * 60 * 2; //token有效时长
    
        private int replacement_protection_timeout = 60 * 60; //token置换保护时长
    
        private int replacement_delay= 60 * 2; //旧token延迟过期时间
    
        private String token_prefix = "token:";//token前缀
    
        /** 获取token默认有效时长 */
        public int getTimeout(){
            return session_timeout;
        }
    
        /** 获取登录时间的总毫秒数 */
        public long getLogin(String token) throws ParseException {
            //拆分token
            String[] arr = token.split("[-]");
    
            return new SimpleDateFormat("yyyyMMddHHmmss").parse(arr[3]).getTime();
        }
    
        /** token是否存在 */
        public boolean exists(String token){
            return redisUtil.exists(token);
        }
    
        /**
         * 保存token
         * @param token
         * @param user
         */
        public void save(String token, Object user) {
            //判断, token是否以"token:PC-"开头
            if (token.startsWith(token_prefix + "PC-")){
                //PC端登录有效期2小时
                redisUtil.setString(token, JSON.toJSONString(user), session_timeout);
            }else {
                //移动端登录永久有效
                redisUtil.setString(token, JSON.toJSONString(user));
            }
        }
    
        /**
         * 读取token中的对象
         * @param token
         * @param clazz
         * @return
         */
        public Object load(String token, Class clazz){
            return JSON.parseObject(redisUtil.getString(token), clazz);
        }
    
        /**
         * 删除token
         * @param token
         */
        public void delete(String token) throws Exception {
            //判断, token在redis中是否存在
            if (!redisUtil.exists(token))
                throw new Exception();
            //删除token
            redisUtil.delete(token);
        }
    
        /**
         * 生成token
         * @param name 用户账号
         * @param id 用户id
         * @param agent 设备信息
         * @return token<br>
         *     格式:<br>
         *         PC端: 前缀PC-32位的加密name-id-yyyyMMddHHmmss-6位加密的agent<br>
         *         移动端: 前缀MOBLIE-32位的加密name-id-yyyyMMddHHmmss-6位加密的agent
         */
        public String generateToken(String name, Object id, String agent){
    
            StringBuilder sb = new StringBuilder();
    
            //添加token前缀
            sb.append(token_prefix);
    
            //添加设备类型
            try {
                //获取终端类型
                String deviceType = getDeviceType(agent);
                //添加到token
                sb.append(deviceType + "-");
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
    
            //添加账号(32位加密)
            sb.append(MD5.getMd5(name, 32) + "-");
    
            //添加id
            sb.append(id + "-");
    
            //添加登录时间, 格式yyyyMMddHHmmss
            sb.append(new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "-");
    
            //添加设备信息(6加密)
            sb.append(MD5.getMd5(agent, 6));
    
            return sb.toString();
        }
    
        /**
         * 验证token
         * @param token
         * @param agent 设备信息
         * @param type 验证类型<br>
         *     1: 基本验证. 包含结果1,11,12,13,20
         *     2: 置换验证. 包含结果1,11,12,14
         * @return 结果数值<br>
         *     11: token不存在<br>
         *     12: token格式有误<br>
         *     13: token已超时<br>
         *     14: token仍在保护期<br>
         *     20: 其它token异常<br>
         *     1: token正常
         */
        public int validate(String token, String agent, String type){
            // token不存在
            if (!exists(token)) return 11;
            //拆分token字符串
            String[] tokenDetails = token.split("-");
            //获取token生成时间的总毫秒数
            long genTime = 0;
            try {
               genTime = getLogin(token);
            } catch (ParseException e) {
                e.printStackTrace();
                return 12;
            }
            //计算登录时长
            long passed = Calendar.getInstance().getTimeInMillis() - genTime;
            System.out.println(passed);
            System.out.println(passed / 1000 / 60);
            //判断, 验证类型
            if (type.equals("1")){
                //判断, 是否已超时
                if (passed > session_timeout * 1000) return 13;
                //获取登录设备加密信息
                String agentMD5 = tokenDetails[4];
                //判断, 当前设备是否与登录设备一致
                if(MD5.getMd5(agent, 6).equals(agentMD5)) return 1;
            }else {
                //判断, 是否处于置换保护期内
                if (passed < replacement_protection_timeout * 1000) return 14;
                return 1;
            }
    
            return 20;
        }
    
        /**
         * 置换token
         * @param token
         * @param agent 设备信息
         * @param clazz 置换对象的类型
         * @param nameFieldName 账号属性名
         * @param idFiledName id属性名
         * @return
         */
        public String replaceToken(String token, String agent, Class clazz, String nameFieldName, String idFiledName) {
            //获取旧token中存储的用户数据
            Object user = load(token, clazz);
            System.out.println(user);
    
            //获取旧token有效期(剩余秒数 )
            long ttl = redisUtil.getExpire(token);
    //        System.out.println("ttl:" + ttl);
            //判断token是否仍在有效期内容
    //        if (ttl > 0 || ttl == -1) {
                Object name = ""; //用户账号
                Object id = ""; //用户id
                try {
                    //获取用户账号
                    Method getName = clazz.getDeclaredMethod(nameFieldName);
                    name = getName.invoke(user);
                    //获取用户id
                    Method getId = clazz.getDeclaredMethod(idFiledName);
                    id = getId.invoke(user);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                //生成新token
                String newToken = this.generateToken(name.toString(), id.toString(), agent);
                //保存新token
                this.save(newToken, user);
                //设置2分钟后旧token过期,注意手机端由永久有效变为2分钟后失效
                redisUtil.setString(token, JSON.toJSONString(user), replacement_delay);
    
                return newToken;
    //        }
    
    //        return null;
        }
    
        /**
         * 获取设备类型
         * @param agent
         * @return
         */
        public String getDeviceType(String agent) throws IOException {
            //用户设备信息对象
            UserAgentInfo userAgentInfo = UserAgentUtil.getUasParser().parse(agent);
            //判断, 设备类型是否未知
            if (userAgentInfo.getDeviceType().equals(UserAgentInfo.UNKNOWN)) {
                //判断, 已知设备类型是否为移动端
                if (UserAgentUtil.CheckAgent(agent)) return "MOBILE";
                //已知设备不是移动端, 则为PC端
                else return "PC";
            }
            //判断, 设备类型是否为PC端
            else if (userAgentInfo.getDeviceType().equals("Personal computer")) return "PC";
            //其他类型均识别为移动端
            else return "MOBILE";
        }
    }
    
    

    控制类

    登录方法

     @PostMapping("/login")
        @ResponseBody
        public Dto login(User user, HttpServletRequest request){
            System.out.println(">>> 用户登录");
            System.out.println(user);
    
            /* 数据验证(略) */
    
            /* 登录查询 */
            List<User> userList = userService.find(user);
    
            /* 处理查询结果 */
            //判断, 集合大小
            if (userList.size() != 1)
                return new Dto("用户名或密码错误!", "100001");
    
            //获取登录用户对象
            user = userList.get(0);
    
            //判断, 账号状态
            if (user.getStatus() == 0)
                return new Dto("账号未激活, 请先激活账号!", "100002");
    
            //登录成功,就要生成token
            String token = tokenUtil.generateToken(user.getUserName(), user.getId(),
                    request.getHeader("user-agent"));
    
            //保存token
            tokenUtil.save(token, user);
    
            try {
                //获取tokenVo
                TokenVO tokenVO = new TokenVO(token,
                        tokenUtil.getLogin(token) + tokenUtil.getTimeout()*1000,
                        tokenUtil.getLogin(token));
    
                return new Dto(tokenVO);
    
            } catch (ParseException e) {
                e.printStackTrace();
                return new Dto("token格式有误!", "100003");
            }
    
        }
    
        @PostMapping("/registe/mail")
        @ResponseBody
        public Dto registeByEmail(User user){
            System.out.println(">>> 用户注册--邮箱");
            System.out.println(user);
    
            //添加用户
            userService.addUserByMail(user);
    
            return new Dto("注册成功, 请尽快激活账号!");
        }
    

    启动启动类
    启动nginx和redis
    就可以访问了

    获取header中的token

    有时候我们登录之后要去token中拿数据
    在这里插入图片描述

     @GetMapping("/login/info")
        @ResponseBody
        public Dto getLoginInfo(HttpServletRequest request){
            System.out.println(">>> 获取登录用户信息");
    
            //获取header中的token
            String token = request.getHeader("token");
            System.out.println(token);
    
            //验证token是否正常
            int result = tokenUtil.validate(token, request.getHeader("user-agent"), "1");
    
            //根据验证结果的所有异常
            switch (result){
                case 11: 
                    return new Dto("token不存在", "200011");
                case 12:
                    return new Dto("token格式有误", "200012");
                case 13:
                    return new Dto("token已超时", "200013");
                case 20:
                    return new Dto("token异常", "200020");
            }
    
            //获取token中的对象
            User user = (User) tokenUtil.load(token, User.class);
    
            //返回
            return new Dto(user);
        }
    

    前端
    每次需要登录向后台请求都要带上 headers

    	  /* 加载登录用户信息 */
    	  $.ajax({
    	     type: "get",
    		 url: "/auth/login/info",
    		 success: function (res) {
    			 if (res.success == "true") {
    				 $("#loginUserName").html(res.data.realName);
    			 }else {
    				 $("#loginUserName").html("游客(未登录)");
    			 }
    		 },
    	     headers: {
    		 	token: localStorage.token
    		 }
    	  });
    

    置换token,退出

    置换

    前端需要判断token是否过期,如果过期就需要置换(如果需要登录才能访问的情况下)

    前端

    	 function parseToken(token){
    		  //拆分token
    		  var arr = token.split("-");
    		  //获取年
    		  var year = arr[3].substring(0, 4);
    		  //获取月
    		  var month = arr[3].substring(4, 6);
    		  //获取日
    		  var date = arr[3].substring(6, 8);
    		  //获取时
    		  var hours = arr[3].substring(8, 10);
    		  //获取分
    		  var min = arr[3].substring(10, 12);
    		  //获取秒
    		  var sec = arr[3].substring(12);
    
    		  return new Date(year, month-1, date, hours, min, sec);
    		  // alert(d);
    	  }
    
    	  replaceToken();
    
    	  //置换token
    	  function replaceToken() {
    		  //获取token
    		  var token = localStorage.token;
    		  //获取过期时间
    		  var expTime = localStorage.tokenExpire;
    		  // alert(expTime)
    		  // var date = parseToken(token);
    
    		  //获取剩余登录时间的毫秒数
    		  var loginTimeLeft =  expTime - new Date().getTime();
    		  // alert(loginTimeLeft < 1000 * 60 * 60);
    		  //判断, 是否需要置换token
    		  if (loginTimeLeft < 1000 * 60 * 60){
    		  	$.ajax({
    				headers: {
    					token: localStorage.token
    				},
    				type: "get",
    				url: "/auth/token/replace",
    				success: function (res) {
    					if (res.success == "true"){
    						localStorage.token = res.data.token;
    						localStorage.tokenExpire = res.data.expTime;
    					}
    					// else{
    					// 	alert(res.msg);
    					// }
    				}
    			});
    		  }
    	  }
    
    

    控制类

     @GetMapping("/token/replace")
        @ResponseBody
        public Dto replaceToken(HttpServletRequest request){
            System.out.println(">>> 置换token");
    
            //获取header中的token
            String token = request.getHeader("token");
            System.out.println(token);
    
            //验证token是否正常
            int result = tokenUtil.validate(token, null,"2");
    
            try {
    //            System.out.println(tokenUtil.getLogin(token));
                System.out.println(new Date(tokenUtil.getLogin(token)));
                System.out.println(tokenUtil.getTimeout());
            } catch (ParseException e) {
                e.printStackTrace();
            }
    
            //根据验证结果的所有异常
            switch (result){
                case 11:
                    return new Dto("token不存在", "200011");
                case 12:
                    return new Dto("token格式有误", "200012");
                case 14:
                    return new Dto("token已超时", "200013");
            }
    
            //置换token, 获取新token
            token = tokenUtil.replaceToken(token, request.getHeader("user-agent"),
                    User.class, "userName", "id");
    
            //判断, 新token
            if (token == null)
                return new Dto("token置换失败!", "200030");
    
    
    
            try {
                TokenVO tokenVO = new TokenVO(token,
                        tokenUtil.getLogin(token) + tokenUtil.getTimeout()*1000,
                        tokenUtil.getLogin(token));
    
                return new Dto(tokenVO);
    
            } catch (ParseException e) {
                e.printStackTrace();
                return new Dto("token格式有误!", "100003");
            }
        }
    
        @GetMapping("/login/info")
        @ResponseBody
        public Dto getLoginInfo(HttpServletRequest request){
            System.out.println(">>> 获取登录用户信息");
    
            //获取header中的token
            String token = request.getHeader("token");
            System.out.println(token);
    
            //验证token是否正常
            int result = tokenUtil.validate(token, request.getHeader("user-agent"), "1");
    
            //根据验证结果的所有异常
            switch (result){
                case 11:
                    return new Dto("token不存在", "200011");
                case 12:
                    return new Dto("token格式有误", "200012");
                case 13:
                    return new Dto("token已超时", "200013");
                case 20:
                    return new Dto("token异常", "200020");
            }
    
            //获取token中的对象
            User user = (User) tokenUtil.load(token, User.class);
    
            //返回
            return new Dto(user);
        }
    

    退出

    验证,是否是我记录的token,以及是否是同个设备
    前端

     <a href="javascript:void(0)" id="logoff">退出</a>
     function replaceToken() {
    		
    	  $("#logoff").bind("click", function () {
    
    	  	if (!confirm("确定退出系统吗?")) return;
    
    		$.ajax({
    			headers: {
    				token: localStorage.token
    			},
    		  	url: "/auth/logoff",
    			type: "get",
    			success: function (res) {
    				if (res.success == "true") {
    				#清除
    					localStorage.token = null;
    					localStorage.tokenExpire = null;
    					location = "/page/login.html";
    				}
    			}
    		});
      })
    

    后台

     @GetMapping("/logoff")
        @ResponseBody
        public Dto logoff(HttpServletRequest request){
            System.out.println(">>> 退出登录");
    
            //获取header中的token
            String token = request.getHeader("token");
            System.out.println(token);
    
            //删除token
            try {
                tokenUtil.delete(token);
            } catch (Exception e) {
                e.printStackTrace();
                return new Dto("token不存在!", "100011");
            }
    
            return new Dto("退出成功!");
        }
    
    展开全文
  • 而现在这种基于token的登录方式,是在登录成功 后 将用户信息存入到客户端中,不会额外占用服务端的内存资源,并且通过签名和验签可以保证数据不被篡改, 又因为登录成功后是将用户的信息存入到客户端中,所以在进行...

    java实现基于JWT的token登陆认证

    前言

    之前基于session的登录方式,是在用户登录成功后将用户信息存入到session中,这样不利于程序的横向扩展, 如果将项目部署多份,会出现session漂移的问题,并且随着登录用户的增加,会不断的占用服务端的内存资源;而现在这种基于token的登录方式,是在登录成功 后 将用户信息存入到客户端中,不会额外占用服务端的内存资源,并且通过签名和验签可以保证数据不被篡改,
    又因为登录成功后是将用户的信息存入到客户端中,所以在进行横向扩展,部署多份的时候,不会产生session漂移的问题。

    在项目中的使用

    导入所需依赖

      <!--引入JWT依赖,由于是基于Java,所以需要的是java-jwt-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
            <dependency>
                <groupId>com.auth0</groupId>
                <artifactId>java-jwt</artifactId>
                <version>3.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.4</version>
            </dependency>
    

    项目后台代码

    登录认证相关代码

    写一个简单的登陆测试一下怎么使用token

    bean

    public class User {
        private Long userId;
        private String userName;
        private String password;
    }
    

    核心方法

    package com.fh.jwt;
    
    import com.fh.util.response.ResponseServer;
    import com.fh.util.response.ServerEnum;
    import io.jsonwebtoken.*;
    import sun.misc.BASE64Encoder;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    public class JwtTokenUtils {
    
        public static  String createToken(Map<String,Object> map){
            //jwt如何生成字符串
            //声明头部信息
            Map<String,Object> headerMap=new HashMap<String,Object>();
            headerMap.put("alg","HS256");
            headerMap.put("typ","JWT");
            //设置负载:不要放着涉密的东西比如:银行账号密码,余额,身份证号
            Map<String,Object> payload=new HashMap<String,Object>();
            payload.putAll(map);
            Long iat=System.currentTimeMillis();
            //设置jwt的失效时间 一分钟
            Long endTime = iat+60000l;
    
            //签名值就是我们的安全密钥
            String token=Jwts.builder()
                    .setHeader(headerMap)
                    .setClaims(payload)
                    .setExpiration(new Date(endTime))
                    .signWith(SignatureAlgorithm.HS256,getSecretKey("secretKey"))
                    .compact();
            return token;
        }
    
        public static ResponseServer resolverToken(String token ){
            Claims claims=null;
            try {
                claims = Jwts.parser()
                        .setSigningKey(getSecretKey("secretKey"))
                        .parseClaimsJws(token)
                        .getBody();
    
            }catch (ExpiredJwtException exp){
                System.out.println("token超时,token失效了");
                return ResponseServer.error(ServerEnum.TOKEN_TIMEOUT);
            }catch (SignatureException sing){
                System.out.println("token解析失败");
                return ResponseServer.error(ServerEnum.SAFETY_ERROR);
            }
            return ResponseServer.success(claims);
        }
        private  static String getSecretKey(String key){
            return new BASE64Encoder().encode(key.getBytes());
        }
    
    }
    

    登录认证相关代码

    控制层

    package com.fh.controller;
    
    import com.fh.jwt.JwtTokenUtils;
    import com.fh.model.User;
    import com.fh.service.UserService;
    import com.fh.tokenauth.TokenCheckAnnotation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @CrossOrigin
    @RestController
    @RequestMapping("user")
    public class UserController {
    
        @Autowired
        public UserService service;
    
        @RequestMapping("login")
        public Map<String,Object> login(String userName,String password){
            HashMap<String, Object> map = new HashMap<>();
             User u =  service.queryUser(userName);
            if (u == null) {
                map.put("code",3000);
                return map;
            }
    
            // 生成token
            Map<String,Object> m = new HashMap<>();
            m.put("id",u.getUserId());
            String token = JwtTokenUtils.createToken(m);
            map.put("token",token);
            return map;
        }
    
    	//@TokenCheckAnnotation 是自定义的注解,在需要进行登录认证的所有方法加上注解
        //客户端访问本方法时会拦截 
        @TokenCheckAnnotation
        @RequestMapping("test")
        public void test(){
            System.out.println("我被调用了");
        }
    }
    

    自定义注解

    package com.fh.tokenauth;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD) // 修饰范围
    @Retention(RetentionPolicy.RUNTIME) // 用来描述注解的声明周期
    public @interface TokenCheckAnnotation {
    }
    
    

    登录验证

    附一个随便找的状态码

    就用到了几个,其他的都用不到

    package com.fh.util.response;
    
    public enum ServerEnum {
    
        SUCCESS(200,"操作成功"),
        DEL_DEPT_SCUCCESS(201,"删除部门成功"),
        LOGIN_ISNULL(5000,"用户名或者密码为空"),
        PHONE_ISNULL(5007,"手机号不能为空"),
        USERNAME_NOTEXIST(5001,"用户名输入有误。"),
        PASSWORD_WRONG(5002,"密码输入错误,请检查"),
        LOGIN_SUCCESS(5003,"登陆成功"),
        LOGIN_EXPIRED(5004,"登录超时,请重新登陆"),
        SECRET_ERROR(5005,"传入的token值有误,不能通过签名验证"),
        TOKEN_TIMEOUT(5006,"登录失效,请重新登录"),
        TOKEN_ISNULL(5008,"获取到的Token值为空"),
        NO_MENU_RIGHT(6000,"没有权限访问该菜单,请联系管理员"),
        NOT_DATA(7001,"没有要导出的数据"),
        HTTP_URL_ISNULL(8002,"你传递的URL路径为空了"),
        SERVER_TIMEOUT(8004,"服务连接请求超时"),
        HTTP_ERROR(8003,"接口访问失败"),
        SERVER_STOP(8005,"服务连接不上"),
        SAFETY_ERROR(9000,"接口验签失败"),
        SAFETY_BAD(9001,"接口被非法攻击"),
        SAFETY_TIMEOUT(9002,"接口访问超时"),
        SAFETY_INVALID(9003,"签名值无效"),
        SAFETY_REPLAY_ATTACK(9004,"接口被重放攻击"),
        LOGIN_PHONEORCODE_INNULL(10000,"手机号或者验证码为空了"),
        LOGIN_CODE_ERROR(10001,"手机验证码输入有误"),
        ALL_STOCK_NULL(20001,"商品的库存都不足了"),
        NO_ORDER_TO_PAY(20002,"没有要支付的订单"),
        CRATER_PAY_ERROR(20003,"生成支付二维码失败"),
        PAY_TIMEOUT(20004,"支付超时请刷新页面"),
        ERROR(500,"操作失败");
    
        private ServerEnum(int code ,String msg){
            this.code=code;
            this.msg=msg;
        }
    
        private Integer code;
    
        private String msg;
    
        public Integer getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    }
    

    对前端返回过来的数据进行判断

    package com.fh.util.response;
    
    public class ResponseServer {
    
        private Integer code;
    
        private String msg;
    
        private Object data;
    
    
        private ResponseServer(){
    
        }
        private ResponseServer(Integer code,String msg){
            this.code=code;
            this.msg=msg;
    
        }
        private ResponseServer(Integer code,String msg,Object data){
            this.code=code;
            this.msg=msg;
            this.data=data;
        }
    
        /**
         * 返回默认的 成功状态 200
         * @return
         */
        public static  ResponseServer success(){
            return new ResponseServer(ServerEnum.SUCCESS.getCode(),ServerEnum.SUCCESS.getMsg());
        }
    
        /**
         * 返回默认的带数据 成功状态 200
         * @param data
         * @return
         */
        public static  ResponseServer success(Object data){
            return new ResponseServer(ServerEnum.SUCCESS.getCode(),ServerEnum.SUCCESS.getMsg(),data);
        }
    
        /**
         * 其他特殊类型的成功状态,
         * @param serverEnum
         * @return
         */
        public static  ResponseServer success(ServerEnum serverEnum){
            return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg());
        }
    
        /**
         * 带返回数据的其他特殊类型的成功状态
         * @param serverEnum
         * @param data
         * @return
         */
        public static  ResponseServer success(ServerEnum serverEnum,Object data){
            return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg(),data);
        }
    
    
        //失败
            public static ResponseServer error(Integer code,String msg){
                return new ResponseServer(code,msg);
            }
        /**
    +     * @return
         */
        public static  ResponseServer error(){
            return new ResponseServer(ServerEnum.ERROR.getCode(),ServerEnum.ERROR.getMsg());
        }
    
        /**
         * 返回默认的带数据 失败状态 500
         * @param data
         * @return
         */
        public static  ResponseServer error(Object data){
            return new ResponseServer(ServerEnum.ERROR.getCode(),ServerEnum.ERROR.getMsg(),data);
        }
    
        /**
         * 其他特殊类型的失败状态,
         * @param serverEnum
         * @return
         */
        public static  ResponseServer error(ServerEnum serverEnum){
            return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg());
        }
    
        /**
         * 带返回数据的其他特殊类型的失败状态
         * @param serverEnum
         * @param data
         * @return
         */
        public static  ResponseServer error(ServerEnum serverEnum,Object data){
            return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg(),data);
        }
    
        public Integer getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public Object getData() {
            return data;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    
        public void setData(Object data) {
            this.data = data;
        }
    }
    

    异常处理

    验证异常

    package com.fh.exception;
    
    import com.fh.util.response.ServerEnum;
    
    public class AuthenticateException extends RuntimeException{
        private Integer code;
        public AuthenticateException(ServerEnum serverEnum) {
            super(serverEnum.getMsg());
            this.code=serverEnum.getCode();
        }
        public Integer getCode() {
            return code;
        }
    
    }	
    

    全局异常处理

    package com.fh.controller;
    
    import com.fh.exception.AuthenticateException;
    import com.fh.util.response.ResponseServer;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    
        @ExceptionHandler(AuthenticateException.class)
        public ResponseServer authenticateException(AuthenticateException e, HttpServletRequest request, HttpServletResponse response){
            return ResponseServer.error(e.getCode(),e.getMessage());
        }
    
        @ExceptionHandler(Exception.class)
        public ResponseServer exceptionHandler(Exception e,HttpServletRequest request, HttpServletResponse response){
                e.printStackTrace();
            return ResponseServer.error();
    
        }
    }
    

    登录拦截的AOP 将拦截器横切到程序中

    package com.fh.tokenauth;
    
    
    import com.fh.exception.AuthenticateException;
    import com.fh.jwt.JwtTokenUtils;
    import com.fh.util.response.ResponseServer;
    import com.fh.util.response.ServerEnum;
    import org.apache.commons.lang3.StringUtils;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import javax.servlet.http.HttpServletRequest;
    
    @Aspect // 作用是把当前类标识为一个切面供容器读取
    @Component // 把当前类交给spring管理
    public class TokenAuthenticateAop {
    	//切点表达式 aop横切的是 com.fh.controller 包下的 任意类 任意方法 任意参数
        @Around(value = "execution(* com.fh.controller.*.*(..)) && @annotation(tokenCheckAnnotation)")
        public Object tokenAuth(ProceedingJoinPoint joinPoint, TokenCheckAnnotation tokenCheckAnnotation){
            Object proceed = null;
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            String token = request.getHeader("Authorization-token");
            // 验证token是否为空
            if (!StringUtils.isNotBlank(token)){
                throw new AuthenticateException(ServerEnum.TOKEN_ISNULL);
            }
    
            // 验证token是否失效
            ResponseServer responseServer = JwtTokenUtils.resolverToken(token);
            if (responseServer.getCode() != 200){
                throw new AuthenticateException(ServerEnum.LOGIN_EXPIRED);
            }
    
            // 执行目标方法
            try {
                proceed = joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
    
            return proceed;
        }
    }
    

    前端

    前端采用vue+Element Ui

    使用安装命令安装vuex:

    npm install vuex --save
    

    新建vuex文件

    在新建一个store文件夹

    新建index.js

    import  Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    //声明一个token常量
    const store={
      state:{
        'token':""
      }
    }
    export default new Vuex.Store({
      store,
      mutations:{
        //给常量赋值
        set_token(state,token){
          state.token=token;
          localStorage.setItem("token",token);
        }
      }
      ,getters:{
        token : state => state.token
      }
    })
    

    让vuex生效,在main.js中

    import store from './store'
    new Vue({
      el: '#app',
      router,
      store, // 使vuex生效
      components: { App },
      template: '<App/>'
    })
    

    新建登录页面

    新建一个Login文件夹 在里面建一个Login.vue

    前端对密码进行了MD5加密,所有需要提前在main.js把MD5导入进来

    import md5 from 'js-md5'; // 导入MD5
    Vue.prototype.$md5 = md5;
    
    <template>
        <div >
          <el-form ref="form" :model="form" :rules="rules" label-width="80px" class="login-box demo-ruleForm" >
            <h3 class="login-title">欢迎登录</h3>
            <el-form-item label="用户名" prop="userName"> <!-- prop:将该字段设置为需检验 -->
              <el-input v-model="form.userName"></el-input>
            </el-form-item>
            <el-form-item label="密码" prop="password">
              <el-input type="password" v-model="form.password"></el-input>
            </el-form-item>
            <el-form-item>
              <el-button type="primary" @click="onSubmit('form')">登录</el-button>
              <el-button @click="resetForm('form')">重置</el-button>
            </el-form-item>
          </el-form>
        </div>
    </template>
    
    <script>
        export default {
          name: "Login",
          data(){
            return{
              form:{
                userName:'',
                password:'',
              },
              rules: {
                userName: [
                  { required: true, message: '请输入用户名', trigger: 'blur' },
                  { min: 3, max: 10, message: '长度在 3 到 10 个字符', trigger: 'blur' }
                ],
                password: [
                  { required: true, message: '请输入密码', trigger: 'blur' }
                ],
              }
            }
          },
          mounted(){
            //绑定事件
            window.addEventListener('keydown',this.keyDown);
          },
          methods:{
            keyDown(e){
              //如果是回车则执行登录方法
              if(e.keyCode == 13){
                this.onSubmit('form');
              }
            },
            onSubmit(formName){
              this.$refs[formName].validate((valid) => {
                var aa = this
                if (valid) {
                  this.form.password = this.$md5(this.form.password)
                  this.$axios.post("http://localhost:80/user/login",this.$qs.stringify(this.form)).then(res=>{
                    if(res.data.code != 3000){
    
                      // console.log(res.data.token)
                      // 登录成功以后将token放到vuex中
                      aa.$store.commit("set_token",res.data.token);
    
                      aa.$router.push("/house");
                    }else{
                      aa.$alert("用户名或密码不正确!")
                    }
                  }).catch(err => {
    
                  })
                } else {
                  console.log('error submit!!');
                  return false;
                }
              });
            },
            resetForm(form){
              this.$refs[form].resetFields();
            }
          },
        }
    </script>
    
    <style scoped>
      .login-box{
        width: 600px;
        border: 1px solid #DCDFE6;
        margin: 180px auto;
        padding: 35px 35px 15px 35px;
        border-radius: 30px;
        box-shadow: 0 0 10px aqua;
      }
      .login-title{
        text-align: center;
        margin: 0 auto 30px auto;
        color: #00FFFF;
      }
    </style>
    

    新建test页面

    新建house文件夹 创建一个登录成功之后跳转的页面index.vue

    <template>
        <div>
          <h1>我是House</h1>
            <el-button type="primary" @click="onSubmit()">点我测试</el-button>
        </div>
    </template>
    <script>
        export default {
            name: "index",
            methods:{
              onSubmit(){
                this.axios({
                  method:"get",
                  url: "http://localhost:80/user/test",
                })
              }
            }
        }
    </script>
    <style scoped>
    </style>
    

    配置这两个页面的路由

    在router中配置路由

    import Vue from 'vue'
    import Router from 'vue-router'
    import House from '@/views/house/index'
    import Login from '@/views/login/Login'
    import store from '../store'
    
    Vue.use(Router)
    
    const router = new Router({
      routes: [
        {
          path: '/',
          name: 'Login',
          component: () => import('@/views/login/Login'),
        },
        {
          path: '/house',
          name: 'House',
          component: () => import('@/views/house/index'),
        }
      ]
    })
    

    vue在路由中验证用户是否登录

    import Vue from 'vue'
    import Router from 'vue-router'
    import House from '@/views/house/index'
    import Login from '@/views/login/Login'
    import store from '../store'
    
    Vue.use(Router)
    
    if(localStorage.getItem("token")){
      store.commit("set_token",localStorage.getItem("token"));
    }
    
    const router = new Router({
      routes: [
        {
          path: '/',
          name: 'Login',
          component: () => import('@/views/login/Login'),
        },
        {
          path: '/house',
          name: 'House',
          component: () => import('@/views/house/index'),
          meta:{
            requireAuth:true
          }
        }
      ]
    })
    
    //store.state.token
    router.beforeEach((to,form,next)=>{
      if(to.matched.some(r=>r.meta.requireAuth)){
        //console.log(store.state.token);
        //if(store.state.token){
        if(localStorage.getItem("token") != null){
          next();
        }else{
          next({
              //跳转到登陆页面
            path:"/"
          })
        }
      }else{
        next();
      }
    })
    export default router;
    

    在需要进行登录认证的方法加上自定义注解(后端,上面已经写好了)

     @LoginAnnotation
    
    @TokenCheckAnnotation
    @RequestMapping("test")
     public void test(){
         System.out.println("我被调用了");
     }
    

    vue自定义axios的前置拦截器Main.js

    对登录的请求进行拦截,判断是否登录

    通过查看token里面是否有从后端传过来的token值,如果有就说明已登录,可以继续访问

    如果值为空,就是没登陆,重定向的登录页面

    同时也要判断token是否已经失效,如果失效也需要重定向到登录页面

    // The Vue build version to load with the `import` command
    // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import axios from 'axios'//导入axios
    import VueAxios from 'vue-axios'
    import ElementUI from 'element-ui'//导入elementui
    import md5 from 'js-md5'; // 导入MD5
    import 'element-ui/lib/theme-chalk/index.css'
    import moment from 'moment'
    import qs from 'qs'
    import store from './store'
    
    Vue.use(VueAxios,axios)
    Vue.use(ElementUI)
    
    
    // 发送axios请求之前将token设置到请求的头信息里
    axios.defaults.headers.common["Authorization-token"] = store.getters.token
    
    // 在请求拦截器中判断头信息中有没有token,防止token没有传到后台
    axios.interceptors.request.use(config=>{
      alert(store.state.token)
      if(store.state.token){
        config.headers.common['Authorization-token']=store.state.token;
      }
      return config;
    })
    
    // 判断后台返回的状态码,token失效后跳转到登录页面
    axios.interceptors.response.use(response => {
      var code = response.data.code;
      if(code == 5004 || code == 5006){
        router.replace({
          path: '/',
          query: {redirect: router.currentRoute.fullPath}//登录成功后跳入浏览的当前页面
        })
      }
      return response;
    })
    
    
    Vue.prototype.$axios=axios
    Vue.prototype.$qs=qs
    Vue.prototype.$md5 = md5;
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      store,
      components: { App },
      template: '<App/>'
    })
    

    End

    展开全文
  • 异常全文: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?   出现场景 意思是无法弹出Popup Window,确认你要弹出的这个窗口...
  • 出现Syntax error on token(s), misplaced construct(s)异常时,说明源代码中出现了语法错误,要仔细检查源代码。  例如, %@page contentType="text/html;charset=utf-8" %>  当前页面为compute.jsp ...
  • Uncaught SyntaxError: Unexpected token )

    万次阅读 多人点赞 2014-09-05 10:34:22
    Uncaught SyntaxError: Unexpected token )以下代码引发了此异常:&lt;div class="Hd_live_Sharediv left"&gt; &lt;a href="javascript:void()" onclick="loadLivePlayer('...
  • token失效后再次请求获取新token

    万次阅读 2015-11-06 16:01:54
    在keystonemiddleware中打印认证的token,发现认证失败的token都是已经过期的token,这些过期的token认证时肯定是失败的,所以返回认证失败。在认证失败后,服务会再次请求获取新的token,再用新的token进行验证,...
  • 出现Syntax error on token(s), misplaced construct(s)异常时,说明源代码中出现了语法错误,要仔细检查源代码。  例如, %@page contentType="text/html;charset=utf-8" %>  当前页面为compute.jsp ...
  • 我们需要每次都知道当前请求的人是谁,但是又不想每次都让他提交用户名和密码,这时就需要有一个等同于用户名密码也能够标识用户身份的东西,即—token 基于Token的身份验证方法(步骤) 客户端使用用户名和密码请求...
  • flask刷新token

    2019-09-22 02:14:06
    我们在做前后端分离的项目中,最常用的都是使用token认证。 登录后将用户信息,过期时间以及私钥一起加密生成token,但是比较头疼的就是token过期刷新的问题,因为用户在登录后,如果在使用过程中,突然提示token...
  • axios token过期刷新

    千次阅读 2019-01-22 14:58:49
    Axios token刷新,拦截器设置 此项目是用vue做的管理报表系统,代码中的“quasar”是基于vue的框架 需求:出于安全性考虑,每次发起请求需要在header里带上token,并设置过期时间,在过期时间内重新请求可以刷新...
  • 当完整的搭建了Spring Security Oauth2 + JWT工程之后,我在想该如何获取当前用户的信息?这本质算不上是太大问题,配合Redis很容易就能解决,但既然JWT的优点就在于保存了用户信息,再去Redis中去拿就显得多此一举...
  • Jwt生成token以及token过期校验

    千次阅读 2019-12-06 09:48:02
    Java的JJWT实现JWT 1 什么是JJWT ​ JJWT是一个提供端到端的JWT创建和验证的Java库。永远免费和开源(Apache License,版本2.0),JJWT很容易...2.1 token的创建 (1)创建maven工程,引入依赖 <dependency> ...
  • JWT登录验证Token,创建Token,设置登录过期时间创建Token工具类创建自定义注解配置拦截器编写自定义拦截器 创建Token工具类 public class TokenUtil { /** * 解析Token 解析Token 传换成Clasims类 * @param token...
  • Feign调用进行token鉴权

    2021-04-17 00:23:52
    请求进来时,通过拦截器,校验header的token,然后在业务中调用feignClient时,通过新加一个feign拦截器,拦截feign请求,把当前的header中的token添加到feign的请求头中去。实现token在链路中的传递。 三、具体...
  • 当前开发的 App 遇到一个问题: 当请求某个接口时,由于 token 已经失效,所以接口会报错。 但是产品经理希望 app 能够马上刷新 token ,然后重复请求刚才那个接口,这个过程对用户来说是无感的。 请求 A ...
  • token令牌生成和验证

    2020-07-19 11:19:44
    token令牌生成和验证
  • android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@43cf0778 is not valid; is your activity runing? 异常打印日志: FATAL EXCEPTION: main
  • [token]前后端分离项目token机制详解

    千次阅读 2020-01-07 22:31:44
    一直想写一篇博客,对token进行详解一下,也是对以前学的知识进行整理,所以今天在此记录一下。另外自己开通的博客还在备案中,等开通成功后,将会在自己的博客上发表,敬请期待。 什么是token? 我们在登陆设计的...
  • Token验证失败的解决方法

    万次阅读 2019-04-10 10:37:21
    Token验证失败的解决方法 一、问题由来 在使用URL和Token启用微信公众平台开发模式消息接口的时候,我们会碰到下面三种情况 token校验失败 这样回头检查一下各项配置是否正确。如果确定配置没有问题,请按下面的...
  • JS获得token

    万次阅读 2012-07-23 17:42:38
    使用struts有一个很奇怪的事情,就是你不知道在什么时候,就放进来一个...虽然这个token能够有效地防止重复提交,但是也能够让你原来的架构一团糟。可以拿一个亲身经历作为教材。在一个搭了frame的框架里,左边的frame
  • Web项目中经常会用token来进行用户的访问验证,那么在获得token之后,如果有很多地方需要根据token获得对应的用户信息,你会怎么获取? 本文给大家提供N种方式,对照一下,看看你的项目中所使用的方式属于哪个Level...
  • token 自动续期的方案

    2021-04-07 14:58:51
    后端写个拦截器,首先验证token、此次从缓存中拿到token并进行自动续期,即从新算时间,可理解为设置当前时间, 那什么时候token才会失效呢? 写个定时任务,定时去检查cache中过期的token,发现时间已经大于1小时...
  • 作为下一代互联网的基础设施,区块链token认为区块链比传递更重要,并且一直认为“皮肤不会被存储”。 然而,随着区块链的发展,我们发现它是通行证的通用证书和经济规则,这促进了数字货币的复杂性。一般经济的重要...
  • 一,提供一个login接口获取token: 1,通过jwt的方式生成token: private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS512; //用于进行签名的秘钥 private static String SECRET = ...
  • token的生命周期

    千次阅读 2019-07-10 18:07:27
    1,前端 肯定没有token 用户执行注册/登录 1,一旦基础数据校验成功,后端生成token,并且token包含此次 注册/登录 用户的用户名  并通过 JsonResponse返回给前端 2,前端拿到返回的token后,存入浏览器本地存储...
  • 如何使用token保存用户登录信息

    千次阅读 2019-07-31 10:09:53
    登录成功之后,后台生成一个token,将token保存在redis中,key是token,value是用户id,并且把token响应给前端,前端每次请求时都把token传给后台进行鉴权。生成token代码如下: private String logining(String ...
  • java实现基于token认证

    2021-01-10 00:36:40
    token验证是前后端交互中用的比较频繁的功能,这里我们的token采用前端cookie和后端session的方式来实现 首先token认证是用户通过浏览器登录成功后,后台返给前端的一个唯一标识,前端可以通过这个标识来访问后台...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 47,991
精华内容 19,196
关键字:

当前token异常