【spring cloud】对接口调用者提供API使用的安全验证微服务【这里仅通过代码展示一种设计思想】【后续可以加入redis限流的功能,某段时间某个IP可以访问API几次】

场景:

  公司的微服务集群,有些API 会对外提供接口,供其他厂商进行调用。这些公开的API接口,由一个OpenAPI微服务统一提供给大家。

  那么所有的调用者在调用公开API接口的时候,需要验证是否有权限调用API 接口。

  这套验证的工作,同样也在OpenAPI中为调用者提供验证。

==============================================================================================

简图说明:

  【spring cloud】对接口调用者提供API使用的安全验证微服务【这里仅通过代码展示一种设计思想】【后续可以加入redis限流的功能,某段时间某个IP可以访问API几次】

===============================================================================================================

正文仅通过贴出来的代码展示

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;
}
}

整个的思想 就是上面的这样。

  

上一篇:Redis 五种数据结构详解(string,hash,list,set,zset)


下一篇:poj2513- Colored Sticks 字典树+欧拉通路判断