互聯網開放平台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。

 

 

 

 

 

 

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM