spa项目开发之jwt验证码实现

友情提示:本篇博客需要具备spring、springmvc、mybatis、redis、Vue、Vuex、axios、jwt技术功底方能看懂,相关技术可以翻看之前所写博客;
Jwt比较难搞,不知道的可以看一下之前写过关于JWT的博客:Jwt

实现思路及细节
思路:
登录界面向后台请求验证码,后台就先调用随机函数生成验证码,并且根据验证码生成一张图片,以 base64 字符串的形式传到前台,这时我们还要生成verificationJwt令牌做为请求验证码客户端的区分。我们先将验证码信息存入redis。key是 verificationJwt令牌的值,value就是验证码了。并且将令牌放入到响应头。传给客户端。当客户端提交的时候将保持的verificationJwt令牌放入请求头带过来。后端根据前端传过来的 jwt令牌去redis中获取数据,将验证码拿到后和现有的验证码进行比较。看看是否相等

细节:
访问一次登录页面,生成一个verificationJwt令牌,这个令牌的有效时间是5min,验证码在redis中的有效时间是1min,无论是verificationJwt令牌超时失效,还是验证码生成后超时失效,都会造成登录失败;
简单来说就是跳转到登陆界面,需要在5min中之内完成登录,新的验证码出来后,需要在1min中之内完成登录

逻辑图如下
spa项目开发之jwt验证码实现_第1张图片
相关代码:

后端:

首先我们要导入pom依赖:


		
			io.jsonwebtoken
			jjwt
			0.9.1
		
		
			com.auth0
			java-jwt
			3.4.0
		

业务代码

User.Java

package com.thf.model;

public class User {
    private String uname;

    private String pwd;

    public User(String uname, String pwd) {
        this.uname = uname;
        this.pwd = pwd;
    }

    public User() {
        super();
    }

    public String getUname() {
        return uname;
    }

    public void setUname(String uname) {
        this.uname = uname;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

UserMapper.java

package com.thf.mapper;

import com.thf.model.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {
    int deleteByPrimaryKey(String uname);

    int insert(User record);

    int insertSelective(User record);

    User selectByPrimaryKey(String uname);

    int updateByPrimaryKeySelective(User record);

    int updateByPrimaryKey(User record);

    User login(User user);
}

UserMapper.xml



如图所示:
spa项目开发之jwt验证码实现_第2张图片
Service层
UserService

package com.thf.service;

import com.thf.model.User;

public interface UserService {
   public User login(User user);
}

UserServiceImpl

package com.thf.service.impl;

import com.thf.mapper.UserMapper;
import com.thf.model.User;
import com.thf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


/**
 * @author tanhaifang
 * @site www.thf.com
 * @company
 * @create 2019-10-30 19:04
 */
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;

    @Override
    public User login(User user) {
        return userMapper.login(user);
    }
}

UserController
请求验证码图片和用户登录都是在这里了

package com.thf.controller;


import com.thf.model.User;
import com.thf.service.UserService;
import com.thf.util.ImageUtil;
import com.thf.util.JSONResult;
import com.thf.util.JwtUtils;
import com.thf.util.VerifyCodeUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/vue/user")
public class UserController {
    private static final String VERIFICATION_CODE = "verificationCode_";

    @Autowired
    private UserService userService;

    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/login")
    @ResponseBody
    public JSONResult login(User u, HttpServletRequest request, HttpServletResponse response){
        //获取用户输入的验证码
        String userVerificationCode = request.getParameter("verificationCode");
        //获取验证码jwt令牌
        String userJwt = request.getHeader(JwtUtils.JWT_VERIFICATION_KEY);
        //获取到保存在redis中的验证码
        Object redisVerificationCode =  redisTemplate.opsForValue().get(VERIFICATION_CODE + userJwt) ;

//        这里存在两种情况:1、令牌超时   2、验证码超时
        if(StringUtils.isEmpty(redisVerificationCode)){
            return JSONResult.errorMsg("你的验证码已超时");
        }

        if(!redisVerificationCode.toString().equalsIgnoreCase(userVerificationCode)){
            return JSONResult.errorMsg("验证码错误");
        }

        User user = userService.login(u);
        //判断是否登录成功
        if(user != null){
            Map map=new HashMap();
            map.put("User", user);
            //这是颁发用户登录成功的jwt令牌
            String jwt= JwtUtils.createJwt(map, JwtUtils.JWT_WEB_TTL);
            response.setHeader(JwtUtils.JWT_HEADER_KEY, jwt);
            return JSONResult.ok(user);
        }else {
            return JSONResult.errorMsg("密码或账户错误");
        }
        
    }




