javascript

VUE+Axios请求加安全级别

2018-09-30  本文已影响0人  little_short

文章转自《https://my.oschina.net/Lady/blog/1814825

一、前端

封装加密工具,这个是引用了crypto.js,通过npm安装

npm i --save crypto-js
/**

/**

/**

const ajax = axios.create({
baseURL: store.getters.serviceHost, // url前缀
timeout: 10000, // 超时毫秒数
withCredentials: true // 携带认证信息cookie
});

/**

// 参数转换
const param2String = data => {
console.log('data', data);
if (typeof data === 'string') {
return data;
}
let ret = '';
for (let it in data) {
let val = data[it];
if (typeof val === 'object' && //
(!(val instanceof Array) || (val.length > 0 && (typeof val[0] === 'object')))) {
val = JSON.stringify(val);
}
ret += it + '=' + encodeURIComponent(val) + '&';
}
if (ret.length > 0) {
ret = ret.substring(0, ret.length - 1);
}
return ret;
};

// 错误回调函数
const errback = error => {
if ('code' in error) {
// 未登录
if (error.code === 30001) {
sessionStorage.clear();
window.location.href = '/';
return;
}
return Promise.reject(error);
}
// 网络异常,或链接超时
Message({
message: error.message,
type: 'error'
});
return Promise.reject({data: error.message});
};
// 成功回调函数
const successback = res => {
if (res.status === 200 && res.data.code !== 20000) {
let errMsg = {'30002': '对不起无权限', '30003': '验签失败'};
let msg = errMsg[res.data.code];
if (msg) {
Message({
message: errMsg[res.data.code],
type: 'error'
});
}
return Promise.reject(res.data);
}
return res.data;
};

/**

// 统一方法输出口
export {
ajax,
get,
postJson,
post,
del,
putJson,
put
};
二、java后端代码

1、添加注解@CryptoType,需求是哪些controller类或方法需要加密或者签名加上对应注解即可实现。

package com.nja.baseweb.crypto;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**

*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CryptoType {

Type value() default Type.NONE;

enum Type {
    /**
     * 无
     */
    NONE,
    /**
     * 签名
     */
    SIGN, 
    /**
     * 加密
     */
    CRYPTO
}

}
2、添加签名验签拦截器,处理带签名请求方法

package com.nja.baseweb.crypto;

import java.io.IOException;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import com.alibaba.fastjson.JSON;
import com.nja.base.common.bean.Result;
import com.nja.baseweb.crypto.CryptoType.Type;

import liquibase.util.MD5Util;

