token验证对称加密
一:首先建一个加密类ApiSecuritySHA
import java.nio.charset.StandardCharsets; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class ApiSecuritySHA { private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256"; public ApiSecuritySHA() { } public static String sha256(String key, String msg) { try { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(msg.getBytes()); StringBuilder sb = new StringBuilder(); byte[] var6 = array; int var7 = array.length; for(int var8 = 0; var8 < var7; ++var8) { byte item = var6[var8]; sb.append(Integer.toHexString(item & 255 | 256), 1, 3); } return sb.toString().toUpperCase(); } catch (Exception var10) { throw new RuntimeException(var10); } } public static String sha1(String key, String data) { try { SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA1"); Mac sha1_HMAC = Mac.getInstance("HmacSHA1"); sha1_HMAC.init(secret_key); byte[] array = sha1_HMAC.doFinal(data.getBytes()); String hash = Base64.encodeBase64String(array); return hash; } catch (Exception var6) { throw new RuntimeException(var6); } } }
二:建一个拦截器进行token验证
import cn.xdf.scrm.common.cache.RedisService; import cn.xdf.scrm.common.util.ApiSecuritySHA; import com.alibaba.fastjson.JSONObject; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.*; /** * @author yaochunhui * @date 2021-08-30 * 1.使用HandlerInterceptorAdapter比直接实现HandlerInterceptor可以按需进行方法的覆盖 * 2.拦截器和切面的执行顺序:HandlerInterceptor preHandle ==》 HandlerMethodArgumentResolver 》 业务 Method》AOP afterReturning == 》ResponseBodyAdvice beforeBodyWrite 》 * HttpMessageConverter(转JSON )>HandlerInterceptor postHandle ==>HandlerInterceptor afterCompletion */ @Component public class CheckSignInterceptor extends HandlerInterceptorAdapter { @Resource private RedisService redisService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { /** * 验证签名算法: * 一:申请appId和secretKey * 应用使用者向CRM团队申请appId和secretKey。CRM团队在库里插入appId和secretKey,同事插入redis缓存 * 二:验证签名 * (1)headers中要含有appId、sign、ts。前端的sign由appId+secretKey+ts+参数顺序化字符串 * (2)A.比对ts是否超时,超时直接返回false。首先验证是否超时可以避免签名计算,减少取缓存的io时间和计算签名时间。 * B.拿到appId在缓存中查到secretKey,查不到直接返回false。查到后用appId+secretKey+ts+body部分的参数生成sign和前端传过的sign比对 */ /** * 第一:获取header参数 */ Map<String, String> headerMap = new HashMap<String, String>(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); headerMap.put(key, value); } String appId=headerMap.get("appId"); String sign=headerMap.get("sign"); String ts=headerMap.get("ts"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); JSONObject res = new JSONObject(); /** * 第二:验证参数是否为空,为空返回 */ if(StringUtils.isBlank(appId)||StringUtils.isBlank(sign)||StringUtils.isBlank(ts)){ res.put("success", "false"); res.put("code", "403"); res.put("msg", "appId或sign或ts不能会空"); response.getWriter().append(res.toString()); return false; } /** * 第三:token失效直接返回,时间戳单位秒 */ Long timeDiffer=System.currentTimeMillis()/1000-Long.valueOf(ts); int minute=30; if(timeDiffer>minute*60||timeDiffer<0){ res.put("success", "false"); res.put("code", "403"); res.put("msg", "签名验证超时,失效时间"+minute+"分"); response.getWriter().append(res.toString()); return false; } /** * 第四:秘钥是空说明无授权,直接返回 */ String secretKey=redisService.getStringValue(appId); if(StringUtils.isBlank(secretKey)){ res.put("success", "false"); res.put("code", "403"); res.put("msg", "无授权的secretKey,请联系crm开发申请"); response.getWriter().append(res.toString()); return false; } String requestMethod=request.getMethod(); String signResult=""; if(requestMethod.equals("GET")){ /** * 第五:生成sign */ Map<String,String[]> parameterMap=request.getParameterMap(); //Map<String,String[]> sortMap=map.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toMap(a->a.getKey(),a->a.getValue())); StringBuilder paramStr=new StringBuilder(); for (Map.Entry<String,String[]> entry: parameterMap.entrySet()) { paramStr.append(Arrays.toString(entry.getValue())); } String signOrigin=appId+secretKey+ts+paramStr.toString(); signResult = ApiSecuritySHA.sha256(secretKey,signOrigin); }else{ /** * post方式要区分application/x-www-form-urlencoded和application/json。 * x-www-form-urlencoded还是从request.getParameterMap()取数据; * json则用RequestWrapper包装下request */ String contentType = request.getHeader("content-type"); if (contentType.equals("application/x-www-form-urlencoded")) { /** * 第五:生成sign */ Map<String,String[]> parameterMap=request.getParameterMap(); StringBuilder paramStr=new StringBuilder(); for (Map.Entry<String,String[]> entry: parameterMap.entrySet()) { paramStr.append(Arrays.toString(entry.getValue())); } String signOrigin=appId+secretKey+ts+paramStr.toString(); signResult = ApiSecuritySHA.sha256(secretKey,signOrigin); } else if (contentType.equals("application/json")) { RequestWrapper requestWrapper = new RequestWrapper(request); String body = requestWrapper.getBody(); String signOrigin=appId+secretKey+ts+body; signResult = ApiSecuritySHA.sha256(secretKey,signOrigin); } } /** * 第六:验证sign的正确性 */ if(!sign.equals(signResult)){ res.put("success", "false"); res.put("code", "403"); res.put("msg", "sign不正确"); response.getWriter().append(res.toString()); return false; } return true; } }
三:配置拦截器
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; /** * @author vitem */ @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Resource private CheckSignInterceptor checkSignInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { //注册TestInterceptor拦截器 InterceptorRegistration registrationSys = registry.addInterceptor(checkSignInterceptor); //需要拦截的请求 registrationSys.addPathPatterns("/**"); //请求白名单 registrationSys.excludePathPatterns("/swagger*/**"); registrationSys.excludePathPatterns("/doc.html"); } }
四:对post的application/json进行特殊处理
4.1首先建立个RequestWrapper类
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { ex.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } }
4.2建立一个过滤器
import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @Component @WebFilter(urlPatterns = "/*", filterName = "channelFilter") public class ChannelsFilter implements Filter { @Override public void init(FilterConfig filterConfig) { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if (servletRequest instanceof HttpServletRequest) { //关注点 if (servletRequest.getContentType().equals("application/json")) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } } if (requestWrapper == null) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
4.3在Application启动类上加上@ServletComponentScan注解
@SpringBootApplication @EnableSwagger2 @EnableEncryptableProperties @EnableFeignClients @ServletComponentScan public class ScrmOpenApplication { public static void main(final String[] args) { SpringApplication.run(ScrmOpenApplication.class, args); } }
五:测试
@ApiOperation(value="get header验证") @GetMapping(value="/scrm/header") public Object getHeader(String name,String password){ return name+password; } @ApiOperation(value="post body json验证") @PostMapping(value="/scrm/json") public Object postBody(@RequestBody Student student){ return student.getName()+student.getPassword(); } @ApiOperation(value="post form urlencoded验证") @PostMapping(value="/scrm/form") public Object abc(String name,String password){ return name+password; }