    /**生成图片验证码*/
    @RequestMapping("/verificationCode")
    @ResponseBody
    public String verificationCode(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //生成验证码随机数
        String word = VerifyCodeUtil.produceNumAndChar(4);
//        获取用户的jwt令牌
        String userVerificationJwt = req.getHeader(JwtUtils.JWT_VERIFICATION_KEY);
        //验证码令牌
        Claims claims = JwtUtils.validateJwtToken(userVerificationJwt);
        if(claims == null){
            //如果用户令牌过期那么对应存放在redis中的数据也要清空
            if(!StringUtils.isEmpty(userVerificationJwt)){
                redisTemplate.expire(VERIFICATION_CODE + userVerificationJwt, 1, TimeUnit.DAYS);
            }
            userVerificationJwt =  JwtUtils.createJwt(new HashMap() ,JwtUtils.JWT_WEB_TTL);
            //将jwt令牌放入 response head中
            resp.setHeader(JwtUtils.JWT_VERIFICATION_KEY, userVerificationJwt);
        }
        //刷新缓存,更新验证码
        redisTemplate.opsForValue().set(VERIFICATION_CODE + userVerificationJwt , word,60, TimeUnit.SECONDS);
        //生成图片
        String code = "data:image/png;base64," + ImageUtil.createImageWithVerifyCode(word, 116,40);;
        return code;
    }
}

Web.xml


  Archetype Created Web Application
  
    contextConfigLocation
    classpath:applicationCatext.xml
  
  
  
    org.springframework.web.context.ContextLoaderListener
  

  
  