/**

*/
public class RequestSignVerifyInterceptor extends HandlerInterceptorAdapter {

private static Logger logger = LoggerFactory.getLogger(RequestSignVerifyInterceptor.class);

/** 时间戳超时设置60s*/
private static final long TIMEOUT = 60 * 1000L;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws Exception {
    logger.debug("RequestSignVerifyInterceptor -> preHandle");
    if (handler instanceof HandlerMethod) {
        HandlerMethod method = (HandlerMethod) handler;
        // 方法上注解
        CryptoType type = method.getMethodAnnotation(CryptoType.class);
        if (null == type) {
            // 类上是否有注解
            type = method.getBeanType().getAnnotation(CryptoType.class);
            if (null == type) {
                return true;
            }
        }
        if (type.value() != Type.SIGN) {
            // 不是签名验证跳过
            return true;
        }
    }
    logger.debug("Start verifySign ->");
    if (!verifySign(request)) {
        // 不通过
        logger.debug("VerifySign fail");
        response.setContentType("application/json; charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(Result.failure(Result.FAILURE_VERIFY_SIGN, "验签失败")));
        return false;
    }
    // 验签通过
    return true;
}

/**
 * 验证签名是否一致
 * 
 * @param request
 */
private boolean verifySign(HttpServletRequest request) throws IOException {
    String timestamp = request.getHeader("timestamp");
    String signstr = request.getHeader("signstr");
    String level = request.getHeader("level");
    logger.debug("VerifySign params -> timestamp:{}, signstr:{}, level:{}", timestamp, signstr, level);
    if (StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(signstr)) {
        logger.error("VerifySign head param timestamp or signstr is empty");
        return false;
    }
    // 时间戳是否过期 (60s)
    if (System.currentTimeMillis() - Long.parseLong(timestamp) > TIMEOUT) {
        // 超时
        logger.error("VerifySign timestamp timeout");
        return false;
    }
    // FIXME 从session获取id 作token,session需要做redis共享,不然集群部署每个服务获取sessionID不一致
    String token = request.getSession().getId();
    TreeMap<String, String> map = new TreeMap<>();
    Enumeration<String> names = request.getParameterNames();
    while (names.hasMoreElements()) {
        String name = names.nextElement();
        map.put(name, URLDecoder.decode(request.getParameter(name), "UTF-8"));
    }
    return signstr.equals(sign(token, timestamp, map));
}

/**
 * 签名
 * 
 * @param token
 * @param timestamp
 * @param params
 */
private String sign(String token, String timestamp, TreeMap<String, String> params) {
    StringBuilder paramValues = new StringBuilder();
    paramValues.append(timestamp).append(token);

    for (Map.Entry<String, String> entry : params.entrySet()) {
        paramValues.append(entry.getKey()).append(entry.getValue());
    }
    return MD5Util.computeMD5(paramValues.toString());
}

}
3、添加解密过滤器,处理前端加密请求方法,重写request参数

package com.nja.baseweb.crypto;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerMapping;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.nja.base.common.bean.Result;
import com.nja.baseweb.crypto.CryptoType.Type;

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;

/**

/
@WebFilter(urlPatterns = { "/
" }, filterName = "requestDecryptFilter")
public class RequestDecryptFilter extends OncePerRequestFilter implements ApplicationContextAware {

private static Logger logger = LoggerFactory.getLogger(RequestDecryptFilter.class);

/** 方法映射集 */
private List<HandlerMapping> handlerMappings;

/** AES加解密 */
protected static AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, "1234567812345678".getBytes(),
        "1234567812345678".getBytes());

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    try {
        Object handler = getHandler(request).getHandler();
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            // 方法上注解
            CryptoType type = method.getMethodAnnotation(CryptoType.class);
            if (null == type) {
                // 类上是否有注解
                type = method.getBeanType().getAnnotation(CryptoType.class);
                if (null == type) {
                    filterChain.doFilter(request, response);
                    return;
                }
            }
            if (type.value() != Type.CRYPTO) {
                // 不是解密跳过
                filterChain.doFilter(request, response);
                return;
            }
        }
    } catch (Exception e) {
        logger.error("", e);
        filterChain.doFilter(request, response);
        return;
    }

    try {
        // 调用自定义request解析参数
        filterChain.doFilter(new DecryptRequest(request), response);
    } catch (IOException e) {
        // 异常处理
        logger.debug("Decrypt fail");
        response.setContentType("application/json; charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(Result.failure(Result.FAILURE_VERIFY_SIGN, "验签失败")));
    }
}

@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
    Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
            HandlerMapping.class, true, false);
    if (!matchingBeans.isEmpty()) {
        this.handlerMappings = new ArrayList<>(matchingBeans.values());
        // We keep HandlerMappings in sorted order.
        AnnotationAwareOrderComparator.sort(this.handlerMappings);
    }
}

