|
|
package com.yoho.unions.interceptor;
|
|
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
|
import com.google.common.collect.UnmodifiableListIterator;
|
|
|
import com.yoho.core.common.utils.MD5;
|
|
|
import com.yoho.error.exception.ServiceException;
|
|
|
import com.yoho.unions.exception.RequestHeaderInvalidateException;
|
|
|
import com.yoho.unions.exception.SecurityNotMatchException;
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
import org.springframework.web.servlet.ModelAndView;
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
import java.util.*;
|
|
|
|
|
|
/**
|
|
|
* Created by xinfei on 16/2/20.
|
|
|
*/
|
|
|
public class SecurityInterceptor implements HandlerInterceptor {
|
|
|
|
|
|
private final Logger logger = LoggerFactory.getLogger(SecurityInterceptor.class);
|
|
|
|
|
|
|
|
|
//读取配置文件中的private key 配置
|
|
|
private final Map<String, String> privateKeyMap = new HashMap<>();
|
|
|
|
|
|
// 这些url不会进行client-security校验。 例如 "/notify"
|
|
|
private List<String> excludeUrls;
|
|
|
|
|
|
//这些方法不用校验
|
|
|
private List<String> excludeMethods;
|
|
|
|
|
|
//这些IP下的这些方法不校验
|
|
|
private Map<String /*method*/, String /*ip*/> excludeMethodsBySubnet;
|
|
|
//是否启用
|
|
|
private boolean isDebugEnable = false;
|
|
|
|
|
|
@Override
|
|
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
|
|
//(1)排除掉exclude和debug模式
|
|
|
if (this.isIgnore(request)) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
//(2)获取请求参数的信息
|
|
|
Map<String, String> params = this.getRequestInfo(request);
|
|
|
|
|
|
//(3)验证请求参数中是否包含必填参数, 如果不包含则请求失败(联盟暂时只做 client_secret 的校验)
|
|
|
this.validateReqParams(params);
|
|
|
|
|
|
//(4)校验安全码是否正确
|
|
|
this.validateSecurity(params);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
|
|
|
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 验证请求参数中的必填参数
|
|
|
*
|
|
|
* @param params 请求参数
|
|
|
* @throws ServiceException
|
|
|
*/
|
|
|
private void validateReqParams(Map<String, String> params) throws RequestHeaderInvalidateException {
|
|
|
ImmutableList must_exist_params = ImmutableList.of("client_secret");
|
|
|
UnmodifiableListIterator<String> it = must_exist_params.listIterator();
|
|
|
while (it.hasNext()) {
|
|
|
String k = it.next();
|
|
|
String headerValue = params.get(k);
|
|
|
if (StringUtils.isEmpty(headerValue)) {
|
|
|
logger.warn("header {} not exist or empty", k);
|
|
|
throw new RequestHeaderInvalidateException(k);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 验证请求参数的client_secret是否正确
|
|
|
*
|
|
|
* @param reqParams
|
|
|
* @throws SecurityNotMatchException
|
|
|
*/
|
|
|
private void validateSecurity(Map<String, String> reqParams) throws SecurityNotMatchException {
|
|
|
//根据请求参数生成加密码
|
|
|
String caculated_sign = this.getSign(reqParams);
|
|
|
//获取前台传入的client_secret, 比较参数是否一致
|
|
|
String request_sign = reqParams.get("client_secret");
|
|
|
if (!request_sign.equalsIgnoreCase(caculated_sign)) {
|
|
|
logger.warn("client security not match. request_sign:{}, caculate_sign:{}", request_sign, caculated_sign);
|
|
|
throw new SecurityNotMatchException();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 获取请求参数信息: requestParam
|
|
|
*
|
|
|
* @param httpServletRequest
|
|
|
* @return Map<String, String> 请求参数的键-值
|
|
|
*/
|
|
|
private Map<String, String> getRequestInfo(HttpServletRequest httpServletRequest) {
|
|
|
Map<String, String> map = new HashMap<>();
|
|
|
Enumeration paramNames = httpServletRequest.getParameterNames();
|
|
|
while (paramNames.hasMoreElements()) {
|
|
|
String key = (String) paramNames.nextElement();
|
|
|
String value = httpServletRequest.getParameter(key);
|
|
|
map.put(key, value);
|
|
|
}
|
|
|
return map;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据请求的参数生成client_secret
|
|
|
*
|
|
|
* @param reqParams
|
|
|
* @return
|
|
|
*/
|
|
|
private String getSign(Map<String, String> reqParams) {
|
|
|
//(1)删除不需要生成加密内容的参数
|
|
|
ImmutableList list = ImmutableList.of("/api", "client_secret", "q", "debug_data");
|
|
|
SortedMap<String, String> filtedMap = new TreeMap<>();
|
|
|
for (Map.Entry<String, String> entry : reqParams.entrySet()) {
|
|
|
String k = entry.getKey();
|
|
|
if (!list.contains(k)) {
|
|
|
filtedMap.put(k, entry.getValue());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//(2)根据客户端类型生成相映的客户端类型的KEY
|
|
|
String clientType = reqParams.get("client_type");
|
|
|
String privateKey = privateKeyMap.get(clientType);
|
|
|
filtedMap.put("private_key", privateKey);
|
|
|
|
|
|
|
|
|
//(3)将参数组装起来, 用=相连, 多个参数直接使用&连接, 如: string: k1=v1&k2=v2
|
|
|
List<String> array = new LinkedList<>();
|
|
|
for (Map.Entry<String, String> entry : filtedMap.entrySet()) {
|
|
|
String pair = entry.getKey() + "=" + entry.getValue();
|
|
|
array.add(pair.trim());
|
|
|
}
|
|
|
String signStr = String.join("&", array);
|
|
|
//sign md5
|
|
|
String sign = MD5.md5(signStr);
|
|
|
return sign.toLowerCase();
|
|
|
}
|
|
|
|
|
|
|
|
|
//排除掉不需要校验的
|
|
|
private boolean isIgnore(HttpServletRequest request) {
|
|
|
//如果请求url包含在过滤的url,则直接返回. 请求url可能是 "/gateway/xxx”这种包含了context的。
|
|
|
if (excludeUrls != null) {
|
|
|
final String requestUri = request.getRequestURI();
|
|
|
for (String excludeUri : excludeUrls) {
|
|
|
if (requestUri.equals(excludeUri) || requestUri.endsWith(excludeUri)) {
|
|
|
logger.info("excludeUri uri: {} for client-security check success.", requestUri);
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//如果请求method包含这些,则不校验
|
|
|
if (excludeMethods != null) {
|
|
|
final String method = request.getParameter("method");
|
|
|
if (StringUtils.isNotEmpty(method) && excludeMethods.contains(method)) {
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
//配置文件配置为 is_debug_enable 为true,并且请求携带参数debug为XYZ,就放行
|
|
|
if (isDebugEnable && "XYZ".equals(request.getParameter("debug"))) {
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* spring setter from security-keyyml
|
|
|
*
|
|
|
* @param keyConfigMap security key 的配置
|
|
|
*/
|
|
|
public void setKeyConfigMap(Map<String, Object> keyConfigMap) {
|
|
|
List<Map<String, Object>> keys = (List<Map<String, Object>>) keyConfigMap.get("client_keys");
|
|
|
|
|
|
for (Map<String, Object> one : keys) {
|
|
|
privateKeyMap.put((String) one.get("type"), (String) one.get("key"));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置不校验的url地址
|
|
|
*/
|
|
|
public void setExcludeUrls(List<String> excludeUrls) {
|
|
|
this.excludeUrls = excludeUrls;
|
|
|
}
|
|
|
|
|
|
public void setExcludeMethods(List<String> excludeMethods) {
|
|
|
this.excludeMethods = excludeMethods;
|
|
|
}
|
|
|
|
|
|
public void setIsDebugEnable(boolean isDebugEnable) {
|
|
|
this.isDebugEnable = isDebugEnable;
|
|
|
}
|
|
|
} |
...
|
...
|
|