    corsFilter
    com.thf.util.CorsFilter
  
  
    corsFilter
    /*
  
  
  
  
  
  
  
  
  
  

  
  
    SpringMVC
    org.springframework.web.servlet.DispatcherServlet
    
    
      contextConfigLocation
      WEB-INF/springmvc-servlet.xml
    
    1
    
    true
  
  
    SpringMVC
    /
  

工具类

VerifyCodeUtil
生成验证码随机数工具类

package com.thf.util;

import java.util.Random;

public class VerifyCodeUtil {


    /**生成N位数字和字母混合的验证码
     * @param  num 验证码位数
     * @return code 生成的验证码字符串*/
    public static String produceNumAndChar(int num){
        Random random = new Random();
        String code = "";
        String ch = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
        String n = "123456789";
        for(int i=0;i

ImageUtil
生成 base64 格式生成的验证码图片 的工具类

package com.thf.util;

import sun.misc.BASE64Encoder;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;

public class ImageUtil {

    /**
     * 根据指定的随机数 生成验证码图片 转 base64
     * @param word 要生存的验证码随机字符串
     * @param width 图片宽度
     * @param height 图片高度
     * @return base64 格式生成的验证码图片
     * @throws IOException
     */
    public static String createImageWithVerifyCode(String word, int width, int height) throws IOException {
        String png_base64="";
        //绘制内存中的图片
        BufferedImage bufferedImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
        //得到画图对象
        Graphics graphics = bufferedImage.getGraphics();
        //绘制图片前指定一个颜色
        graphics.setColor(getRandColor(160,200));
        graphics.fillRect(0,0,width,height);
        //绘制边框
        graphics.setColor(Color.white);
        graphics.drawRect(0, 0, width - 1, height - 1);
        // 步骤四 四个随机数字
        Graphics2D graphics2d = (Graphics2D) graphics;
        graphics2d.setFont(new Font("宋体", Font.BOLD, 18));
        Random random = new Random();
        // 定义x坐标
        int x = 10;
        for (int i = 0; i < word.length(); i++) {
            // 随机颜色
            graphics2d.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            // 旋转 -30 --- 30度
            int jiaodu = random.nextInt(60) - 30;
            // 换算弧度
            double theta = jiaodu * Math.PI / 180;
            // 获得字母数字
            char c = word.charAt(i);
            //将c 输出到图片
            graphics2d.rotate(theta, x, 20);
            graphics2d.drawString(String.valueOf(c), x, 20);
            graphics2d.rotate(-theta, x, 20);
            x += 30;
        }
        // 绘制干扰线
        graphics.setColor(getRandColor(160, 200));
        int x1;
        int x2;
        int y1;
        int y2;
        for (int i = 0; i < 30; i++) {
            x1 = random.nextInt(width);
            x2 = random.nextInt(12);
            y1 = random.nextInt(height);
            y2 = random.nextInt(12);
            graphics.drawLine(x1, y1, x1 + x2, x2 + y2);
        }
        graphics.dispose();// 释放资源
        ByteArrayOutputStream baos = new ByteArrayOutputStream();//io流
        ImageIO.write(bufferedImage, "png", baos);//写入流中
        byte[] bytes = baos.toByteArray();//转换成字节
        BASE64Encoder encoder = new BASE64Encoder();
        png_base64 = encoder.encodeBuffer(bytes).trim();
        png_base64 = png_base64.replaceAll("\n", "").replaceAll("\r", "");//删除 \r\n
        return png_base64;
    }



    /**设置随机颜色*/
    private static Color getRandColor(int fc, int bc) {
        // 取其随机颜色
        Random random = new Random();
        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);
    }

}

CorsFilter
在此拦截器中要允许客户端 处理和和发送 verificationJwt 这个头

package com.thf.util;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 配置tomcat允许跨域访问
 * 
 * @author Administrator
 *
 */
public class CorsFilter implements Filter {

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
   }

   @Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
         throws IOException, ServletException {
      HttpServletResponse resp = (HttpServletResponse) servletResponse;
      HttpServletRequest req = (HttpServletRequest) servletRequest;

      // Access-Control-Allow-Origin就是我们需要设置的域名
      // Access-Control-Allow-Headers跨域允许包含的头。
      // Access-Control-Allow-Methods是允许的请求方式
      resp.setHeader("Access-Control-Allow-Origin", "*");// *,任何域名
      resp.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE");
      // resp.setHeader("Access-Control-Allow-Headers", "Origin,X-Requested-With,
      // Content-Type, Accept");
      // 允许客户端,发一个新的请求头jwt
      //允许客户端发送一个新的请求头
      resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, jwt, verificationJwt");
      //允许客户端处理一个新的响应头jwt
      resp.setHeader("Access-Control-Expose-Headers", "jwt");
      resp.setHeader("Access-Control-Expose-Headers", "verificationJwt");
      // String sss = resp.getHeader("Access-Control-Expose-Headers");
      // System.out.println("sss=" + sss);

      // 允许请求头Token
      // httpResponse.setHeader("Access-Control-Allow-Headers","Origin,X-Requested-With,
      // Content-Type, Accept, Token");
      // System.out.println("Token=" + req.getHeader("Token"));

      if ("OPTIONS".equals(req.getMethod())) {// axios的ajax会发两次请求,第一次提交方式为:option,直接返回即可
         return;
      }
      filterChain.doFilter(servletRequest, servletResponse);
   }

   @Override
   public void destroy() {

   }
}

JSONResult

package com.thf.util;

public class JSONResult {

    // 响应业务状态
    private Integer status;

    // 响应消息
    private String msg;

    // 响应中的数据
    private Object data;
    
    private String ok; // 不使用

    public static JSONResult build(Integer status, String msg, Object data) {
        return new JSONResult(status, msg, data);
    }

    public static JSONResult ok(Object data) {
        return new JSONResult(data);
    }

    public static JSONResult ok() {
        return new JSONResult(null);
    }
    
    public static JSONResult errorMsg(String msg) {
        return new JSONResult(500, msg, null);
    }
    
    public static JSONResult errorMap(Object data) {
        return new JSONResult(501, "error", data);
    }
    
    public static JSONResult errorTokenMsg(String msg) {
        return new JSONResult(502, msg, null);
    }
    
    public static JSONResult errorException(String msg) {
        return new JSONResult(555, msg, null);
    }

    public JSONResult() {

    }

    public JSONResult(Integer status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }

    public JSONResult(Object data) {
        this.status = 200;
        this.msg = "OK";
        this.data = data;
    }

