【spring cloud】对接口调用者提供API使用的安全验证微服务【这里仅通过代码展示一种设计思想】【后续可以加入redis限流的功能,某段时间某个IP可以访问API几次】
场景:
公司的微服务集群,有些API 会对外提供接口,供其他厂商进行调用。这些公开的API接口,由一个OpenAPI微服务统一提供给大家。
那么所有的调用者在调用公开API接口的时候,需要验证是否有权限调用API 接口。
这套验证的工作,同样也在OpenAPI中为调用者提供验证。
==============================================================================================
简图说明:
===============================================================================================================
正文仅通过贴出来的代码展示
OpenAPI这个微服务在整个服务体系中做了什么事情
1.用户通过提供loginName和loginPwd来获取sessionKey
2.在获取sessionKey过程中,将
[loginName:sessionKey]
[sessionKey:JSON.toJSONString(userInfo)]
存入redis,并设置了有效期
3.用户每次访问接口,都要提供loginName+sign+调用API所需的参数列表
4.服务器端自定义拦截器,拦截到用户request,根据loginName取出sessionKey,按照规则生成sign
5.对比用户传入的sign和服务器端生成的sign,如果一致,则允许调用API
===============================================================================================================
代码说明:
1.pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.pisen</groupId> <artifactId>pisen-cloud-luna</artifactId> <version>1.0-SNAPSHOT</version> </parent> <artifactId>pisen-cloud-luna-ms-openapi</artifactId> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <!-- Spring boot 1.5.x 的 data-jpa 依赖暂时还没有 所有采用 1.4.x的 data-jpa --> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.googlecode.log4jdbc</groupId> <artifactId>log4jdbc</artifactId> <version>1.2</version> </dependency> <!-- ======================== 工具 ======================== END --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.6</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.0</version> </dependency> <dependency> <groupId>com.xiaoleilu</groupId> <artifactId>hutool-all</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.belerweb</groupId> <artifactId>pinyin4j</artifactId> <version>2.5.0</version> </dependency> <!-- ======================== 工具 ======================== END --> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.11</version> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <!-- Spring boot 1.5.x 的Redis依赖暂时还没有 所有采用 1.4.x的Redis --> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>com.pisen</groupId> <artifactId>pisen-cloud-luna-core</artifactId> <version>${parent.version}</version> </dependency> <!--feign--> <dependency> <groupId>com.pisen</groupId> <artifactId>pisen-cloud-luna-feign-ten</artifactId> <version>${parent.version}</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-codec/commons-codec --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.4</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.提供获取sessionKey的API 接口,地址是/free/sessionKey
FreeAPi
package com.pisen.cloud.luna.ms.openapi.api; import com.pisen.cloud.luna.core.result.AjaxResult; import com.pisen.cloud.luna.ms.openapi.base.domain.SysUser; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; /** * 开发者说明文档 以静态资源文件提供 */ @RequestMapping("/free") public interface IFreeApi { /** * 获取sessionKey * * 帐号/密码 非空 * @param sysUser * @return 返回sessionkey * @throws Exception */ @RequestMapping(value = "/sessionKey",method = RequestMethod.POST) public AjaxResult<String> getSessionKey(SysUser sysUser) throws Exception; }
package com.pisen.cloud.luna.ms.openapi.api.impl; import com.pisen.cloud.luna.core.result.AjaxResult; import com.pisen.cloud.luna.core.result.LunaResultBean; import com.pisen.cloud.luna.ms.openapi.base.domain.SysUser; import com.pisen.cloud.luna.ms.openapi.api.IFreeApi; import com.pisen.cloud.luna.ms.openapi.base.service.SysUserService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @RestController public class FreeApi implements IFreeApi { @Autowired SysUserService sysUserService; @Override public AjaxResult<String> getSessionKey(@RequestBody SysUser sysUser) throws Exception { AjaxResult<String> res = new AjaxResult<String>(); LunaResultBean.checkField(sysUser, "loginName","loginPwd"); String sessionKey = sysUserService.login(sysUser); if(StringUtils.isNotBlank(sessionKey)){ res.initTrue(sessionKey); }else{ res.initFalse("获取sessionKey失败:帐号密码错误", AjaxResult.ERROR_BUSINESS); } return res; } }
统一响应体
package com.pisen.cloud.luna.core.result; public class AjaxResult<T> extends LunaResultBean{ public AjaxResult(){} public AjaxResult(boolean success, String msg, int code, T obj) { super(success,msg,code); this.obj = obj; } private T obj; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public void initTrue(T obj){ this.success = true; this.msg = "successful"; this.code = SUCCESS_REQUEST; this.obj = obj; } public void initFalse(String msg, int code,T obj){ initFalse(msg, code); this.obj = obj; } }
package com.pisen.cloud.luna.core.result; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.List; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import com.pisen.cloud.luna.core.enums.LunaZullErrorMSG; import com.pisen.cloud.luna.core.exceptions.LunaException; public class LunaResultBean { /** * 参数错误返回码 */ public static final int SUCCESS_REQUEST = 200; /** * 参数错误返回码 */ public static final int ERROR_PARAMS = 100001; /** * 业务错误返回码 */ public static final int ERROR_BUSINESS = 200001; /** * 系统异常返回码 */ public static final int ERROR_SYS_EXCPTION = 500001; protected boolean success; protected String msg; protected int code; public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public LunaResultBean() { super(); } public LunaResultBean(boolean success, String msg, int code) { super(); this.success = success; this.msg = msg; this.code = code; } public void initFalse(String msg, int code){ throw new LunaException(msg, code); } public void initFalse2(String msg, int code){ this.success = false; this.msg = msg; this.code = code; } public void initFalse(LunaZullErrorMSG lunaZullErrorMSG){ this.success = false; this.msg = lunaZullErrorMSG.getMsg(); this.code = lunaZullErrorMSG.getCode(); } /** * 字段检验 方法 * @param clazz 需要检验的对象 * @param propertys * @return */ public static void checkField(Object obj,String...propertys) throws LunaException{ if(obj != null && propertys != null && propertys.length > 0){ //字节码 Class<? extends Object> clazz = obj.getClass(); //遍历所有属性 for (int i = 0; i < propertys.length; i++) { String property = propertys[i]; //内省机制获取属性信息 PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(clazz,property ); if(pd != null){ //获取当前字段的javabean读方法 Method readMethod = pd.getReadMethod(); if(readMethod != null){ Object invoke = null; try { invoke = readMethod.invoke(obj); } catch (Exception e) { throw new LunaException("方法 "+ readMethod.getName() +"无法执行",AjaxResult.ERROR_SYS_EXCPTION); } if(invoke != null){ //String类型单独处理 Class<?> propertyType = pd.getPropertyType(); if("java.lang.String".equals(propertyType.getName())){ if(StringUtils.isBlank((String)invoke)){ throw new LunaException("错误 : [ " + property + " ] 不能为空!",AjaxResult.ERROR_PARAMS); } }else if("java.util.List".equals(propertyType.getName())){ List list = (List)invoke; if(list.size() == 0){ throw new LunaException("错误 : [ " + property + " ] 不能为空!",AjaxResult.ERROR_PARAMS); } } }else{ throw new LunaException("错误 : [ " + property + " ] 不能为空!",AjaxResult.ERROR_PARAMS); } }else{ //抛出异常 throw new LunaException("在 " + clazz +"中 找不到"+"[ " + property + " ] 的 读方法",AjaxResult.ERROR_SYS_EXCPTION); } }else{ //抛出异常 throw new LunaException("在 " + clazz +"中 找不到"+"[ " + property + " ] 属性",AjaxResult.ERROR_SYS_EXCPTION); } } } } /** * 单一字段验证 * @param obj 需要验证的对象 * @param property 对象字段名 * @throws LunaException */ public static void simplCheckField(Object obj,String property) throws LunaException{ if(obj instanceof String){ if(StringUtils.isBlank((String)obj)){ throw new LunaException("错误 : [ " + property + " ] 不能为空!",AjaxResult.ERROR_PARAMS); } }else if(obj instanceof List){ List list = (List)obj; if(list.size() == 0){ throw new LunaException("错误 : [ " + property + " ] 不能为空!",AjaxResult.ERROR_PARAMS); } }else{ if(obj == null){ throw new LunaException("错误 : [ " + property + " ] 不能为空!",AjaxResult.ERROR_PARAMS); } } } }
service层
package com.pisen.cloud.luna.ms.openapi.base.service.impl; import com.pisen.cloud.luna.ms.openapi.base.dao.SysUserDao; import com.pisen.cloud.luna.ms.openapi.base.domain.SysUser; import com.pisen.cloud.luna.ms.openapi.base.service.SysUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class SysUserServiceImpl implements SysUserService { @Autowired SysUserDao sysUserDao; @Autowired RedisService redisService; @Override public String login(SysUser sysUser) { SysUser user = sysUserDao.findSysUserByLoginNameAndLoginPwdAndEnabled(sysUser.getLoginName(),sysUser.getLoginPwd(),1); String sessionKey = null; if (user != null){ sessionKey = redisService.getSessionKey(user); } return sessionKey; } }
需要引入的dao层和RedisService
package com.pisen.cloud.luna.ms.openapi.base.dao; ; import com.pisen.cloud.luna.ms.openapi.base.domain.SysUser; import feign.Param; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; public interface SysUserDao extends JpaRepository<SysUser, Long>,JpaSpecificationExecutor<SysUser> { //通过 登录名 + 登录密码 + 是否启用 查找user SysUser findSysUserByLoginNameAndLoginPwdAndEnabled(String loginName,String loginPwd,int enable); }
package com.pisen.cloud.luna.ms.openapi.base.service.impl; import com.alibaba.fastjson.JSON; import com.pisen.cloud.luna.core.utils.MD5Util; import com.pisen.cloud.luna.ms.openapi.base.domain.SysUser; import com.pisen.cloud.luna.ms.openapi.base.utils.MsOpenApiRedisUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * Redis 操作接口调用者 的 信息 * */ @Component public class RedisService { @Autowired private StringRedisTemplate redisTemplate; /** * 调用者认证信息存入在redis为[K:V],分别存入两层 * 1.[loginName:sessionKey] * 2.[sessionKey:userInfo] * 层次存储,所以具体的存入规则为 * 1.[MS-OPENAPI:SESSION_KEY_IN_LOGIN_NAME:loginName,sessionKey] * 2.[MS-OPENAPI:USER_INFO_IN_SESSION_KEY:sessionKey,JSON.toJSONString(user对象)] * * sessionKey是随机生成的,这里使用UUID生成,生成规则为 * String key1 = 登录名+"_"+ UUID.randomUUID().toString(); * String key2 = MD5Util.GetMD5Code(key1); * String key3 = MD5Util.GetMD5Code(key2+"openApi"); * 最后的key2就是返回给调用者的sessionKey * 最后的key3就是redis中存储使用的sessionKey * @param sysUser * @return */ public String getSessionKey(SysUser sysUser){ String loginName = sysUser.getLoginName(); String sessionkeyInLoginName = MsOpenApiRedisUtil.SESSION_KEY_IN_LOGIN_NAME+loginName; String oldSessionKey = redisTemplate.opsForValue().get(sessionkeyInLoginName); //如果 oldSessionKey存在 if (StringUtils.isNotBlank(oldSessionKey)){ //本次属于重复登录,则需要删除原本的登录信息 redisTemplate.delete(MsOpenApiRedisUtil.USER_INFO_IN_SESSION_KEY+oldSessionKey); } //重新生成sessionKey String key1 = loginName+"_"+ UUID.randomUUID().toString(); //返回给用户的sessionKey String key2 = MD5Util.GetMD5Code(key1); //redis中存储使用的sessionKey String key3 = MD5Util.GetMD5Code(key2+"openApi"); //存储 用户名:sessionKey 30分钟过期 redisTemplate.opsForValue().set(MsOpenApiRedisUtil.SESSION_KEY_IN_LOGIN_NAME+loginName,key3,30, TimeUnit.MINUTES); //存储 sessionKey:userInfo 30分钟过期 redisTemplate.opsForValue().set(MsOpenApiRedisUtil.USER_INFO_IN_SESSION_KEY+key3, JSON.toJSONString(sysUser),30,TimeUnit.MINUTES); //返回给调用者的是 初次MD5的sessionKey,防止调用者可以直接用sessionKey获取用户信息 return key2; } /** * 根据sessionKey获取用户信息 * @param sessionKey * @return */ public SysUser getUserInfo(String sessionKey){ String userInfoInSessionKey = MsOpenApiRedisUtil.USER_INFO_IN_SESSION_KEY+MD5Util.GetMD5Code(sessionKey+"openApi"); String jsonStr = redisTemplate.opsForValue().get(userInfoInSessionKey); if (StringUtils.isNotBlank(jsonStr)){ try { SysUser sysUser = JSON.parseObject(jsonStr,SysUser.class); String loginName = sysUser.getLoginName(); //重新设置过期时间为30分钟,刷新时间 redisTemplate.expire(MsOpenApiRedisUtil.SESSION_KEY_IN_LOGIN_NAME+loginName,30,TimeUnit.MINUTES); redisTemplate.expire(userInfoInSessionKey,30,TimeUnit.MINUTES); return sysUser; }catch (Exception e){ e.printStackTrace(); } } return null; } /** * 取消用户登录状态 * @param loginName */ public boolean deleteSessionKey(String loginName){ String sessionInLoginName = MsOpenApiRedisUtil.SESSION_KEY_IN_LOGIN_NAME+loginName; String oldSessionKey = redisTemplate.opsForValue().get(sessionInLoginName); //如果用户在登录状态,则清除用户相关信息 if (StringUtils.isNotBlank(oldSessionKey)) { redisTemplate.delete(MsOpenApiRedisUtil.USER_INFO_IN_SESSION_KEY+oldSessionKey); redisTemplate.delete(sessionInLoginName); return true; } return false; } }
redis用到的key规则
package com.pisen.cloud.luna.ms.openapi.base.utils; public class MsOpenApiRedisUtil { //redis空间名称 private static final String NAME_SPACE = "MS-OPENAPI:"; //通过 loginName 获取对应的 SESSION KEY public static final String SESSION_KEY_IN_LOGIN_NAME = NAME_SPACE + "SESSION_KEY_IN_LOGIN_NAME:"; //通过 session key 获取对应的 用户信息 public static final String USER_INFO_IN_SESSION_KEY = NAME_SPACE + "USER_INFO_IN_SESSION_KEY:"; }
3.spring boot项目中自定义拦截器
package com.pisen.cloud.luna.ms.openapi.api.interceptors; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.pisen.cloud.luna.core.result.AjaxResult; import com.pisen.cloud.luna.core.result.LunaResultBean; import com.pisen.cloud.luna.ms.openapi.api.beans.OpenApiResult; import com.pisen.cloud.luna.ms.openapi.base.feign.tenement.client.FeignMsTenClient; import com.pisen.cloud.luna.ms.openapi.base.utils.MsOpenApiRedisUtil; import com.pisen.cloud.luna.ms.openapi.base.utils.OpenApiSecureUtil; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; import java.util.Set; import java.util.TreeMap; /** * OpenApi拦截器 * */ public class OpenApiInterceptor implements HandlerInterceptor { public static final String[] FREE_URIS = new String[] { "/sessionKey" }; public static final ThreadLocal<String> OPENAPI_REQUEST_DATA = new ThreadLocal<>(); @Autowired StringRedisTemplate redisTemplate; @Autowired FeignMsTenClient feignMsTenClient; /** * 该方法将在请求处理之前进行调用,只有该方法返回true,才会继续执行后续的Interceptor和Controller, * 当返回值为true 时就会继续调用下一个Interceptor的preHandle 方法, * 如果已经是最后一个Interceptor的时候就会是调用当前请求的Controller方法 * @param httpServletRequest * @param httpServletResponse * @param o * @return * @throws Exception * * httpServletRequest 中提供 account sign API所需参数列表 * redis中存储的[K,V]是 [account,sessionKey][sessionKey,JSON(用户信息)] * 【注意】用户提供的sign是使用 调用了API提供的getSessionKey()方法之后得到的sessionKey 再进行 MD5Util.GetMD5Code(sessionKey+"openApi")处理,再按照下面的规则生成的 * * 本拦截器的作用:拦截指定路径的API调用,在调用API,进入API之前【即sign生成规则】 * 1.从request中获取到account,根据这个用户提供的account 从redis中获取到sessionKey * 2.将request中除了sign之外的所有请求参数,按照字段名ASCII码字典序,从小到大排序 * 3.将排序好的按照URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1 * 4.string1+sessionKey 得到string2 * 5.对string2作md5签名成32位小写字符串,也就是进行hash一致性算法,得到服务器端[也就是本拦截器]生成的sign值 * 6.对比服务器端sign和API调用者传进来的sign,如果签名一致,return true;允许调用API接口 */ @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { //标识 是否通过拦截器往下一层拦截器走或往controller走 boolean toNext = false; //URL 过滤 String requestURI = getCleanUri(httpServletRequest.getRequestURI()); if(StringUtils.isNotBlank(requestURI)){ for (String freeUri : FREE_URIS) { // 如果当前路径中包含不需要拦截的路径的话 则放行 if (requestURI.contains(freeUri)) { toNext = true; } } OPENAPI_REQUEST_DATA.set(OpenApiInterceptor.getOpenApiRequestData(httpServletRequest)); } OpenApiResult result = new OpenApiResult(); if (!toNext){ //拿到request中的数据 String requestData = OPENAPI_REQUEST_DATA.get(); //json序列化 request中的数据 JSONObject jsonObject = JSON.parseObject(requestData); //获取 接口调用者提供的 账户名和签名 String account = jsonObject.getString("account"); String sign = jsonObject.getString("sign"); if (StringUtils.isNotBlank(account) && StringUtils.isNotBlank(sign)){ ValueOperations<String,String> operations = redisTemplate.opsForValue(); //获取Redis中存储的本账户的sessionKey[30分钟有效期] String oldSessionKey = operations.get(MsOpenApiRedisUtil.SESSION_KEY_IN_LOGIN_NAME+account); //如果SessionKey有效 则验证 签名 sign if (StringUtils.isNotBlank(oldSessionKey)){ TreeMap<String,String> treeMap = new TreeMap<>(); Set<String> objSet = jsonObject.keySet(); for (String key: objSet){ if (!"sign".equals(key)){ //放进treeMap就是字典序排序,除了sign之外其余都要参与排序 treeMap.put(key,jsonObject.getString(key)); } } String createSign = OpenApiSecureUtil.getSign(treeMap,oldSessionKey); if (sign.equals(createSign)){ toNext = true; } }else{ toNext = false; result.setMessage("sessionKey过期,调用失败"); } }else{ toNext = false; result.setMessage("account/sign无效,调用失败"); } } if (!toNext){ httpServletResponse.setCharacterEncoding("utf-8"); PrintWriter printWriter = httpServletResponse.getWriter(); printWriter.write(JSON.toJSONString(result)); } httpServletResponse.setContentType("text/plain;charset=UTF-8"); return toNext; } /** * 该方法将在请求处理之后,DispatcherServlet进行视图返回渲染之前进行调用, * 可以在这个方法中对Controller 处理之后的ModelAndView 对象进行操作 * @param httpServletRequest * @param httpServletResponse * @param o * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } /** * 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行,该方法将在整个请求结束之后, * 也就是在DispatcherServlet 渲染了对应的视图之后执行。用于进行资源清理。 * @param httpServletRequest * @param httpServletResponse * @param o * @param e * @throws Exception */ @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } // 获取干净的API 去除多余的/ public static String getCleanUri(String uri) { String[] split = uri.split("\\/"); StringBuffer sb = new StringBuffer(); for (String string : split) { if (StringUtils.isNotBlank(string)) { sb.append("/" + string); } } return sb.toString(); } /** * 获取request中的数据 * @param request * @return */ public static String getOpenApiRequestData(HttpServletRequest request){ try { int contentLength = request.getContentLength(); if (contentLength < 0) { return null; } byte buffer[] = new byte[contentLength]; for (int i = 0; i < contentLength;) { int readlen = request.getInputStream().read(buffer, i, contentLength - i); if (readlen == -1) { break; } i += readlen; } String charEncoding = request.getCharacterEncoding(); if (charEncoding == null) { charEncoding = "UTF-8"; } return new String(buffer, charEncoding); } catch (Exception e) { e.printStackTrace(); } return null; } }
需要返回的结构
package com.pisen.cloud.luna.ms.openapi.api.beans; public class OpenApiResult<T> { private String message; private int result; private String sessionKey; private T object; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public int getResult() { return result; } public void setResult(int result) { this.result = result; } public String getSessionKey() { return sessionKey; } public void setSessionKey(String sessionKey) { this.sessionKey = sessionKey; } public T getObject() { return object; } public void setObject(T object) { this.object = object; } public void initTrue(String token) { message = "调用成功"; result = 1; sessionKey = token; object = null; } public void initTrue(String token,T object){ message = "调用成功"; result = 1; sessionKey = token; this.object = object; } public void initFalse(String message) { this.message = message; result = 2; sessionKey = ""; object = null; } }
生成sign的工具类
package com.pisen.cloud.luna.ms.openapi.base.utils; import org.apache.commons.codec.digest.DigestUtils; import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class OpenApiSecureUtil { /** * 完成算法的3.4.5步 获取到服务器端自己生成的sign * 3.将排序好的按照URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串string1 * 4.string1+sessionKey 得到string2 * 5.对string2作md5签名成32位小写字符串,也就是进行hash一致性算法,得到服务器端[也就是本拦截器]生成的sign值 * @param treeMap * @param sessionKey * @return */ public static String getSign(TreeMap<String,String> treeMap,String sessionKey){ StringBuilder stringBuilder = new StringBuilder(200); Set<Map.Entry<String, String>> entrySet = treeMap.entrySet(); for (Map.Entry<String, String> entry : entrySet) { stringBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&"); } String string = stringBuilder.substring(0, stringBuilder.length() - 1) + sessionKey; try { byte[] array = computeHash(string); stringBuilder.delete(0,stringBuilder.length()); for (int i = 0; i < array.length; i++) { byte b = array[i]; String text = Integer.toHexString(b & 0xFF); if (text.length() == 1) { stringBuilder.append("0"); } stringBuilder.append(text); } return stringBuilder.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return null; } /** * hash一致性算法 * 对比DigestUtils.md5Hex(string);到底有什么区别 * @param string * @return * @throws NoSuchAlgorithmException */ public static byte[] computeHash(String string) throws NoSuchAlgorithmException { MessageDigest digest = MessageDigest.getInstance("MD5"); digest.reset(); byte[] utf8bytes = null; try { utf8bytes = string.getBytes("UTF-8"); // digest.update(utf8bytes); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return digest.digest(utf8bytes); } }
4.把自定义的拦截器 添加到配置中可以自动被加载
package com.pisen.cloud.luna.ms.openapi.init.config; import com.pisen.cloud.luna.ms.openapi.api.interceptors.OpenApiInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class OpenApiConfiguration extends WebMvcConfigurerAdapter{ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new OpenApiInterceptor()).addPathPatterns("/openApi/**").excludePathPatterns("/free/**"); super.addInterceptors(registry); } }
5.当然 最后可以写一个 示例,提供给调用者调用的API接口
@RestController @RequestMapping("/openApi") public class OpenApi { @RequestMapping("/test") public AjaxResult<String> test(){ System.out.println(123); AjaxResult<String> result = new AjaxResult<>(); result.initTrue("123"); return result; } }
整个的思想 就是上面的这样。