互联网开放平台API安全设计
互联网开放平台设计
1.需求:现在A公司与B公司进行合作,B公司需要调用A公司开放的外网接口获取数据,
如何保证外网开放接口的安全性。
2.常用解决办法:
2.1 使用加签名方式,防止篡改数据
2.2 使用Https加密传输
2.3 搭建OAuth2.0认证授权
2.4 使用令牌方式
2.5 搭建网关实现黑名单和白名单
案例:A公司调用B公司的外网接口获取数据,如何保证外网开放接口的安全性。
如何保证外网开放接口的安全性:
1.搭建API网关控制接口访问权限
2.开放平台设计 开放凭他设计oauth2.0协议 QQ授权 第三方联合登录
3.采用Https加密传输协议 (使用Nginx配置Https)
4.API接口数字签名(移动的接口)非对称加密RSA
5. 基于令牌方式实现API接口调用。基于accessToken实现API调用 防止抓包分析篡改数据 基于accessToken实现AP调用
基于令牌方式实现:
表的设计:
开放平台提供者需要为每个合作机构提供对应的APPID APPSerect
基于令牌方式实现 AppId区分不同结构 永远不能变 AppSerect 在传输中实现加密功能(密钥) 可以发生改变 Appid+AppSerect生成对应access_token
表字段:
App_Name 表机构名称
App_ID 应用id
App_Serect 应用密钥(可更改)
Is_flag 是否可用
acess_token 上一次access_token
开发步骤:
Appid+AppSerect 对应生成accessToken
对应accessToken去表里查询。查询出的结果要求is_flag 是1, 0 代表不开放权限了
使用令牌方式搭建搭建API开放平台
原理:为每个合作机构创建对应的appid、app_secret,生成对应的access_token(有效期2小时),在调用外网开放接口的时候,必须传递有效的access_token。
生成accessToken之后
用accessToken对接口进行调用 此时会被 配置 API/* 拦截到,获取到携带哦的accessToken,然后去redis中查询对应的appId,如果redis中有对应的appId,利用appId去表中查询对应的app信息
信息包括isFlag 看权限是否开启 如果开启 继续往下走
放行到被拦截的 业务逻辑中
生成accessToken:
controller:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSONObject;
import com.itmayiedu.base.BaseApiService;
import com.itmayiedu.base.ResponseBase;
import com.itmayiedu.entity.AppEntity;
import com.itmayiedu.mapper.AppMapper;
import com.itmayiedu.utils.BaseRedisService;
import com.itmayiedu.utils.TokenUtils; // 创建获取getAccessToken
@RestController
@RequestMapping(value = "/auth")
public class AuthController extends BaseApiService {
@Autowired
private BaseRedisService baseRedisService;
private long timeToken = 60 * 60 * 2;
@Autowired
private AppMapper appMapper; // 使用appId+appSecret 生成AccessToke
@RequestMapping("/getAccessToken")
public ResponseBase getAccessToken(AppEntity appEntity) {
AppEntity appResult = appMapper.findApp(appEntity);
if (appResult == null) {
return setResultError("没有对应机构的认证信息");
}
int isFlag = appResult.getIsFlag();
if (isFlag == 1) {
return setResultError("您现在没有权限生成对应的AccessToken");
}
// ### 获取新的accessToken 之前删除之前老的accessToken
// 从redis中删除之前的accessToken
String accessToken = appResult.getAccessToken();
baseRedisService.delKey(accessToken);
// 生成的新的accessToken
String newAccessToken = newAccessToken(appResult.getAppId());
JSONObject jsonObject = new JSONObject();
jsonObject.put("accessToken", newAccessToken);
return setResultSuccessData(jsonObject); //继承的属性
} private String newAccessToken(String appId) {
// 使用appid+appsecret 生成对应的AccessToken 保存两个小时 保证唯一且临时
String accessToken = TokenUtils.getAccessToken();
// 保证在同一个事物redis 事务中
// 生成最新的token key为accessToken value 为 appid
baseRedisService.setString(accessToken, appId, timeToken); //key为accessToken
// 表中保存当前accessToken
appMapper.updateAccessToken(accessToken, appId);
return accessToken;
}
}
去调用API接口:
拦截器去进行处理验证:
import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView; import com.alibaba.fastjson.JSONObject;
import com.itmayiedu.base.BaseApiService;
import com.itmayiedu.utils.BaseRedisService; //验证AccessToken 是否正确
@Component
public class AccessTokenInterceptor extends BaseApiService implements HandlerInterceptor {
@Autowired
private BaseRedisService baseRedisService; public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)
throws Exception {
System.out.println("---------------------开始进入请求地址拦截----------------------------");
String accessToken = httpServletRequest.getParameter("accessToken");
// 判断accessToken是否空
if (StringUtils.isEmpty(accessToken)) {
// 参数Token accessToken
resultError(" this is parameter accessToken null ", httpServletResponse);
return false;
}
String appId = (String) baseRedisService.getString(accessToken);
if (StringUtils.isEmpty(appId)) {
// accessToken 已经失效!
resultError(" this is accessToken Invalid ", httpServletResponse);
return false;
}
// 正常执行业务逻辑...
return true; } public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o,
ModelAndView modelAndView) throws Exception {
System.out.println("--------------处理请求完成后视图渲染之前的处理操作---------------");
} public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Object o, Exception e) throws Exception {
System.out.println("---------------视图渲染之后的操作-------------------------0");
} // 返回错误提示
public void resultError(String errorMsg, HttpServletResponse httpServletResponse) throws IOException {
PrintWriter printWriter = httpServletResponse.getWriter();
printWriter.write(new JSONObject().toJSONString(setResultError(errorMsg)));
} }
拦截器的配置类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration
public class WebAppConfig {
@Autowired
private AccessTokenInterceptor accessTokenInterceptor; @Bean
public WebMvcConfigurer WebMvcConfigurer() {
return new WebMvcConfigurer() {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessTokenInterceptor).addPathPatterns("/openApi/*");
};
};
} }
接口controller:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import com.itmayiedu.base.BaseApiService;
import com.itmayiedu.base.ResponseBase; @RestController
@RequestMapping("/openApi")
public class MemberController extends BaseApiService { @RequestMapping("/getUser")
public ResponseBase getUser() {
return setResultSuccess("获取会员信息接口");
}
}
Base类:
@Component
public class BaseApiService { public ResponseBase setResultError(Integer code, String msg) {
return setResult(code, msg, null);
} // 返回错误,可以传msg
public ResponseBase setResultError(String msg) {
return setResult(Constants.HTTP_RES_CODE_500, msg, null);
} // 返回成功,可以传data值
public ResponseBase setResultSuccessData(Object data) {
return setResult(Constants.HTTP_RES_CODE_200, Constants.HTTP_RES_CODE_200_VALUE, data);
} public ResponseBase setResultSuccessData(Integer code, Object data) {
return setResult(code, Constants.HTTP_RES_CODE_200_VALUE, data);
} // 返回成功,沒有data值
public ResponseBase setResultSuccess() {
return setResult(Constants.HTTP_RES_CODE_200, Constants.HTTP_RES_CODE_200_VALUE, null);
} // 返回成功,沒有data值
public ResponseBase setResultSuccess(String msg) {
return setResult(Constants.HTTP_RES_CODE_200, msg, null);
} // 通用封装
public ResponseBase setResult(Integer code, String msg, Object data) {
return new ResponseBase(code, msg, data);
} }
package com.itmayiedu.base; import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; @Getter
@Setter
@Slf4j
public class ResponseBase { private Integer rtnCode;
private String msg;
private Object data; public ResponseBase() { } public ResponseBase(Integer rtnCode, String msg, Object data) {
super();
this.rtnCode = rtnCode;
this.msg = msg;
this.data = data;
} public static void main(String[] args) {
ResponseBase responseBase = new ResponseBase();
responseBase.setData("123456");
responseBase.setMsg("success");
responseBase.setRtnCode(200);
System.out.println(responseBase.toString());
log.info("itmayiedu...");
} @Override
public String toString() {
return "ResponseBase [rtnCode=" + rtnCode + ", msg=" + msg + ", data=" + data + "]";
} }
entity:表的实体类
public class AppEntity {
    private long id;
    private String appId;
    private String appName;
    private String appSecret;
    private String accessToken;
    private int isFlag;
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getAppName() {
        return appName;
    }
    public void setAppName(String appName) {
        this.appName = appName;
    }
    public String getAppSecret() {
        return appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
    public int getIsFlag() {
        return isFlag;
    }
    public void setIsFlag(int isFlag) {
        this.isFlag = isFlag;
    }
    public String getAccessToken() {
        return accessToken;
    }
    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }
}
Mapper:
ublic interface AppMapper {
    @Select("SELECT ID AS ID ,APP_NAME AS appName, app_id as appId, app_secret as appSecret ,is_flag as isFlag , access_token as accessToken from m_app "
            + "where app_id=#{appId} and app_secret=#{appSecret}  ")
    AppEntity findApp(AppEntity appEntity);
    @Select("SELECT ID AS ID ,APP_NAME AS appName, app_id as appId, app_secret as appSecret ,is_flag as isFlag  access_token as accessToken from m_app "
            + "where app_id=#{appId} and app_secret=#{appSecret}  ")
    AppEntity findAppId(@Param("appId") String appId);
    @Update(" update m_app set access_token =#{accessToken} where app_id=#{appId} ")
    int updateAccessToken(@Param("accessToken") String accessToken, @Param("appId") String appId);
}
Utils类:
import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component; @Component
public class BaseRedisService { @Autowired
private StringRedisTemplate stringRedisTemplate; public void setString(String key, Object data, Long timeout) {
if (data instanceof String) {
String value = (String) data;
stringRedisTemplate.opsForValue().set(key, value);
}
if (timeout != null) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
} public Object getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
} public void delKey(String key) {
stringRedisTemplate.delete(key);
} }
public interface Constants {
    // 响应请求成功
    String HTTP_RES_CODE_200_VALUE = "success";
    // 系统错误
    String HTTP_RES_CODE_500_VALUE = "fial";
    // 响应请求成功code
    Integer HTTP_RES_CODE_200 = 200;
    // 系统错误
    Integer HTTP_RES_CODE_500 = 500;
    // 未关联QQ账号
    Integer HTTP_RES_CODE_201 = 201;
    // 发送邮件
    String MSG_EMAIL = "email";
    // 会员token
    String TOKEN_MEMBER = "TOKEN_MEMBER";
    // 支付token
    String TOKEN_PAY = "TOKEN_pay";
    // 支付成功
    String PAY_SUCCESS = "success";
    // 支付白
    String PAY_FAIL = "fail";
    // 用户有效期 90天
    Long TOKEN_MEMBER_TIME = (long) (60 * 60 * 24 * 90);
    int COOKIE_TOKEN_MEMBER_TIME = (60 * 60 * 24 * 90);
    Long PAY_TOKEN_MEMBER_TIME = (long) (60 * 15);
    // cookie 会员 totoken 名称
    String COOKIE_MEMBER_TOKEN = "cookie_member_token";
}
public class TokenUtils {
    @RequestMapping("/getToken")
    public static String getAccessToken() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}
yml:
spring:
mvc:
view:
# 页面默认前缀目录
prefix: /WEB-INF/jsp/
# 响应页面默认后缀
suffix: .jsp spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
test-while-idle: true
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
time-between-eviction-runs-millis: 300000
min-evictable-idle-time-millis: 1800000
redis:
database: 1
host: 106.15.185.133
port: 6379
password: meitedu.+@
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
表单设计:
CREATE TABLE `m_app` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `app_name` varchar(255) DEFAULT NULL,
  `app_id` varchar(255) DEFAULT NULL,
  `app_secret` varchar(255) DEFAULT NULL,
  `is_flag` varchar(255) DEFAULT NULL,
  `access_token` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
App_Name 表示机构名称
App_ID 应用id
App_Secret 应用密钥 (可更改)
Is_flag 是否可用 (是否对某个机构开放)
access_token 上一次access_token
小结: 第一步 先去生成accessToken。 如果获取最新的accessToken(使用appid+appsecret 生成对应的AccessToken 保存两个小时 保证唯一且临时),需要从redis删除原先的accessTokne。
通过两个字段去查询:

第二步去调用接口时候 会去拦截器进行限制 ,从redis获取appId,然后去app表中获取信息,看看是否有权限。
原理:为每个合作机构创建对应的appid、app_secret,生成对应的access_token(有效期2小时),在调用外网开放接口的时候,必须传递有效的access_token。
互联网开放平台API安全设计的更多相关文章
- 百度AI开放平台- API实战调用
		
百度AI开放平台- API实战调用 一. 前言 首先说一下项目需求. 两个用户,分别上传了两段不同的文字,要计算两段文字相似度有多少,匹配数据库中的符合条件的数据,初步估计列出来会有60-1 ...
 - 谈互联网开放平台:“去中心化”大势所趋 zz
		
文/磐石之心 几天前与好友聊到众筹咖啡馆的事情,他向我讲述了一个非常具有特色的众筹咖啡馆案例.而这个案例也引发我对当前互联网开放.去中心和集权的一些思考,今天就简单写出来与大家分享. 一个无赚钱目的的 ...
 - Java对接拼多多开放平台API(加密上云等全流程)
		
前言 本文为[小小赫下士 blog]原创,搬运请保留本段,或请在醒目位置设置原文地址和原作者. 作者:小小赫下士 原文地址:Java对接拼多多开放平台API(加密上云等全流程) 本文章为企业ERP(I ...
 - 微信开放平台API开发资料
		
微信大概两年前开启了微信公众平台的API供开发者使用,从账号登陆.消息发送.用户账号管理.公众号菜单.客服接口.微信商店接口.用户卡券接口 以及微信支付接口.可以说是全方面覆盖了电商所需要的要素,与阿 ...
 - 微博开放平台api使用
		
前言:微博开放平台提供了微博数据的api接口,不仅可以直接通过api调用微博服务发布微博查询微博,更重要的是,可以在自己的网站上获得新浪微博api的授权,调用微博的某些内容,就好像我们再网站中看到好文 ...
 - 各开放平台API接口通用 SDK  前言
		
最近两年一直在做API接口相关的工作,在平时工作中以及网上看到很多刚接触API接口调用的新人一开始会感到很不适应,包括自己刚开始做API接口调用的相关工作时,也是比较抓狂的,所有写一序列文章把之前的工 ...
 - 各开放平台API接口通用SDK序列文章    前言
		
最近两年一直在做API接口相关的工作,在平时工作中以及网上看到很多刚接触API接口调用的新人一开始会感到很不适应,要看的文档一大堆,自己要调用的接口找不着,或都找着了不知道怎么去调用,记得包括自己刚开 ...
 - 如何使用OLAMI自然语言理解开放平台API制作自己的智能对话助手小程序
		
我们经常在电影中看到机器和人对答如流,随着越来越多自然语言开放平台的出现,IT爱好者制作一个自己的APP或者小玩具等逐渐可以变为现实. 自然语言对话即你的APP或者你制作的工具.机器人等能够对用户输入 ...
 - 微信小程序,天气预报(百度地图开放平台API)
		
小程序看似一种全新的东西,但好在基本上就是曾经HTML,CSS,JS的一个微变版本. 语法和之前一样.只是一些用法和名字(标签)发生了一些变化. 小程序主要就四种扩展名的文件:js,json,wxml ...
 
随机推荐
- 使用JMX监控Kafka
			
监控数据源 JMX RMI方式启动Broker,Consumer,Producer -ea -Dcom.sun.management.jmxremote.authenticate=false -Dco ...
 - 基础知识《三》java修饰符
			
一.修饰符 private 成员随时都是“私有”的,任何人不得访问.但在实际应用中,经常想把某些东西深深地藏起来,但同时允许访问衍生类的成员. protected 关键字可帮助我们做到这一点.它的意思 ...
 - 更轻更快的Vue.js 2.0与其他框架对比(转)
			
更轻更快的Vue.js 2.0 崭露头角的JavaScript框架Vue.js 2.0版本已经发布,在狂热的JavaScript世界里带来了让人耳目一新的变化. Vue创建者尤雨溪称,Vue 2.0 ...
 - LightOJ - 1422 (Halloween Costumes)
			
题目链接:传送门 题目大意:要参加聚会,对应聚会要穿对应衣服,衣服可以套着穿,也可以脱下来,但脱下来之后不能再穿,问参加完所有聚会至少需要几件衣服? 题目思路:区间DP 一开始自己没有想出来状态转移方 ...
 - 诡异的js
			
[] + {}; 隐式转换后,是0 那 {} + []呢? var a = 42,b; b = ( a++, a);
 - Scala学习之Tuple、Map、Array
			
1.Tuple Tuple的中文意思是元组,它的定义是不需要方法. 例如:val tup=(25,”Tuple”,”Map”,”Array”). 值得注意的是,Tuple在进行索引的时候,与我们平时所 ...
 - 决策树ID3算法python实现 -- 《机器学习实战》
			
from math import log import numpy as np import matplotlib.pyplot as plt import operator #计算给定数据集的香农熵 ...
 - jquery全景拖动查看效果
			
http://sc.chinaz.com/jiaoben/140722166830.htm
 - boost:property_tree::ini_parser:::read_ini 读取ini时崩溃
			
原因: 1 路径错误 2 配置文件中某一行缺少=,例如用// 做注释的,前面应该加";" 解决办法: 添加异常处理,实例代码如下: #include <boost/prope ...
 - Internet Explorer 1.0到9.0截图画廊
			
Internet Explorer 1.0 Windows 95 原先并没有IE,IE1.0是通过一个名叫Windows 95 Plus!Pack的扩展包来到用户的电脑中的,它的到来改变了Netsca ...