/**
 * 获取访问目标方法
 * 
 * @param request
 * @return HandlerExecutionChain
 * @throws Exception
 */
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping hm : this.handlerMappings) {
            if (logger.isTraceEnabled()) {
                logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name ''");
            }
            HandlerExecutionChain handler = hm.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

/**
 * 解密request封装
 * 
 * @author ldy
 *
 */
private class DecryptRequest extends HttpServletRequestWrapper {

    private static final String APPLICATION_JSON = "application/json";
    /** 所有参数的Map集合 */
    private Map<String, String[]> parameterMap;
    /** 输入流 */
    private InputStream inputStream;

    public DecryptRequest(HttpServletRequest request) throws IOException {
        super(request);
        String contentType = request.getHeader("Content-Type");
        logger.debug("DecryptRequest -> contentType:{}", contentType);
        String encrypt = null;
        if (null != contentType && contentType.contains(APPLICATION_JSON)) {
            // json
            ServletInputStream io = request.getInputStream();
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length;
            while ((length = io.read(buffer)) != -1) {
                os.write(buffer, 0, length);
            }
            byte[] bytes = os.toByteArray();
            encrypt = (String) JSON.parseObject(new String(bytes)).get("encrypt");
        } else {
            // url
            encrypt = request.getParameter("encrypt");
        }
        logger.debug("DecryptRequest -> encrypt:{}", encrypt);
        // 解密
        String params = decrypt(encrypt);

        if (null != contentType && contentType.contains(APPLICATION_JSON)) {
            if (this.inputStream == null) {
                this.inputStream = new DecryptInputStream(new ByteArrayInputStream(params.getBytes()));
            }
        }
        parameterMap = buildParams(params);
    }

    private String decrypt(String encrypt) throws IOException {
        try {
            // 解密
            return aes.decryptStrFromBase64(encrypt);
        } catch (Exception e) {
            logger.error("", e);
            throw new IOException(e.getMessage());
        }
    }

    private Map<String, String[]> buildParams(String src) throws UnsupportedEncodingException {
        Map<String, String[]> map = new HashMap<>();
        Map<String, String> params = JSONObject.parseObject(src, new TypeReference<Map<String, String>>() {
        });
        for (String key : params.keySet()) {
            map.put(key, new String[] { params.get(key) });
        }
        return map;
    }

    @Override
    public String getParameter(String name) {
        String[] values = getParameterMap().get(name);
        if (values != null) {
            return (values.length > 0 ? values[0] : null);
        }
        return super.getParameter(name);
    }

    @Override
    public String[] getParameterValues(String name) {
        String[] values = getParameterMap().get(name);
        if (values != null) {
            return values;
        }
        return super.getParameterValues(name);
    }

    @Override
    public Enumeration<String> getParameterNames() {
        Map<String, String[]> multipartParameters = getParameterMap();
        if (multipartParameters.isEmpty()) {
            return super.getParameterNames();
        }

        Set<String> paramNames = new LinkedHashSet<String>();
        Enumeration<String> paramEnum = super.getParameterNames();
        while (paramEnum.hasMoreElements()) {
            paramNames.add(paramEnum.nextElement());
        }
        paramNames.addAll(multipartParameters.keySet());
        return Collections.enumeration(paramNames);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return null == parameterMap ? super.getParameterMap() : parameterMap;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return this.inputStream == null ? super.getInputStream() : (ServletInputStream) this.inputStream;
    }
}

/**
 * 自定义ServletInputStream
 * 
 * @author ldy
 *
 */
private class DecryptInputStream extends ServletInputStream {

    private final InputStream sourceStream;

    /**
     * Create a DelegatingServletInputStream for the given source stream.
     * 
     * @param sourceStream
     *            the source stream (never {@code null})
     */
    public DecryptInputStream(InputStream sourceStream) {
        Assert.notNull(sourceStream, "Source InputStream must not be null");
        this.sourceStream = sourceStream;
    }

    @Override
    public int read() throws IOException {
        return this.sourceStream.read();
    }

    @Override
    public void close() throws IOException {
        super.close();
        this.sourceStream.close();
    }

    @Override
    public boolean isFinished() {
        return false;
    }

    @Override
    public boolean isReady() {
        return false;
    }

    @Override
    public void setReadListener(ReadListener readListener) {

    }
}

}
三、使用

1、后端需要签名或者加密的请求方法或类上加上注解

@CryptoType(Type.SIGN) // 签名

@CryptoType(Type.CRYPTO) // 加密

2、前端在请求方式传参数类型,

post(url, params, 2); // 签名

post('url', params, 1); // 加密

上一篇 下一篇

猜你喜欢

热点阅读