Spring設計安全的Restful介面
阿新 • • 發佈:2019-01-13
設計安全的Restful介面:無使用者互動狀態的介面安全設計。我們需要實現UR攔截請求攔截,介面接入授權驗證和請求驗重。本文中不涉及任何使用者校驗。
設計原理
1.UR攔截請求攔截:通過URL進行攔截過濾;
2.接入授權驗證:驗證請求頭Token;
3.請求驗重:驗證請求序列Sequence;
注:文件禁用於商業用途!
安全過濾器
print?- package com.wlyd.fmcgwms.util.security;
- import java.io.IOException;
- import java.io.PrintWriter;
- import javax.servlet.Filter;
- import javax.servlet.FilterChain;
- import javax.servlet.FilterConfig;
- import javax.servlet.ServletException;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import com.alibaba.fastjson.JSONObject;
- import com.wlyd.fmcgwms.util.Log;
- import com.wlyd.fmcgwms.util.SAASTokenManager;
- import com.wlyd.fmcgwms.util.StaticProperty;
- import com.wlyd.fmcgwms.util.api.RSAUtils;
- import com.wlyd.fmcgwms.util.ehcache.EhcacheUtil;
- import com.wlyd.fmcgwms.util.sysinit.InitSysProperties;
- /**
- * 介面安全過濾器
- *
- * @package com.wlyd.fmcgwms.util.security.SecurityFilter
- * @date 2017年3月15日 下午1:57:18
- * @author pengjunlin
- * @comment
- * @update
- */
- publicclass SecurityFilter implements Filter {
- privatestatic String API_ISENABLE ;// WMS是否開通對外介面
- privatestatic String API_PALTFORM;// WMS開放的平臺編碼
- privatestatic String API_MEMBERCODE;//WMS開放的服務商編碼
- privatestatic String publicKey;// 公鑰
- privatestatic String privateKey;// 私鑰
- @Override
- publicvoid destroy() {
- // TODO Auto-generated method stub
- }
- /**
- * 驗證配置是否規範
- *
- * @MethodName: isConfiged
- * @Description:
- * @param platformCode
- * @param memberCode
- * @return
- * @throws
- */
- privateboolean isConfiged(String platformCode,String memberCode){
- if(API_ISENABLE==null||!API_ISENABLE.equals("true")){
- returnfalse;
- }
- if(API_PALTFORM==null||platformCode==null||!platformCode.equals(API_PALTFORM)){
- returnfalse;
- }
- if (API_MEMBERCODE==null||memberCode == null||! memberCode.equals(API_MEMBERCODE)) {
- returnfalse;
- }
- returntrue;
- }
- /**
- * 驗證token是否有效
- *
- * @MethodName: validateToken
- * @Description:
- * @param token
- * @return
- * @throws
- */
- privateboolean validateToken(String token){
- if(token==null||token.equals("")){
- returnfalse;
- }
- String params[]=SAASTokenManager.decryptToken(privateKey, token, "&");
- if(params==null||params.length<3){
- returnfalse;
- }
- long now=System.currentTimeMillis();
- // Token超時驗證20s
- long seconds=(now-Long.valueOf(params[2]))/1000;
- if(!API_PALTFORM.equals(params[0])||!API_MEMBERCODE.equals(params[1])||seconds>20){
- returnfalse;
- }
- returntrue;
- }
- /**
- * 驗證請求是否重複
- *
- * @MethodName: validateSequece
- * @Description:
- * @param sequence
- * @return
- * @throws
- */
- publicboolean validateSequece(String sequence){
- if(sequence==null||sequence.equals("")){
- returnfalse;
- }
- String requestSequence=(String) EhcacheUtil.get(StaticProperty.REQUESTCACHE, sequence);
- // 請求序列相同驗證失敗
- if(requestSequence!=null&&sequence.equals(requestSequence)){
- returnfalse;
- }
- returntrue;
- }
- @Override
- publicvoid doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- PrintWriter out = null;
- HttpServletRequest req = (HttpServletRequest) request;
- HttpServletResponse res = (HttpServletResponse) response;
- String platformCode = req.getHeader("PlatformCode");
- String memberCode = req.getHeader("MemberCode");
- String token = req.getHeader("Token");
- String sequence = req.getHeader("Sequence");
- //String path = req.getServletPath();
- try {
- byte [] bytes=SAASTokenManager.generateBytesToken(publicKey, platformCode, memberCode,"&");
- token = RSAUtils.bytesToString(bytes);
- } catch (Exception e1) {
- e1.printStackTrace();
- }
- // 驗證介面是否配置正確
- if(!isConfiged(platformCode, memberCode)){
- JSONObject json = new JSONObject();
- json.put("IsSuccess", "false");
- json.put("OperationDesc", "API parameters are not configed right! ");
- json.put("ResultCode", ResultCode.OPEN_API_CONFIG_ERROR);
- try {
- out = res.getWriter();
- out.write(json.toJSONString());
- } catch (Exception e) {
- e.printStackTrace();
- }
- return;
- }
- // 驗證Token是否合法
- if(!validateToken(token)){
- JSONObject json = new JSONObject();
- json.put("IsSuccess", "false");
- json.put("OperationDesc", "Unauthorized:Token is invalid!");
- json.put("ResultCode", ResultCode.OPEN_API_TOKEN_INVALID);
- try {
- out = res.getWriter();
- out.write(json.toJSONString());
- } catch (Exception e) {
- e.printStackTrace();
- }
- return;
- }
- // 驗證Sequence是否合法
- if(!validateSequece(sequence)){
- JSONObject json = new JSONObject();
- json.put("IsSuccess", "false");
- json.put("OperationDesc", "Refused:request API too frequently!");
- json.put("ResultCode", ResultCode.OPEN_API_REQUEST_REQUENTLY);
- try {
- out = res.getWriter();
- out.write(json.toJSONString());
- } catch (Exception e) {
- e.printStackTrace();
- }
- return;
- }
- chain.doFilter(request, response);
- }
- @Override
- publicvoid init(FilterConfig arg0) throws ServletException {
- Log.getLogger(getClass()).info(">>>SecurityFilter invoke init method。。。。。。。。。START!");
- API_ISENABLE = InitSysProperties.getLowerCaseFromEhcache(StaticProperty.WMS_OPEN_API_ISENABLE);
- API_PALTFORM = InitSysProperties.getUpperCaseFromEhcache(StaticProperty.WMS_OPEN_API_PLATFORM);
- API_MEMBERCODE = InitSysProperties.getUpperCaseFromEhcache(StaticProperty.WMS_OPEN_API_MEMBERCODE);
- publicKey=EhcacheUtil.get(StaticProperty.WMS_OPEN_API_RSA_PUBLIC_KEY).toString();
- privateKey=EhcacheUtil.get(StaticProperty.WMS_OPEN_API_RSA_PRIVATE_KEY).toString();
- Log.getLogger(getClass()).info(">>>SecurityFilter invoke init method。。。。。。。。。SUCCESS!");
- }
- }
package com.wlyd.fmcgwms.util.security;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.wlyd.fmcgwms.util.Log;
import com.wlyd.fmcgwms.util.SAASTokenManager;
import com.wlyd.fmcgwms.util.StaticProperty;
import com.wlyd.fmcgwms.util.api.RSAUtils;
import com.wlyd.fmcgwms.util.ehcache.EhcacheUtil;
import com.wlyd.fmcgwms.util.sysinit.InitSysProperties;
/**
* 介面安全過濾器
*
* @package com.wlyd.fmcgwms.util.security.SecurityFilter
* @date 2017年3月15日 下午1:57:18
* @author pengjunlin
* @comment
* @update
*/
public class SecurityFilter implements Filter {
private static String API_ISENABLE ;// WMS是否開通對外介面
private static String API_PALTFORM;// WMS開放的平臺編碼
private static String API_MEMBERCODE;//WMS開放的服務商編碼
private static String publicKey;// 公鑰
private static String privateKey;// 私鑰
@Override
public void destroy() {
// TODO Auto-generated method stub
}
/**
* 驗證配置是否規範
*
* @MethodName: isConfiged
* @Description:
* @param platformCode
* @param memberCode
* @return
* @throws
*/
private boolean isConfiged(String platformCode,String memberCode){
if(API_ISENABLE==null||!API_ISENABLE.equals("true")){
return false;
}
if(API_PALTFORM==null||platformCode==null||!platformCode.equals(API_PALTFORM)){
return false;
}
if (API_MEMBERCODE==null||memberCode == null||! memberCode.equals(API_MEMBERCODE)) {
return false;
}
return true;
}
/**
* 驗證token是否有效
*
* @MethodName: validateToken
* @Description:
* @param token
* @return
* @throws
*/
private boolean validateToken(String token){
if(token==null||token.equals("")){
return false;
}
String params[]=SAASTokenManager.decryptToken(privateKey, token, "&");
if(params==null||params.length<3){
return false;
}
long now=System.currentTimeMillis();
// Token超時驗證20s
long seconds=(now-Long.valueOf(params[2]))/1000;
if(!API_PALTFORM.equals(params[0])||!API_MEMBERCODE.equals(params[1])||seconds>20){
return false;
}
return true;
}
/**
* 驗證請求是否重複
*
* @MethodName: validateSequece
* @Description:
* @param sequence
* @return
* @throws
*/
public boolean validateSequece(String sequence){
if(sequence==null||sequence.equals("")){
return false;
}
String requestSequence=(String) EhcacheUtil.get(StaticProperty.REQUESTCACHE, sequence);
// 請求序列相同驗證失敗
if(requestSequence!=null&&sequence.equals(requestSequence)){
return false;
}
return true;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
PrintWriter out = null;
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String platformCode = req.getHeader("PlatformCode");
String memberCode = req.getHeader("MemberCode");
String token = req.getHeader("Token");
String sequence = req.getHeader("Sequence");
//String path = req.getServletPath();
try {
byte [] bytes=SAASTokenManager.generateBytesToken(publicKey, platformCode, memberCode,"&");
token = RSAUtils.bytesToString(bytes);
} catch (Exception e1) {
e1.printStackTrace();
}
// 驗證介面是否配置正確
if(!isConfiged(platformCode, memberCode)){
JSONObject json = new JSONObject();
json.put("IsSuccess", "false");
json.put("OperationDesc", "API parameters are not configed right! ");
json.put("ResultCode", ResultCode.OPEN_API_CONFIG_ERROR);
try {
out = res.getWriter();
out.write(json.toJSONString());
} catch (Exception e) {
e.printStackTrace();
}
return;
}
// 驗證Token是否合法
if(!validateToken(token)){
JSONObject json = new JSONObject();
json.put("IsSuccess", "false");
json.put("OperationDesc", "Unauthorized:Token is invalid!");
json.put("ResultCode", ResultCode.OPEN_API_TOKEN_INVALID);
try {
out = res.getWriter();
out.write(json.toJSONString());
} catch (Exception e) {
e.printStackTrace();
}
return;
}
// 驗證Sequence是否合法
if(!validateSequece(sequence)){
JSONObject json = new JSONObject();
json.put("IsSuccess", "false");
json.put("OperationDesc", "Refused:request API too frequently!");
json.put("ResultCode", ResultCode.OPEN_API_REQUEST_REQUENTLY);
try {
out = res.getWriter();
out.write(json.toJSONString());
} catch (Exception e) {
e.printStackTrace();
}
return;
}
chain.doFilter(request, response);
}
@Override
public void init(FilterConfig arg0) throws ServletException {
Log.getLogger(getClass()).info(">>>SecurityFilter invoke init method。。。。。。。。。START!");
API_ISENABLE = InitSysProperties.getLowerCaseFromEhcache(StaticProperty.WMS_OPEN_API_ISENABLE);
API_PALTFORM = InitSysProperties.getUpperCaseFromEhcache(StaticProperty.WMS_OPEN_API_PLATFORM);
API_MEMBERCODE = InitSysProperties.getUpperCaseFromEhcache(StaticProperty.WMS_OPEN_API_MEMBERCODE);
publicKey=EhcacheUtil.get(StaticProperty.WMS_OPEN_API_RSA_PUBLIC_KEY).toString();
privateKey=EhcacheUtil.get(StaticProperty.WMS_OPEN_API_RSA_PRIVATE_KEY).toString();
Log.getLogger(getClass()).info(">>>SecurityFilter invoke init method。。。。。。。。。SUCCESS!");
}
}
注:需要通過RSA工具生成金鑰對(公鑰&私鑰)。
web.xml配置過濾器
print?- <filter>
- <filter-name>SecurityFilter</filter-name>
- <filter-class>com.wlyd.fmcgwms.util.security.SecurityFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>SecurityFilter</filter-name>
- <url-pattern>/openapi/*</url-pattern>
- </filter-mapping>
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>com.wlyd.fmcgwms.util.security.SecurityFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<url-pattern>/openapi/*</url-pattern>
</filter-mapping>
控制層openapi介面
print?- package com.wlyd.fmcgwms.controller.security;
- import java.util.HashMap;
- import java.util.Map;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.ResponseBody;
- import com.wlyd.fmcgwms.controller.BaseController;
- import com.wlyd.fmcgwms.util.Tools;
- /**
- * 開放API控制層
- *
- * @package com.wlyd.fmcgwms.controller.platform.OpenAPIController
- * @date 2017年3月14日 下午4:52:12
- * @author pengjunlin
- * @comment
- * @update
- */
- @Controller
- @RequestMapping("/openapi")
- publicclass OpenAPIController extends BaseController{
- /**
- * 未授權
- *
- * @MethodName: unauthenticated
- * @Description:
- * @return
- * @throws
- */
- @RequestMapping("/unauthenticated")
- @ResponseBody
- public String unauthenticated(){
- Map<String,Object> map=new HashMap<String, Object>();
- map.put("IsSuccess", "false");
- map.put("OperationDesc", "Unauthenticated:Please contact to WMS developers!");
- return Tools.toJson(map);
- }
- /**
- * 授權成功
- *
- * @MethodName: success
- * @Description:
- * @return
- * @throws
- */
- @RequestMapping("/success")
- @ResponseBody
- public String success(){
- Map<String,Object> map=new HashMap<String, Object>();
- map.put("IsSuccess", "true");
- map.put("OperationDesc", "Authenticated!");
- return Tools.toJson(map);
- }
- }
package com.wlyd.fmcgwms.controller.security;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wlyd.fmcgwms.controller.BaseController;
import com.wlyd.fmcgwms.util.Tools;
/**
* 開放API控制層
*
* @package com.wlyd.fmcgwms.controller.platform.OpenAPIController
* @date 2017年3月14日 下午4:52:12
* @author pengjunlin
* @comment
* @update
*/
@Controller
@RequestMapping("/openapi")
public class OpenAPIController extends BaseController{
/**
* 未授權
*
* @MethodName: unauthenticated
* @Description:
* @return
* @throws
*/
@RequestMapping("/unauthenticated")
@ResponseBody
public String unauthenticated(){
Map<String,Object> map=new HashMap<String, Object>();
map.put("IsSuccess", "false");
map.put("OperationDesc", "Unauthenticated:Please contact to WMS developers!");
return Tools.toJson(map);
}
/**
* 授權成功
*
* @MethodName: success
* @Description:
* @return
* @throws
*/
@RequestMapping("/success")
@ResponseBody
public String success(){
Map<String,Object> map=new HashMap<String, Object>();
map.put("IsSuccess", "true");
map.put("OperationDesc", "Authenticated!");
return Tools.toJson(map);
}
}
沒有其它的方法。
RSA加密工具
print?- package com.wlyd.fmcgwms.util.api;
- import java.io.ByteArrayOutputStream;
- import java.security.Key;
- import java.security.KeyFactory;
- import java.security.KeyPair;
- import java.security.KeyPairGenerator;
- import java.security.PrivateKey;
- import java.security.PublicKey;
- import java.security.Signature;
- import java.security.interfaces.RSAPrivateKey;
- import java.security.interfaces.RSAPublicKey;
- import java.security.spec.PKCS8EncodedKeySpec;
- import java.security.spec.X509EncodedKeySpec;
- import java.util.HashMap;
- import java.util.Map;
- import javax.crypto.Cipher;
- /**
- * <p>
- * RSA公鑰/私鑰/簽名工具包
- * </p>
- * <p>
- * 羅納德·李維斯特(Ron [R]ivest)、阿迪·薩莫爾(Adi [S]hamir)和倫納德·阿德曼(Leonard [A]dleman)
- * </p>
- * <p>
- * 字串格式的金鑰在未在特殊說明情況下都為BASE64編碼格式<br/>
- * 由於非對稱加密速度極其緩慢,一般檔案不使用它來加密而是使用對稱加密,<br/>
- * 非對稱加密演算法可以用來對對稱加密的金鑰加密,這樣保證金鑰的安全也就保證了資料的安全
- * </p>
- *
- * @author IceWee
- * @date 2012-4-26
- * @version 1.0
- */
- publicclass RSAUtils {
- /**
- * 加密演算法RSA
- */
- publicstaticfinal String KEY_ALGORITHM = "RSA";
- /**
- * 簽名演算法
- */
- publicstaticfinal String SIGNATURE_ALGORITHM = "MD5withRSA";
- /**
- * 獲取公鑰的key
- */
- privatestaticfinal String PUBLIC_KEY = "RSAPublicKey";
- /**
- * 獲取私鑰的key
- */
- privatestaticfinal String PRIVATE_KEY = "RSAPrivateKey";
- /**
- * RSA最大加密明文大小
- */
- privatestaticfinalint MAX_ENCRYPT_BLOCK = 117;
- /**
- * RSA最大解密密文大小
- */
- privatestaticfinalint MAX_DECRYPT_BLOCK = 128;
- /**
- * <p>
- * 生成金鑰對(公鑰和私鑰)
- * </p>
- *
- * @return
- * @throws Exception
- */
- publicstatic Map<String, Object> genKeyPair() throws Exception {
- KeyPairGenerator keyPairGen = KeyPairGenerator
- .getInstance(KEY_ALGORITHM);
- keyPairGen.initialize(1024);
- KeyPair keyPair = keyPairGen.generateKeyPair();
- RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
- RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
- Map<String, Object> keyMap = new HashMap<String, Object>(2);
- keyMap.put(PUBLIC_KEY, publicKey);
- keyMap.put(PRIVATE_KEY, privateKey);
- return keyMap;
- }
- /**
- * <p>
- * 用私鑰對資訊生成數字簽名
- * </p>
- *
- * @param data
- * 已加密資料
- * @param privateKey
- * 私鑰(BASE64編碼)
- *
- * @return
- * @throws Exception
- */
- publicstatic String sign(byte[] data, String privateKey) throws Exception {
- byte[] keyBytes = Base64Utils.decode(privateKey);
- PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
- KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
- PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
- Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
- signature.initSign(privateK);
- signature.update(data);
- return Base64Utils.encode(signature.sign());
- }
- /**
- * <p>
- * 校驗數字簽名
- * </p>
- *
- * @param data
- * 已加密資料
- * @param publicKey
- * 公鑰(BASE64編碼)
- * @param sign
- * 數字簽名
- *
- * @return
- * @throws Exception
- *
- */
- publicstaticboolean verify(byte[] data, String publicKey, String sign)
- throws Exception {
- byte[] keyBytes = Base64Utils.decode(publicKey);
- X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
- KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
- PublicKey publicK = keyFactory.generatePublic(keySpec);
- Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
- signature.initVerify(publicK);
- signature.update(data);
- return signature.verify(Base64Utils.decode(sign));
- }
- /**
- * <P>
- * 私鑰解密
- * </p>
- *