    public Boolean isOK() {
        return this.status == 200;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

   public String getOk() {
      return ok;
   }

   public void setOk(String ok) {
      this.ok = ok;
   }

}

JwtUtils

package com.thf.util;

import java.util.Date;
import java.util.Map;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * JWT验证过滤器:配置顺序 CorsFilte->JwtUtilsr-->StrutsPrepareAndExecuteFilter
 *
 */
public class JwtUtils {
   /**
    * JWT_WEB_TTL:WEBAPP应用中token的有效时间,默认30分钟
    */
   public static final long JWT_WEB_TTL = 5 * 60 * 1000;

   /**
    * 将jwt令牌保存到header中的key
    */
   public static final String JWT_HEADER_KEY = "jwt";
   public static final String JWT_VERIFICATION_KEY = "verificationJwt";

   // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
   private static final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;
   private static final String JWT_SECRET = "f356cdce935c42328ad2001d7e9552a3";// JWT密匙
   private static final SecretKey JWT_KEY;// 使用JWT密匙生成的加密key
// private static final SecretKey JWT_VERIFICATION_KEY;// 使用JWT密匙生成的加密key


   static {
      byte[] encodedKey = Base64.decodeBase64(JWT_SECRET);
      JWT_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
//    这里我偷个懒,用户登录jwt密钥,与图形验证码jwt密钥搞成同一个
//    JWT_VERIFICATION_KEY = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
   }

   private JwtUtils() {
   }

   /**
    * 解密jwt,获得所有声明(包括标准和私有声明)
    * 
    * @param jwt
    * @return
    * @throws Exception
    */
   public static Claims parseJwt(String jwt) {
      Claims claims = Jwts.parser().setSigningKey(JWT_KEY).parseClaimsJws(jwt).getBody();
      return claims;
   }

   /**
    * 创建JWT令牌,签发时间为当前时间
    * 
    * @param claims
    *            创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
    * @param ttlMillis
    *            JWT的有效时间(单位毫秒),当前时间+有效时间=过期时间
    * @return jwt令牌
    */
   public static String createJwt(Map claims, long ttlMillis) {
      // 生成JWT的时间,即签发时间
      long nowMillis = System.currentTimeMillis();

      // 下面就是在为payload添加各种标准声明和私有声明了
      // 这里其实就是new一个JwtBuilder,设置jwt的body
      JwtBuilder builder = Jwts.builder()
            // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
            .setClaims(claims)
            // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
            // 可以在未登陆前作为身份标识使用
            .setId(UUID.randomUUID().toString().replace("-", ""))
            // iss(Issuser)签发者,写死
            // .setIssuer("zking")
            // iat: jwt的签发时间
            .setIssuedAt(new Date(nowMillis))
            // 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
            // .setSubject("{}")
            // 设置签名使用的签名算法和签名使用的秘钥
            .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
            // 设置JWT的过期时间
            .setExpiration(new Date(nowMillis + ttlMillis));

      return builder.compact();
   }

   /**
    * 复制jwt,并重新设置签发时间(为当前时间)和失效时间
    * 
    * @param jwt
    *            被复制的jwt令牌
    * @param ttlMillis
    *            jwt的有效时间(单位毫秒),当前时间+有效时间=过期时间
    * @return
    */
   public static String copyJwt(String jwt, Long ttlMillis) {
      Claims claims = parseJwt(jwt);

      // 生成JWT的时间,即签发时间
      long nowMillis = System.currentTimeMillis();

      // 下面就是在为payload添加各种标准声明和私有声明了
      // 这里其实就是new一个JwtBuilder,设置jwt的body
      JwtBuilder builder = Jwts.builder()
            // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
            .setClaims(claims)
            // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
            // 可以在未登陆前作为身份标识使用
            //.setId(UUID.randomUUID().toString().replace("-", ""))
            // iss(Issuser)签发者,写死
            // .setIssuer("zking")
            // iat: jwt的签发时间
            .setIssuedAt(new Date(nowMillis))
            // 代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可放数据{"uid":"zs"}。此处没放
            // .setSubject("{}")
            // 设置签名使用的签名算法和签名使用的秘钥
            .signWith(SIGNATURE_ALGORITHM, JWT_KEY)
            // 设置JWT的过期时间
            .setExpiration(new Date(nowMillis + ttlMillis));
      return builder.compact();
   }

   public static Claims validateJwtToken(String jwt) {
      Claims claims = null;
      try {
         if (null != jwt) {
            claims = JwtUtils.parseJwt(jwt);
         }
      } catch (Exception e) {
         e.printStackTrace();
      }
      return claims;
   }
}

前端

首先要在state.js中定义一个用于保持验证码jwt的属性

