互联网开放平台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 ...
随机推荐
- 在 App Store 三年學到的 13 件事(下)
博文转载至 http://blog.csdn.net/iunion/article/details/18959801 Steven Shen,曾經寫過一本書,也翻過一本書,開發 iOS app ...
- 网络虚拟化之FlowVisor:网络虚拟层(上)
概念解释:切片:虚拟网络的一个实例 一. 网络虚拟化(虚拟网络) 人类社会的发展在很大方面得益于自然界,飞机受益于鸟,雷达受益于蝙蝠等等,所以专门有个学科为仿生学就是研究和模仿生物的特殊本质,利用生物 ...
- Windows 磁盘分区
在“我的电脑”右键,点击“管理”,打开计算机管理,然后如图操作
- 巨蟒python全栈开发数据库前端5:JavaScript1
1.js介绍&变量&基础数据类型 2.类型查询&运算符&if判断&for循环 3.while循环&三元运算符 4.函数 5.今日总结 1.js介绍&am ...
- Downgrading an Exchange 2010 Server(Exchange降级)
Downgrading an Exchange 2010 Server Microsoft Exchange Server 2010 comes in two versions: enterprise ...
- use getters and setters Learning PHP Design Patterns
w Learning PHP Design Patterns Much of what passes as OOP misuses getters and setters, and making ac ...
- Grunt-Less批量编译为css
Grunt批量编译less module.exports = function (grunt) { grunt.initConfig({ less: { main: { expand: true, s ...
- python基础之类和对象、对象之间的交互、类名称空间与对象/实例名称空间
一 面向对象初识 Python要么是面向过程要么是面向对象. 概念及优缺点: 面向过程的程序设计的核心是过程,过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东 ...
- module使用
官方文档:http://modules.sourceforge.net/ 加载 module load 卸载 module unload 查看已加载 module list 查看可用 module a ...
- CNI IPAM插件分析 --- 以hostlocal为示例
skel.CmdArgs数据结构如下所示: type CmdArgs struct { ContainerID string Netns string IfName string Args strin ...