	verificationJwt:null, //这是用来保存用户等登录验证码jwt身份识别的

State.js

export default {
	resturantName: '哈哈哈',
	jwt:'',
	options: [],//存放tab页的容器
    activeIndex: '',//激活的tab页路由路径
	showName:'show',//tab页的标题
	role:"",//用来区分是否是因为左侧菜单被点击造成的路由路径发生改变,是:pass;不是:nopass
	verificationJwt:null, //这是用来保存用户等登录验证码jwt身份识别的
}

Mutations.js

setVerificationJwt: (state, payload) => {
			state.verificationJwt = payload.verificationJwt;
		}

http.js
在http.js中要的拦截器就要在响应的时候如果有 verificationJwt 这个响应头 就要保存。并且在下次请求的时候带上这个 verificationJwt放入请求头都 。

/**
 * vue项目对axios的全局配置
 */
import axios from 'axios'
import qs from 'qs'

//引入action模块,并添加至axios的类属性urls上
import action from '@/api/action'
axios.urls = action

// axios默认配置
axios.defaults.timeout = 10000; // 超时时间
// axios.defaults.baseURL = 'http://localhost:8080/j2ee15'; // 默认地址
axios.defaults.baseURL = action.SERVER;

//整理数据
// 只适用于 POST,PUT,PATCH,transformRequest` 允许在向服务器发送前,修改请求数据
axios.defaults.transformRequest = function(data) {
	data = qs.stringify(data);
	return data;
};
// 请求拦截器
axios.interceptors.request.use(function(config) {
	//设置验证码jwt令牌
	let verificationJwt = window.vm.$store.getters.getVerificationJwt;
	if (verificationJwt) {
		config.headers['verificationJwt'] = verificationJwt;
	}

	var jwt = window.vm.$store.getters.getJwt;
	config.headers['jwt'] = jwt;
	return config;
}, function(error) {
	return Promise.reject(error);
});

// 响应拦截器
axios.interceptors.response.use(function(response) {
	// debugger;
	//保存验证码jwt令牌
	let verificationjwt = response.headers['verificationjwt'];
	if (verificationjwt) {
		window.vm.$store.commit('setVerificationJwt', {
			verificationJwt: verificationjwt
		});
	}
	
	var jwt = response.headers['jwt'];
	if (jwt) {
		window.vm.$store.commit('setJwt', {
			jwt: jwt
		});
	}
	return response;
}, function(error) {
	return Promise.reject(error);
});


export default axios;

action.js
在 action.js中添加 验证码图片获取的请求路径映射变量

/**
 * 对后台请求的地址的封装,URL格式如下:
 * 模块名_实体名_操作
 */
export default {
	// 'SERVER': 'http://localhost:8080/T216_SSH', //服务器
	'SERVER': 'http://localhost:8080', //服务器
	// 'SYSTEM_USER_DOLOGIN': '/vue/userAction_login.action', //用户登陆
	'SYSTEM_USER_DOLOGIN': '/vue/user/login', //用户登陆
	'VERIFICATION': '/vue/user/verificationCode', //用户登陆
	'SYSTEM_USER_DOREG': '/vue/userAction_reg.action', //用户注册
	'SYSTEM_MENU_TREE': '/vue/treeNodeAction.action', //左侧树形菜单加载
	'SYSTEM_ARTICLE_LIST': '/vue/articleAction_list.action', //文章列表
	'SYSTEM_ARTICLE_ADD': '/vue/articleAction_add.action', //文章新增
	'SYSTEM_ARTICLE_EDIT': '/vue/articleAction_edit.action', //文章修改
	'SYSTEM_ARTICLE_DEL': '/vue/articleAction_del.action', //文章删除
	'SYSTEM_USER_GETASYNCDATA': '/vue/userAction_getAsyncData.action', //vuex中的异步加载数据
	'getFullPath': k => { //获得请求的完整地址,用于mockjs测试时使用
		return this.SERVER + this[k];
	}
}

Login.vue








展示效果

前台:
spa项目开发之jwt验证码实现_第3张图片
redis中
spa项目开发之jwt验证码实现_第4张图片
!!!

你可能感兴趣的:(原创)